diff --git a/extras/rpc-spec.txt b/extras/rpc-spec.txt index 6fc56f246..5da5bc3cd 100644 --- a/extras/rpc-spec.txt +++ b/extras/rpc-spec.txt @@ -406,13 +406,32 @@ Request arguments: - string | value type & description - ---------------------------+------------------------------------------------- - "ids" | array torrent list, as described in 3.1 - "location" | string the new torrent location - "move" | boolean if true, move from previous location. - | otherwise, search "location" for files - | (default: false) + string | value type & description + ---------------------------------+------------------------------------------------- + "ids" | array torrent list, as described in 3.1 + "location" | string the new torrent location + "move" | boolean if true, move from previous location. + | otherwise, search "location" for files + | (default: false) + + Response arguments: none + + +3.7. Renaming a Torrent's Path + + Method name: "torrent-rename-path" + + For more information on the use of this function, see the + transmission.h documentation of tr_torrentRenamePath() + + Request arguments: + + string | value type & description + ---------------------------------+------------------------------------------------- + "ids" | array the torrent torrent list, as described in 3.1 + | (though, this function doesn't make sense for >1 torrent) + "path" | string the path to the file or folder that will be renamed + "name" | string the file or folder's new name Response arguments: none @@ -581,141 +600,142 @@ The following changes have been made to the RPC interface: - RPC | Release | Backwards | | - Vers. | Version | Compat? | Method | Description - ------+---------+-----------+----------------+------------------------------- - 1 | 1.30 | n/a | n/a | Initial version - ------+---------+-----------+----------------+------------------------------- - 2 | 1.34 | yes | torrent-get | new arg "peers" - ------+---------+-----------+----------------+------------------------------- - 3 | 1.41 | yes | torrent-get | added "port" to "peers" - | | yes | torrent-get | new arg "downloaders" - | | yes | session-get | new arg "version" - | | yes | torrent-remove | new method - ------+---------+-----------+----------------+------------------------------- - 4 | 1.50 | yes | session-get | new arg "rpc-version" - | | yes | session-get | new arg "rpc-version-minimum" - | | yes | session-stats | added "cumulative-stats" - | | yes | session-stats | added "current-stats" - | | yes | torrent-get | new arg "downloadDir" - ------+---------+-----------+----------------+------------------------------- - 5 | 1.60 | yes | | new method "torrent-reannounce" - | | yes | | new method "blocklist-update" - | | yes | | new method "port-test" - | | | | - | | yes | session-get | new arg "alt-speed-begin" - | | yes | session-get | new arg "alt-speed-down" - | | yes | session-get | new arg "alt-speed-enabled" - | | yes | session-get | new arg "alt-speed-end" - | | yes | session-get | new arg "alt-speed-time-enabled" - | | yes | session-get | new arg "alt-speed-up" - | | yes | session-get | new arg "blocklist-enabled" - | | yes | session-get | new arg "blocklist-size" - | | yes | session-get | new arg "peer-limit-per-torrent" - | | yes | session-get | new arg "seedRatioLimit" - | | yes | session-get | new arg "seedRatioLimited" - | | NO | session-get | renamed "pex-allowed" to "pex-enabled" - | | NO | session-get | renamed "port" to "peer-port" - | | NO | session-get | renamed "peer-limit" to "peer-limit-global" - | | | | - | | yes | torrent-add | new arg "files-unwanted" - | | yes | torrent-add | new arg "files-wanted" - | | yes | torrent-add | new arg "priority-high" - | | yes | torrent-add | new arg "priority-low" - | | yes | torrent-add | new arg "priority-normal" - | | | | - | | yes | torrent-set | new arg "bandwidthPriority" - | | yes | torrent-set | new arg "honorsSessionLimits" - | | yes | torrent-set | new arg "seedRatioLimit" - | | yes | torrent-set | new arg "seedRatioLimited" - | | NO | torrent-set | renamed "speed-limit-down" to "downloadLimit" - | | NO | torrent-set | renamed "speed-limit-down-enabled" to "downloadLimited" - | | NO | torrent-set | renamed "speed-limit-up" to "uploadLimit" - | | NO | torrent-set | renamed "speed-limit-up-enabled" to "uploadLimited" - | | | | - | | yes | torrent-get | new arg "bandwidthPriority" - | | yes | torrent-get | new arg "fileStats" - | | yes | torrent-get | new arg "honorsSessionLimits" - | | yes | torrent-get | new arg "percentDone" - | | yes | torrent-get | new arg "pieces" - | | yes | torrent-get | new arg "seedRatioLimit" - | | yes | torrent-get | new arg "seedRatioMode" - | | yes | torrent-get | new arg "torrentFile" - | | yes | torrent-get | new ids option "recently-active" - | | NO | torrent-get | removed arg "downloadLimitMode" - | | NO | torrent-get | removed arg "uploadLimitMode" - ------+---------+-----------+----------------+------------------------------- - 6 | 1.70 | yes | | new "method torrent-set-location" - ------+---------+-----------+----------------+------------------------------- - 7 | 1.80 | NO | torrent-get | removed arg "announceResponse" - | | NO | torrent-get | removed arg "announceURL" - | | NO | torrent-get | removed arg "downloaders" - | | NO | torrent-get | removed arg "lastAnnounceTime" - | | NO | torrent-get | removed arg "lastScrapeTime" - | | NO | torrent-get | removed arg "leechers" - | | NO | torrent-get | removed arg "nextAnnounceTime" - | | NO | torrent-get | removed arg "nextScrapeTime" - | | NO | torrent-get | removed arg "scrapeResponse" - | | NO | torrent-get | removed arg "scrapeURL" - | | NO | torrent-get | removed arg "seeders" - | | NO | torrent-get | removed arg "timesCompleted" - | | NO | torrent-get | removed arg "swarmSpeed" - | | yes | torrent-get | new arg "magnetLink" - | | yes | torrent-get | new arg "metadataPercentComplete" - | | yes | torrent-get | new arg "trackerStats" - | | yes | session-set | new arg "incomplete-dir" - | | yes | session-set | new arg "incomplete-dir-enabled" - ------+---------+-----------+----------------+------------------------------- - 8 | 1.90 | yes | session-set | new arg "rename-partial-files" - | | yes | session-get | new arg "rename-partial-files" - | | yes | session-get | new arg "config-dir" - | | yes | torrent-add | new arg "bandwidthPriority" - | | yes | torrent-get | new trackerStats arg "lastAnnounceTimedOut" - ------+---------+-----------+----------------+------------------------------- - 8 | 1.92 | yes | torrent-get | new trackerStats arg "lastScrapeTimedOut" - ------+---------+-----------+----------------+------------------------------- - 9 | 2.00 | yes | session-set | new arg "start-added-torrents" - | | yes | session-set | new arg "trash-original-torrent-files" - | | yes | session-get | new arg "start-added-torrents" - | | yes | session-get | new arg "trash-original-torrent-files" - | | yes | torrent-get | new arg "isFinished" - ------+---------+-----------+----------------+------------------------------- - 10 | 2.10 | yes | session-get | new arg "cache-size-mb" - | | yes | torrent-set | new arg "trackerAdd" - | | yes | torrent-set | new arg "trackerRemove" - | | yes | torrent-set | new arg "trackerReplace" - | | yes | session-set | new arg "idle-seeding-limit" - | | yes | session-set | new arg "idle-seeding-limit-enabled" - | | yes | session-get | new arg "units" - | | yes | torrent-set | new arg "seedIdleLimit" - | | yes | torrent-set | new arg "seedIdleMode" - ------+---------+-----------+----------------+------------------------------- - 11 | 2.12 | yes | session-get | new arg "blocklist-url" - | | yes | session-set | new arg "blocklist-url" - ------+---------+-----------+----------------+------------------------------- - 12 | 2.20 | yes | session-get | new arg "download-dir-free-space" - | | yes | session-close | new method - ------+---------+-----------+----------------+------------------------------- - 13 | 2.30 | yes | session-get | new arg "isUTP" to the "peers" list - | | yes | torrent-add | new arg "cookies" - | | NO | torrent-get | removed arg "peersKnown" - ------+---------+-----------+----------------+------------------------------- - 14 | 2.40 | NO | torrent-get | values of "status" field changed - | | yes | torrent-get | new arg "queuePosition" - | | yes | torrent-get | new arg "isStalled" - | | yes | torrent-get | new arg "fromLpd" in peersFrom - | | yes | torrent-set | new arg "queuePosition" - | | yes | session-set | new arg "download-queue-size" - | | yes | session-set | new arg "download-queue-enabled" - | | yes | session-set | new arg "seed-queue-size" - | | yes | session-set | new arg "seed-queue-enabled" - | | yes | session-set | new arg "queue-stalled-enabled" - | | yes | session-set | new arg "queue-stalled-minutes" - | | yes | | new method "queue-move-top" - | | yes | | new method "queue-move-up" - | | yes | | new method "queue-move-down" - | | yes | | new method "queue-move-bottom" - | | yes | | new method "torrent-start-now" - ------+---------+-----------+----------------+------------------------------- - 15 | 2.80 | yes | torrent-get | new arg "etaIdle" + RPC | Release | Backwards | | + Vers. | Version | Compat? | Method | Description + ------+---------+-----------+----------------------+------------------------------- + 1 | 1.30 | n/a | n/a | Initial version + ------+---------+-----------+----------------------+------------------------------- + 2 | 1.34 | yes | torrent-get | new arg "peers" + ------+---------+-----------+----------------------+------------------------------- + 3 | 1.41 | yes | torrent-get | added "port" to "peers" + | | yes | torrent-get | new arg "downloaders" + | | yes | session-get | new arg "version" + | | yes | torrent-remove | new method + ------+---------+-----------+----------------------+------------------------------- + 4 | 1.50 | yes | session-get | new arg "rpc-version" + | | yes | session-get | new arg "rpc-version-minimum" + | | yes | session-stats | added "cumulative-stats" + | | yes | session-stats | added "current-stats" + | | yes | torrent-get | new arg "downloadDir" + ------+---------+-----------+----------------------+------------------------------- + 5 | 1.60 | yes | | new method "torrent-reannounce" + | | yes | | new method "blocklist-update" + | | yes | | new method "port-test" + | | | | + | | yes | session-get | new arg "alt-speed-begin" + | | yes | session-get | new arg "alt-speed-down" + | | yes | session-get | new arg "alt-speed-enabled" + | | yes | session-get | new arg "alt-speed-end" + | | yes | session-get | new arg "alt-speed-time-enabled" + | | yes | session-get | new arg "alt-speed-up" + | | yes | session-get | new arg "blocklist-enabled" + | | yes | session-get | new arg "blocklist-size" + | | yes | session-get | new arg "peer-limit-per-torrent" + | | yes | session-get | new arg "seedRatioLimit" + | | yes | session-get | new arg "seedRatioLimited" + | | NO | session-get | renamed "pex-allowed" to "pex-enabled" + | | NO | session-get | renamed "port" to "peer-port" + | | NO | session-get | renamed "peer-limit" to "peer-limit-global" + | | | | + | | yes | torrent-add | new arg "files-unwanted" + | | yes | torrent-add | new arg "files-wanted" + | | yes | torrent-add | new arg "priority-high" + | | yes | torrent-add | new arg "priority-low" + | | yes | torrent-add | new arg "priority-normal" + | | | | + | | yes | torrent-set | new arg "bandwidthPriority" + | | yes | torrent-set | new arg "honorsSessionLimits" + | | yes | torrent-set | new arg "seedRatioLimit" + | | yes | torrent-set | new arg "seedRatioLimited" + | | NO | torrent-set | renamed "speed-limit-down" to "downloadLimit" + | | NO | torrent-set | renamed "speed-limit-down-enabled" to "downloadLimited" + | | NO | torrent-set | renamed "speed-limit-up" to "uploadLimit" + | | NO | torrent-set | renamed "speed-limit-up-enabled" to "uploadLimited" + | | | | + | | yes | torrent-get | new arg "bandwidthPriority" + | | yes | torrent-get | new arg "fileStats" + | | yes | torrent-get | new arg "honorsSessionLimits" + | | yes | torrent-get | new arg "percentDone" + | | yes | torrent-get | new arg "pieces" + | | yes | torrent-get | new arg "seedRatioLimit" + | | yes | torrent-get | new arg "seedRatioMode" + | | yes | torrent-get | new arg "torrentFile" + | | yes | torrent-get | new ids option "recently-active" + | | NO | torrent-get | removed arg "downloadLimitMode" + | | NO | torrent-get | removed arg "uploadLimitMode" + ------+---------+-----------+----------------------+------------------------------- + 6 | 1.70 | yes | | new "method torrent-set-location" + ------+---------+-----------+----------------------+------------------------------- + 7 | 1.80 | NO | torrent-get | removed arg "announceResponse" + | | NO | torrent-get | removed arg "announceURL" + | | NO | torrent-get | removed arg "downloaders" + | | NO | torrent-get | removed arg "lastAnnounceTime" + | | NO | torrent-get | removed arg "lastScrapeTime" + | | NO | torrent-get | removed arg "leechers" + | | NO | torrent-get | removed arg "nextAnnounceTime" + | | NO | torrent-get | removed arg "nextScrapeTime" + | | NO | torrent-get | removed arg "scrapeResponse" + | | NO | torrent-get | removed arg "scrapeURL" + | | NO | torrent-get | removed arg "seeders" + | | NO | torrent-get | removed arg "timesCompleted" + | | NO | torrent-get | removed arg "swarmSpeed" + | | yes | torrent-get | new arg "magnetLink" + | | yes | torrent-get | new arg "metadataPercentComplete" + | | yes | torrent-get | new arg "trackerStats" + | | yes | session-set | new arg "incomplete-dir" + | | yes | session-set | new arg "incomplete-dir-enabled" + ------+---------+-----------+----------------------+------------------------------- + 8 | 1.90 | yes | session-set | new arg "rename-partial-files" + | | yes | session-get | new arg "rename-partial-files" + | | yes | session-get | new arg "config-dir" + | | yes | torrent-add | new arg "bandwidthPriority" + | | yes | torrent-get | new trackerStats arg "lastAnnounceTimedOut" + ------+---------+-----------+----------------------+------------------------------- + 8 | 1.92 | yes | torrent-get | new trackerStats arg "lastScrapeTimedOut" + ------+---------+-----------+----------------------+------------------------------- + 9 | 2.00 | yes | session-set | new arg "start-added-torrents" + | | yes | session-set | new arg "trash-original-torrent-files" + | | yes | session-get | new arg "start-added-torrents" + | | yes | session-get | new arg "trash-original-torrent-files" + | | yes | torrent-get | new arg "isFinished" + ------+---------+-----------+----------------------+------------------------------- + 10 | 2.10 | yes | session-get | new arg "cache-size-mb" + | | yes | torrent-set | new arg "trackerAdd" + | | yes | torrent-set | new arg "trackerRemove" + | | yes | torrent-set | new arg "trackerReplace" + | | yes | session-set | new arg "idle-seeding-limit" + | | yes | session-set | new arg "idle-seeding-limit-enabled" + | | yes | session-get | new arg "units" + | | yes | torrent-set | new arg "seedIdleLimit" + | | yes | torrent-set | new arg "seedIdleMode" + ------+---------+-----------+----------------------+------------------------------- + 11 | 2.12 | yes | session-get | new arg "blocklist-url" + | | yes | session-set | new arg "blocklist-url" + ------+---------+-----------+----------------------+------------------------------- + 12 | 2.20 | yes | session-get | new arg "download-dir-free-space" + | | yes | session-close | new method + ------+---------+-----------+----------------------+------------------------------- + 13 | 2.30 | yes | session-get | new arg "isUTP" to the "peers" list + | | yes | torrent-add | new arg "cookies" + | | NO | torrent-get | removed arg "peersKnown" + ------+---------+-----------+--------------------------+------------------------------- + 14 | 2.40 | NO | torrent-get | values of "status" field changed + | | yes | torrent-get | new arg "queuePosition" + | | yes | torrent-get | new arg "isStalled" + | | yes | torrent-get | new arg "fromLpd" in peersFrom + | | yes | torrent-set | new arg "queuePosition" + | | yes | session-set | new arg "download-queue-size" + | | yes | session-set | new arg "download-queue-enabled" + | | yes | session-set | new arg "seed-queue-size" + | | yes | session-set | new arg "seed-queue-enabled" + | | yes | session-set | new arg "queue-stalled-enabled" + | | yes | session-set | new arg "queue-stalled-minutes" + | | yes | | new method "queue-move-top" + | | yes | | new method "queue-move-up" + | | yes | | new method "queue-move-down" + | | yes | | new method "queue-move-bottom" + | | yes | | new method "torrent-start-now" + ------+---------+-----------+--------------------------+------------------------------- + 15 | 2.80 | yes | torrent-get | new arg "etaIdle" + | | yes | torrent-rename-path | new method diff --git a/libtransmission/Makefile.am b/libtransmission/Makefile.am index 8a2c26a04..5c742d907 100644 --- a/libtransmission/Makefile.am +++ b/libtransmission/Makefile.am @@ -1,161 +1,162 @@ AM_CPPFLAGS = \ - -I$(top_srcdir) \ - -D__TRANSMISSION__ \ - -DPACKAGE_DATA_DIR=\""$(datadir)"\" + -I$(top_srcdir) \ + -D__TRANSMISSION__ \ + -DPACKAGE_DATA_DIR=\""$(datadir)"\" AM_CFLAGS = \ - @DHT_CFLAGS@ \ - @LIBUTP_CFLAGS@ \ - @LIBUPNP_CFLAGS@ \ - @LIBNATPMP_CFLAGS@ \ - @LIBEVENT_CFLAGS@ \ - @LIBCURL_CFLAGS@ \ - @OPENSSL_CFLAGS@ \ - @PTHREAD_CFLAGS@ \ - @ZLIB_CFLAGS@ + @DHT_CFLAGS@ \ + @LIBUTP_CFLAGS@ \ + @LIBUPNP_CFLAGS@ \ + @LIBNATPMP_CFLAGS@ \ + @LIBEVENT_CFLAGS@ \ + @LIBCURL_CFLAGS@ \ + @OPENSSL_CFLAGS@ \ + @PTHREAD_CFLAGS@ \ + @ZLIB_CFLAGS@ noinst_LIBRARIES = libtransmission.a libtransmission_a_SOURCES = \ - announcer.c \ - announcer-http.c \ - announcer-udp.c \ - bandwidth.c \ - bitfield.c \ - blocklist.c \ - cache.c \ - clients.c \ - completion.c \ - ConvertUTF.c \ - crypto.c \ - fdlimit.c \ - handshake.c \ - history.c \ - inout.c \ - list.c \ - magnet.c \ - makemeta.c \ - metainfo.c \ - natpmp.c \ - net.c \ - peer-io.c \ - peer-mgr.c \ - peer-msgs.c \ - platform.c \ - port-forwarding.c \ - ptrarray.c \ - quark.c \ - resume.c \ - rpcimpl.c \ - rpc-server.c \ - session.c \ - stats.c \ - torrent.c \ - torrent-ctor.c \ - torrent-magnet.c \ - tr-dht.c \ - tr-lpd.c \ - tr-udp.c \ - tr-utp.c \ - tr-getopt.c \ - trevent.c \ - upnp.c \ - utils.c \ - variant.c \ - variant-benc.c \ - variant-json.c \ - verify.c \ - web.c \ - webseed.c \ - wildmat.c + announcer.c \ + announcer-http.c \ + announcer-udp.c \ + bandwidth.c \ + bitfield.c \ + blocklist.c \ + cache.c \ + clients.c \ + completion.c \ + ConvertUTF.c \ + crypto.c \ + fdlimit.c \ + handshake.c \ + history.c \ + inout.c \ + list.c \ + magnet.c \ + makemeta.c \ + metainfo.c \ + natpmp.c \ + net.c \ + peer-io.c \ + peer-mgr.c \ + peer-msgs.c \ + platform.c \ + port-forwarding.c \ + ptrarray.c \ + quark.c \ + resume.c \ + rpcimpl.c \ + rpc-server.c \ + session.c \ + stats.c \ + torrent.c \ + torrent-ctor.c \ + torrent-magnet.c \ + tr-dht.c \ + tr-lpd.c \ + tr-udp.c \ + tr-utp.c \ + tr-getopt.c \ + trevent.c \ + upnp.c \ + utils.c \ + variant.c \ + variant-benc.c \ + variant-json.c \ + verify.c \ + web.c \ + webseed.c \ + wildmat.c noinst_HEADERS = \ - announcer.h \ - announcer-common.h \ - bandwidth.h \ - bitfield.h \ - blocklist.h \ - cache.h \ - clients.h \ - ConvertUTF.h \ - crypto.h \ - completion.h \ - fdlimit.h \ - handshake.h \ - history.h \ - inout.h \ - jsonsl.c \ - jsonsl.h \ - libtransmission-test.h \ - list.h \ - magnet.h \ - makemeta.h \ - metainfo.h \ - natpmp_local.h \ - net.h \ - peer-common.h \ - peer-io.h \ - peer-mgr.h \ - peer-msgs.h \ - platform.h \ - port-forwarding.h \ - ptrarray.h \ - quark.h \ - resume.h \ - rpcimpl.h \ - rpc-server.h \ - session.h \ - stats.h \ - torrent.h \ - torrent-magnet.h \ - tr-getopt.h \ - transmission.h \ - tr-dht.h \ - tr-udp.h \ - tr-utp.h \ - tr-lpd.h \ - trevent.h \ - upnp.h \ - utils.h \ - variant.h \ - variant-common.h \ - verify.h \ - version.h \ - web.h \ - webseed.h + announcer.h \ + announcer-common.h \ + bandwidth.h \ + bitfield.h \ + blocklist.h \ + cache.h \ + clients.h \ + ConvertUTF.h \ + crypto.h \ + completion.h \ + fdlimit.h \ + handshake.h \ + history.h \ + inout.h \ + jsonsl.c \ + jsonsl.h \ + libtransmission-test.h \ + list.h \ + magnet.h \ + makemeta.h \ + metainfo.h \ + natpmp_local.h \ + net.h \ + peer-common.h \ + peer-io.h \ + peer-mgr.h \ + peer-msgs.h \ + platform.h \ + port-forwarding.h \ + ptrarray.h \ + quark.h \ + resume.h \ + rpcimpl.h \ + rpc-server.h \ + session.h \ + stats.h \ + torrent.h \ + torrent-magnet.h \ + tr-getopt.h \ + transmission.h \ + tr-dht.h \ + tr-udp.h \ + tr-utp.h \ + tr-lpd.h \ + trevent.h \ + upnp.h \ + utils.h \ + variant.h \ + variant-common.h \ + verify.h \ + version.h \ + web.h \ + webseed.h TESTS = \ - bitfield-test \ - blocklist-test \ - clients-test \ - history-test \ - json-test \ - magnet-test \ - metainfo-test \ - peer-msgs-test \ - quark-test \ - rpc-test \ - test-peer-id \ - utils-test \ - variant-test + bitfield-test \ + blocklist-test \ + clients-test \ + history-test \ + json-test \ + magnet-test \ + metainfo-test \ + peer-msgs-test \ + quark-test \ + rename-test \ + rpc-test \ + test-peer-id \ + utils-test \ + variant-test noinst_PROGRAMS = $(TESTS) apps_ldflags = \ - @ZLIB_LDFLAGS@ + @ZLIB_LDFLAGS@ apps_ldadd = \ - ./libtransmission.a \ - @LIBUPNP_LIBS@ \ - @LIBNATPMP_LIBS@ \ - @INTLLIBS@ \ - @DHT_LIBS@ \ - @LIBUTP_LIBS@ \ - @LIBCURL_LIBS@ \ - @LIBEVENT_LIBS@ \ - @OPENSSL_LIBS@ \ - @PTHREAD_LIBS@ \ - @ZLIB_LIBS@ + ./libtransmission.a \ + @LIBUPNP_LIBS@ \ + @LIBNATPMP_LIBS@ \ + @INTLLIBS@ \ + @DHT_LIBS@ \ + @LIBUTP_LIBS@ \ + @LIBCURL_LIBS@ \ + @LIBEVENT_LIBS@ \ + @OPENSSL_LIBS@ \ + @PTHREAD_LIBS@ \ + @ZLIB_LIBS@ TEST_SOURCES = libtransmission-test.c @@ -210,3 +211,7 @@ utils_test_LDFLAGS = ${apps_ldflags} variant_test_SOURCES = variant-test.c $(TEST_SOURCES) variant_test_LDADD = ${apps_ldadd} variant_test_LDFLAGS = ${apps_ldflags} + +rename_test_SOURCES = rename-test.c $(TEST_SOURCES) +rename_test_LDADD = ${apps_ldadd} +rename_test_LDFLAGS = ${apps_ldflags} diff --git a/libtransmission/rename-test.c b/libtransmission/rename-test.c new file mode 100644 index 000000000..de811fa5e --- /dev/null +++ b/libtransmission/rename-test.c @@ -0,0 +1,520 @@ +#include +#include +#include /* remove() */ +#include /* strcmp() */ +#include + +#include /* stat(), opendir() */ +#include /* stat() */ +#include /* opendir() */ +#include /* getcwd() */ + +#include "transmission.h" +#include "resume.h" +#include "torrent.h" /* tr_isTorrent() */ +#include "utils.h" /* tr_mkdirp() */ +#include "variant.h" + +#include "libtransmission-test.h" + +static tr_session * session = NULL; + +static char* +tr_getcwd (void) +{ + char * result; + char buf[2048]; + +#ifdef WIN32 + result = _getcwd (buf, sizeof (buf)); +#else + result = getcwd (buf, sizeof (buf)); +#endif + + if (result == NULL) + { + fprintf (stderr, "getcwd error: \"%s\"", tr_strerror (errno)); + *buf = '\0'; + } + + return tr_strdup (buf); +} + +/*** +**** +***/ + +#define verify_and_block_until_done(tor) \ + do { \ + tr_torrentVerify (tor); \ + do { \ + tr_wait_msec (10); \ + } while (tor->verifyState != TR_VERIFY_NONE); \ + } while (0) + +#define check_have_none(tor, totalSize) \ + do { \ + const tr_stat * st = tr_torrentStat(tor); \ + check_int_eq (TR_STATUS_STOPPED, st->activity); \ + check_int_eq (TR_STAT_OK, st->error); \ + check_int_eq (totalSize, st->sizeWhenDone); \ + check_int_eq (totalSize, st->leftUntilDone); \ + check_int_eq (totalSize, tor->info.totalSize); \ + check_int_eq (0, st->haveValid); \ + } while (0) + +#define check_have_all(tor, totalSize) \ + do { \ + const tr_stat * st = tr_torrentStat(tor); \ + check_int_eq (TR_STATUS_STOPPED, st->activity); \ + check_int_eq (TR_STAT_OK, st->error); \ + check_int_eq (0, st->leftUntilDone); \ + check_int_eq (0, st->haveUnchecked); \ + check_int_eq (0, st->desiredAvailable); \ + check_int_eq (totalSize, st->sizeWhenDone); \ + check_int_eq (totalSize, st->haveValid); \ + } while (0) + +static bool +testFileExistsAndConsistsOfThisString (const tr_torrent * tor, tr_file_index_t fileIndex, const char * str) +{ + char * path; + const size_t str_len = strlen (str); + bool success = false; + + path = tr_torrentFindFile (tor, fileIndex); + if (path != NULL) + { + uint8_t * contents; + size_t contents_len; + + assert (tr_fileExists (path, NULL)); + + contents = tr_loadFile (path, &contents_len); + + success = (str_len == contents_len) + && (!memcmp (contents, str, contents_len)); + + tr_free (contents); + tr_free (path); + } + + return success; +} + +static void +onRenameDone (tr_torrent * tor UNUSED, const char * oldpath UNUSED, const char * newname UNUSED, int error, void * user_data) +{ + *(int*)user_data = error; +} + +static int +torrentRenameAndWait (tr_torrent * tor, + const char * oldpath, + const char * newname) +{ + int error = -1; + tr_torrentRenamePath (tor, oldpath, newname, onRenameDone, &error); + do { + tr_wait_msec (10); + } while (error == -1); + return error; +} + +/*** +**** +***/ + +static void +create_file_with_contents (const char * path, const char * str) +{ + FILE * fp; + char * dir; + + dir = tr_dirname (path); + tr_mkdirp (dir, 0700); + tr_free (dir); + + remove (path); + fp = fopen (path, "wb"); + fprintf (fp, "%s", str); + fclose (fp); + + sync (); +} + +static void +create_single_file_torrent_contents (const char * top) +{ + char * path = tr_buildPath (top, "hello-world.txt", NULL); + create_file_with_contents (path, "hello, world!\n"); + tr_free (path); +} + +static tr_torrent * +create_torrent_from_base64_metainfo (tr_ctor * ctor, const char * metainfo_base64) +{ + int err; + int metainfo_len; + char * metainfo; + tr_torrent * tor; + + /* create the torrent ctor */ + metainfo = tr_base64_decode (metainfo_base64, -1, &metainfo_len); + assert (metainfo != NULL); + assert (metainfo_len > 0); + assert (session != NULL); + tr_ctorSetMetainfo (ctor, (uint8_t*)metainfo, metainfo_len); + tr_ctorSetPaused (ctor, TR_FORCE, true); + + /* create the torrent */ + err = 0; + tor = tr_torrentNew (ctor, &err); + assert (!err); + + /* cleanup */ + tr_free (metainfo); + return tor; +} + +static int +test_single_filename_torrent (void) +{ + tr_torrent * tor; + char * tmpstr; + const size_t totalSize = 14; + tr_ctor * ctor; + + /* this is a single-file torrent whose file is hello-world.txt, holding the string "hello, world!" */ + ctor = tr_ctorNew (session); + tor = create_torrent_from_base64_metainfo (ctor, + "ZDEwOmNyZWF0ZWQgYnkyNTpUcmFuc21pc3Npb24vMi42MSAoMTM0MDcpMTM6Y3JlYXRpb24gZGF0" + "ZWkxMzU4NTQ5MDk4ZTg6ZW5jb2Rpbmc1OlVURi04NDppbmZvZDY6bGVuZ3RoaTE0ZTQ6bmFtZTE1" + "OmhlbGxvLXdvcmxkLnR4dDEyOnBpZWNlIGxlbmd0aGkzMjc2OGU2OnBpZWNlczIwOukboJcrkFUY" + "f6LvqLXBVvSHqCk6Nzpwcml2YXRlaTBlZWU="); + check (tr_isTorrent (tor)); + tr_ctorFree (ctor); + + /* sanity check the info */ + check_int_eq (1, tor->info.fileCount); + check_streq ("hello-world.txt", tor->info.files[0].name); + check (!tor->info.files[0].is_renamed); + + /* sanity check the (empty) stats */ + verify_and_block_until_done (tor); + check_have_none (tor, totalSize); + + create_single_file_torrent_contents (tor->downloadDir); + + /* sanity check the stats again, now that we've added the file */ + verify_and_block_until_done (tor); + check_have_all (tor, totalSize); + + /** + *** okay! we've finally put together all the scaffolding to test + *** renaming a single-file torrent + **/ + + /* confirm that bad inputs get caught */ + + check_int_eq (EINVAL, torrentRenameAndWait (tor, "hello-world.txt", NULL)); + check_int_eq (EINVAL, torrentRenameAndWait (tor, "hello-world.txt", "")); + check_int_eq (EINVAL, torrentRenameAndWait (tor, "hello-world.txt", ".")); + check_int_eq (EINVAL, torrentRenameAndWait (tor, "hello-world.txt", "..")); + check_int_eq (0, torrentRenameAndWait (tor, "hello-world.txt", "hello-world.txt")); + check_int_eq (EINVAL, torrentRenameAndWait (tor, "hello-world.txt", "hello/world.txt")); + + check (!tor->info.files[0].is_renamed); + check_streq ("hello-world.txt", tor->info.files[0].name); + + /*** + **** Now try a rename that should succeed + ***/ + + tmpstr = tr_buildPath (tor->downloadDir, "hello-world.txt", NULL); + check (tr_fileExists (tmpstr, NULL)); + check_int_eq (0, torrentRenameAndWait (tor, "hello-world.txt", "foobar")); + check (!tr_fileExists (tmpstr, NULL)); + check (tor->info.files[0].is_renamed); + check_streq ("foobar", tor->info.files[0].name); + tr_free (tmpstr); + check (testFileExistsAndConsistsOfThisString (tor, 0, "hello, world!\n")); + + /*** + **** ...and rename it back again + ***/ + + tmpstr = tr_buildPath (tor->downloadDir, "foobar", NULL); + check (tr_fileExists (tmpstr, NULL)); + check_int_eq (0, torrentRenameAndWait (tor, "foobar", "hello-world.txt")); + check (!tr_fileExists (tmpstr, NULL)); + check (tor->info.files[0].is_renamed); + check_streq ("hello-world.txt", tor->info.files[0].name); + tr_free (tmpstr); + check (testFileExistsAndConsistsOfThisString (tor, 0, "hello, world!\n")); + + /* cleanup */ + tr_torrentRemove (tor, false, NULL); + return 0; +} + +/*** +**** +**** +**** +***/ + +static void +create_multifile_torrent_contents (const char * top) +{ + char * path; + + path = tr_buildPath (top, "Felidae", "Felinae", "Acinonyx", "Cheetah", "Chester", NULL); + create_file_with_contents (path, "It ain't easy bein' cheesy.\n"); + tr_free (path); + + path = tr_buildPath (top, "Felidae", "Pantherinae", "Panthera", "Tiger", "Tony", NULL); + create_file_with_contents (path, "They’re Grrrrreat!\n"); + tr_free (path); + + path = tr_buildPath (top, "Felidae", "Felinae", "Felis", "catus", "Kyphi", NULL); + create_file_with_contents (path, "Inquisitive\n"); + tr_free (path); + + path = tr_buildPath (top, "Felidae", "Felinae", "Felis", "catus", "Saffron", NULL); + create_file_with_contents (path, "Tough\n"); + tr_free (path); + + sync (); +} + +static int +test_multifile_torrent (void) +{ + //tr_file_stat * file_stats; + //tr_file_index_t n; + tr_file_index_t i; + uint64_t loaded; + tr_torrent * tor; + //tr_file_index_t i; + tr_ctor * ctor; + char * str; + char * tmp; + static const size_t totalSize = 67; + const tr_file * files; + const char * expected_files[4] = { + "Felidae/Felinae/Acinonyx/Cheetah/Chester", + "Felidae/Felinae/Felis/catus/Kyphi", + "Felidae/Felinae/Felis/catus/Saffron", + "Felidae/Pantherinae/Panthera/Tiger/Tony" + }; + const char * expected_contents[4] = { + "It ain't easy bein' cheesy.\n", + "Inquisitive\n", + "Tough\n", + "They’re Grrrrreat!\n" + }; + + ctor = tr_ctorNew (session); + tor = create_torrent_from_base64_metainfo (ctor, + "ZDEwOmNyZWF0ZWQgYnkyNTpUcmFuc21pc3Npb24vMi42MSAoMTM0MDcpMTM6Y3JlYXRpb24gZGF0" + "ZWkxMzU4NTU1NDIwZTg6ZW5jb2Rpbmc1OlVURi04NDppbmZvZDU6ZmlsZXNsZDY6bGVuZ3RoaTI4" + "ZTQ6cGF0aGw3OkZlbGluYWU4OkFjaW5vbnl4NzpDaGVldGFoNzpDaGVzdGVyZWVkNjpsZW5ndGhp" + "MTJlNDpwYXRobDc6RmVsaW5hZTU6RmVsaXM1OmNhdHVzNTpLeXBoaWVlZDY6bGVuZ3RoaTZlNDpw" + "YXRobDc6RmVsaW5hZTU6RmVsaXM1OmNhdHVzNzpTYWZmcm9uZWVkNjpsZW5ndGhpMjFlNDpwYXRo" + "bDExOlBhbnRoZXJpbmFlODpQYW50aGVyYTU6VGlnZXI0OlRvbnllZWU0Om5hbWU3OkZlbGlkYWUx" + "MjpwaWVjZSBsZW5ndGhpMzI3NjhlNjpwaWVjZXMyMDp27buFkmy8ICfNX4nsJmt0Ckm2Ljc6cHJp" + "dmF0ZWkwZWVl"); + check (tr_isTorrent (tor)); + files = tor->info.files; + + /* sanity check the info */ + check_streq (tor->info.name, "Felidae"); + check_int_eq (totalSize, tor->info.totalSize); + check_int_eq (4, tor->info.fileCount); + for (i=0; i<4; ++i) + check_streq (expected_files[i], files[i].name); + + /* sanity check the (empty) stats */ + verify_and_block_until_done (tor); + check_have_none (tor, totalSize); + + /* build the local data */ + create_multifile_torrent_contents (tor->downloadDir); + + /* sanity check the (full) stats */ + verify_and_block_until_done (tor); + check_have_all (tor, totalSize); + + /** + *** okay! let's test renaming. + **/ + + /* rename a leaf... */ + check_int_eq (0, torrentRenameAndWait (tor, "Felidae/Felinae/Felis/catus/Kyphi", "placeholder")); + check_streq (files[1].name, "Felidae/Felinae/Felis/catus/placeholder"); + check (testFileExistsAndConsistsOfThisString (tor, 1, "Inquisitive\n")); + + /* ...and back again */ + check_int_eq (0, torrentRenameAndWait (tor, "Felidae/Felinae/Felis/catus/placeholder", "Kyphi")); + check_streq (files[1].name, "Felidae/Felinae/Felis/catus/Kyphi"); + testFileExistsAndConsistsOfThisString (tor, 1, "Inquisitive\n"); + + /* rename a branch... */ + check_int_eq (0, torrentRenameAndWait (tor, "Felidae/Felinae/Felis/catus", "placeholder")); + check_streq (expected_files[0], files[0].name); + check_streq ("Felidae/Felinae/Felis/placeholder/Kyphi", files[1].name); + check_streq ("Felidae/Felinae/Felis/placeholder/Saffron", files[2].name); + check_streq (expected_files[3], files[3].name); + check (testFileExistsAndConsistsOfThisString (tor, 1, expected_contents[1])); + check (testFileExistsAndConsistsOfThisString (tor, 2, expected_contents[2])); + check (files[0].is_renamed == false); + check (files[1].is_renamed == true); + check (files[2].is_renamed == true); + check (files[3].is_renamed == false); + + /* (while the branch is renamed: confirm that the .resume file remembers the changes) */ + tr_torrentSaveResume (tor); + /* this is a bit dodgy code-wise, but let's make sure the .resume file got the name */ + tr_free (files[1].name); + tor->info.files[1].name = tr_strdup ("gabba gabba hey"); + loaded = tr_torrentLoadResume (tor, ~0, ctor); + check ((loaded & TR_FR_FILENAMES) != 0); + check_streq (expected_files[0], files[0].name); + check_streq ("Felidae/Felinae/Felis/placeholder/Kyphi", files[1].name); + check_streq ("Felidae/Felinae/Felis/placeholder/Saffron", files[2].name); + check_streq (expected_files[3], files[3].name); + + /* ...and back again */ + check_int_eq (0, torrentRenameAndWait (tor, "Felidae/Felinae/Felis/placeholder", "catus")); + for (i=0; i<4; ++i) + { + check_streq (expected_files[i], files[i].name); + check (testFileExistsAndConsistsOfThisString (tor, 1, expected_contents[1])); + } + check (files[0].is_renamed == false); + check (files[1].is_renamed == true); + check (files[2].is_renamed == true); + check (files[3].is_renamed == false); + + /*** + **** Test it an incomplete torrent... + ***/ + + /* remove the directory Felidae/Felinae/Felis/catus */ + str = tr_buildPath (tor->downloadDir, files[1].name, NULL); + remove (str); + tr_free (str); + str = tr_buildPath (tor->downloadDir, files[2].name, NULL); + remove (str); + tmp = tr_dirname (str); + remove (tmp); + tr_free (tmp); + tr_free (str); + verify_and_block_until_done (tor); + testFileExistsAndConsistsOfThisString (tor, 0, expected_contents[0]); + check (tr_torrentFindFile (tor, 1) == NULL); + check (tr_torrentFindFile (tor, 2) == NULL); + testFileExistsAndConsistsOfThisString (tor, 3, expected_contents[3]); + + /* rename a branch... */ + check_int_eq (0, torrentRenameAndWait (tor, "Felidae/Felinae/Felis/catus", "foo")); + check_streq (expected_files[0], files[0].name); + check_streq ("Felidae/Felinae/Felis/foo/Kyphi", files[1].name); + check_streq ("Felidae/Felinae/Felis/foo/Saffron", files[2].name); + check_streq (expected_files[3], files[3].name); + + /* ...and back again */ + check_int_eq (0, torrentRenameAndWait (tor, "Felidae/Felinae/Felis/foo", "catus")); + for (i=0; i<4; ++i) + check_streq (expected_files[i], files[i].name); + + check_int_eq (0, torrentRenameAndWait (tor, "Felidae", "gabba")); + check_streq ("gabba/Felinae/Acinonyx/Cheetah/Chester", files[0].name); + check_streq ("gabba/Felinae/Felis/catus/Kyphi", files[1].name); + check_streq ("gabba/Felinae/Felis/catus/Saffron", files[2].name); + check_streq ("gabba/Pantherinae/Panthera/Tiger/Tony", files[3].name); + + /*** + **** + ***/ + + /* cleanup */ + tr_ctorFree (ctor); + tr_torrentRemove (tor, false, NULL); + return 0; +} + +/*** +**** +***/ + +static void +rm_rf (const char * killme) +{ + struct stat sb; + + if (!stat (killme, &sb)) + { + DIR * odir; + + if (S_ISDIR (sb.st_mode) && ((odir = opendir (killme)))) + { + struct dirent *d; + for (d = readdir(odir); d != NULL; d=readdir(odir)) + { + if (d->d_name && strcmp(d->d_name,".") && strcmp(d->d_name,"..")) + { + char * tmp = tr_buildPath (killme, d->d_name, NULL); + rm_rf (tmp); + tr_free (tmp); + } + } + closedir (odir); + } + + if (verbose) + fprintf (stderr, "cleanup: removing %s\n", killme); + + remove (killme); + } +} + +int +main (void) +{ + int ret; + char * cwd; + char * sandbox; + char * downloadDir; + tr_variant dict; + const testFunc tests[] = { test_single_filename_torrent, + test_multifile_torrent }; + + /* create a sandbox for the test session */ + cwd = tr_getcwd (); + sandbox = tr_buildPath (cwd, "sandbox-XXXXXX", NULL); + tr_mkdtemp (sandbox); + downloadDir = tr_buildPath (sandbox, "Downloads", NULL); + tr_mkdirp (downloadDir, 0700); + + /* create a test session */ + tr_variantInitDict (&dict, 3); + tr_variantDictAddStr (&dict, TR_KEY_download_dir, downloadDir); + tr_variantDictAddBool (&dict, TR_KEY_port_forwarding_enabled, false); + tr_variantDictAddBool (&dict, TR_KEY_dht_enabled, false); + session = tr_sessionInit ("rename-test", sandbox, true, &dict); + + /* run the tests */ + ret = runTests (tests, NUM_TESTS (tests)); + + /* cleanup */ + tr_sessionClose (session); + tr_freeMessageList (tr_getQueuedMessages ()); + tr_variantFree (&dict); + rm_rf (sandbox); + tr_free (downloadDir); + tr_free (sandbox); + tr_free (cwd); + return ret; +} diff --git a/libtransmission/resume.c b/libtransmission/resume.c index 93567b4a3..aa767a551 100644 --- a/libtransmission/resume.c +++ b/libtransmission/resume.c @@ -335,6 +335,62 @@ loadIdleLimits (tr_variant * dict, tr_torrent * tor) return ret; } + +/*** +**** +***/ + +static void +saveFilenames (tr_variant * dict, const tr_torrent * tor) +{ + tr_file_index_t i; + bool any_renamed; + const tr_file_index_t n = tor->info.fileCount; + const tr_file * files = tor->info.files; + + any_renamed = false; + for (i=0; !any_renamed && iinfo.files; + + for (i=0; iinfo.fileCount && iinfo.fileCount); + + n = 0; + oldpath_len = strlen (oldpath); + for (i=0; i!=tor->info.fileCount; ++i) + { + const char * name = tor->info.files[i].name; + const size_t len = strlen (name); + if ((len >= oldpath_len) && !memcmp (oldpath, name, oldpath_len)) + indices[n++] = i; + } + + *setme_n = n; + return indices; +} + +static int +renamePath (tr_torrent * tor, + const char * oldpath, + const char * newname) +{ + char * src; + const char * base; + int error = 0; + + if (!tr_torrentIsSeed(tor) && (tor->incompleteDir != NULL)) + base = tor->incompleteDir; + else + base = tor->downloadDir; + + src = tr_buildPath (base, oldpath, NULL); + /*fprintf (stderr, "%s:%d src \"%s\"\n", __FILE__, __LINE__, src);*/ + + if (tr_fileExists (src, NULL)) + { + int tmp; + bool tgt_exists; + char * parent = tr_dirname (src); + char * tgt = tr_buildPath (parent, newname, NULL); + + tmp = errno; + tgt_exists = tr_fileExists (tgt, NULL); + errno = tmp; + /*fprintf (stderr, "%s:%d tgt \"%s\"\n", __FILE__, __LINE__, tgt);*/ + + if (!tgt_exists) + { + int rv; + + tmp = errno; + rv = rename (src, tgt); + /*fprintf (stderr, "%s:%d rv \"%d\"\n", __FILE__, __LINE__, rv);*/ + if (rv != 0) + error = errno; + errno = tmp; + } + + tr_free (tgt); + tr_free (parent); + } + + tr_free (src); + + return error; +} + +static void +renameTorrentFileString (tr_torrent * tor, + const char * oldpath, + const char * newname, + tr_file_index_t fileIndex) +{ + char * name; + tr_file * file = &tor->info.files[fileIndex]; + const size_t oldpath_len = strlen (oldpath); + + if (strchr (oldpath, TR_PATH_DELIMITER) == NULL) + { + if (oldpath_len >= strlen(file->name)) + name = tr_buildPath (newname, NULL); + else + name = tr_buildPath (newname, file->name + oldpath_len + 1, NULL); + } + else + { + char * tmp = tr_dirname (oldpath); + + if (oldpath_len >= strlen(file->name)) + name = tr_buildPath (tmp, newname, NULL); + else + name = tr_buildPath (tmp, newname, file->name + oldpath_len + 1, NULL); + + tr_free (tmp); + } + + if (!strcmp (file->name, name)) + { + tr_free (name); + } + else + { + tr_free (file->name); + file->name = name; + file->is_renamed = true; + } +} + +struct rename_data +{ + tr_torrent * tor; + char * oldpath; + char * newname; + tr_torrent_rename_done_func * callback; + void * callback_user_data; +}; + +static void +torrentRenamePath (void * vdata) +{ + int error = 0; + struct rename_data * data = vdata; + tr_torrent * const tor = data->tor; + const char * const oldpath = data->oldpath; + const char * const newname = data->newname; + + /*** + **** + ***/ + + assert (tr_isTorrent (tor)); + + if (!renameArgsAreValid (oldpath, newname)) + { + error = EINVAL; + } + else + { + size_t n; + tr_file_index_t * file_indices; + + file_indices = renameFindAffectedFiles (tor, oldpath, &n); + if (n == 0) + { + errno = EINVAL; + } + else + { + size_t i; + + error = renamePath (tor, oldpath, newname); + + if (!error) + { + for (i=0; icallback != NULL) + (*data->callback)(tor, data->oldpath, data->newname, error, data->callback_user_data); + + /* cleanup */ + tr_free (data->oldpath); + tr_free (data->newname); + tr_free (data); +} + +void +tr_torrentRenamePath (tr_torrent * tor, + const char * oldpath, + const char * newname, + tr_torrent_rename_done_func callback, + void * callback_user_data) +{ + struct rename_data * data; + + data = tr_new0 (struct rename_data, 1); + data->tor = tor; + data->oldpath = tr_strdup (oldpath); + data->newname = tr_strdup (newname); + data->callback = callback; + data->callback_user_data = callback_user_data; + + tr_runInEventThread (tor->session, torrentRenamePath, data); +} diff --git a/libtransmission/transmission.h b/libtransmission/transmission.h index 07e526c19..8d2a28777 100644 --- a/libtransmission/transmission.h +++ b/libtransmission/transmission.h @@ -1093,6 +1093,57 @@ void tr_torrentStart (tr_torrent * torrent); /** @brief Stop (pause) a torrent */ void tr_torrentStop (tr_torrent * torrent); + +typedef void (tr_torrent_rename_done_func)(tr_torrent * torrent, + const char * oldpath, + const char * newname, + int error, + void * user_data); + +/** + * @brief Rename a file or directory in a torrent. + * + * @tor: the torrent whose path will be renamed. + * @oldpath: the path to the file or folder that will be renamed + * @newname: the file or folder's new name + * @callback: the callback invoked when the renaming finishes, or NULL + * @callback_data: the pointer to pass in the callback's user_data arg + * + * EXAMPLES + * + * Consider a torrent where + * files[0].path is "frobnitz-linux/checksum" and + * files[1].path is "frobnitz-linux/frobnitz.iso". + * + * 1. tr_torrentRenamePath (tor, "frobnitz-linux", "foo") will rename + * the "frotbnitz-linux" folder as "foo" and update files[*].path. + * + * 2. tr_torrentRenamePath (tor, "frobnitz-linux/checksum", "foo") will + * rename the "frobnitz-linux/checksum" file as "foo" and update + * files[0].path to "frobnitz-linux/foo". + * + * RETURN + * + * Changing tr_info's contents requires a session lock, so this function + * returns asynchronously to avoid blocking. If you don't care about error + * checking, you can pass NULL as the callback and callback_user_data arg. + * + * On success, the callback's error argument will be 0. + * + * If oldpath can't be found in files[*].path, or if newname is already + * in files[*].path, or contains a directory separator, or is NULL, "", + * ".", or "..", the error argument will be EINVAL. + * + * If the path exists on disk but can't be renamed, the error argument + * will be the errno set by rename(). + */ +void tr_torrentRenamePath (tr_torrent * tor, + const char * oldpath, + const char * newname, + tr_torrent_rename_done_func callback, + void * callback_user_data); + + enum { TR_LOC_MOVING, @@ -1353,15 +1404,15 @@ tr_completeness; * it changed its completeness state */ typedef void (tr_torrent_completeness_func)(tr_torrent * torrent, - tr_completeness completeness, - bool wasRunning, - void * user_data); + tr_completeness completeness, + bool wasRunning, + void * user_data); typedef void (tr_torrent_ratio_limit_hit_func)(tr_torrent * torrent, - void * user_data); + void * user_data); typedef void (tr_torrent_idle_limit_hit_func)(tr_torrent * torrent, - void * user_data); + void * user_data); /** @@ -1683,6 +1734,7 @@ typedef struct tr_file char * name; /* Path to the file */ int8_t priority; /* TR_PRI_HIGH, _NORMAL, or _LOW */ int8_t dnd; /* "do not download" flag */ + int8_t is_renamed; /* true if we're using a different path from the one in the metainfo; ie, if the user has renamed it */ tr_piece_index_t firstPiece; /* We need pieces [firstPiece... */ tr_piece_index_t lastPiece; /* ...lastPiece] to dl this file */ uint64_t offset; /* file begins at the torrent's nth byte */