(trunk) #920: add "move data" to libT so all clients can use it

This commit is contained in:
Charles Kerr 2009-05-13 15:54:04 +00:00
parent ccb14d491a
commit 29b1d3e2cc
31 changed files with 715 additions and 52 deletions

2
NEWS
View File

@ -2,6 +2,8 @@ NEWS file for Transmission <http://www.transmissionbt.com/>
1.70 (2009/mm/dd)
<http://trac.transmissionbt.com/query?milestone=1.70&group=component&groupdesc=1&order=severity>
- All Platforms
+ Add ability to move a torrent's data and/or tell Transmission where to look for it
- Mac
+ Hold down the option key on launch to pause all transfers

View File

@ -75,6 +75,8 @@ static tr_option opts[] =
{ 'i', "info", "Show the current torrent(s)' details", "i", 0, NULL },
{ 920, "session-info", "Show the session's details", "si", 0, NULL },
{ 'l', "list", "List all torrents", "l", 0, NULL },
{ 960, "move", "Move current torrent's data to a new folder", NULL, 1, "<path>" },
{ 961, "find", "Tell Transmission where to find a torrent's data", NULL, 1, "<path>" },
{ 'm', "portmap", "Enable portmapping via NAT-PMP or UPnP", "m", 0, NULL },
{ 'M', "no-portmap", "Disable portmapping", "M", 0, NULL },
{ 'n', "auth", "Set authentication info", "n", 1, "<username:password>" },
@ -588,6 +590,20 @@ readargs( int argc,
tr_bencDictAddBool( args, "seedRatioLimited", FALSE );
break;
case 960:
tr_bencDictAddStr( &top, "method", "torrent-set-location" );
tr_bencDictAddStr( args, "location", optarg );
tr_bencDictAddBool( args, "move", TRUE );
addIdArg( args, id );
break;
case 961:
tr_bencDictAddStr( &top, "method", "torrent-set-location" );
tr_bencDictAddStr( args, "location", optarg );
tr_bencDictAddBool( args, "move", FALSE );
addIdArg( args, id );
break;
case TR_OPT_ERR:
fprintf( stderr, "invalid option\n" );
showUsage( );

View File

@ -167,6 +167,12 @@ Remove the current torrent(s). This does not delete the downloaded data.
.It Fl -remove-and-delete
Remove the current torrent(s) and delete their downloaded data.
.It Fl -move
Move the current torrents' data from their current locations to the specified directory.
.It Fl -find
Tell Transmission where to look for the current torrents' data.
.It Fl sr Fl -seedratio Ar ratio
Let the current torrent(s) seed until a specific
.Ar ratio

View File

@ -95,6 +95,7 @@
"files-unwanted" | array indices of file(s) to not download
"honorsSessionLimits" | boolean true if session upload limits are honored
"ids" | array torrent list, as described in 3.1
"location" | string new location of the torrent's content
"peer-limit" | number maximum number of peers
"priority-high" | array indices of high-priority file(s)
"priority-low" | array indices of low-priority file(s)
@ -344,6 +345,21 @@
Response arguments: none
3.6. Moving a Torrent
Method name: "torrent-set-location"
Request arguments:
string | value type & description
---------------------------+-------------------------------------------------
"ids" | array torrent list, as described in 3.1
"location" | string destination folder
"move" | boolean if true, move from previous location
Response arguments: none
4. Session Requests
4.1. Session Arguments
@ -513,5 +529,7 @@
| | NO | torrent-get | removed arg "downloadLimitMode"
| | NO | torrent-get | removed arg "uploadLimitMode"
------+---------+-----------+----------------+-------------------------------
6 | 1.70 | yes | torrent-set | new arg "location"
------+---------+-----------+----------------+-------------------------------

View File

@ -49,6 +49,7 @@ noinst_HEADERS = \
msgwin.h \
notify.h \
options-icon.h \
relocate.h \
stats.h \
sexy-icon-entry.h \
sexy-marshal.h \
@ -81,6 +82,7 @@ transmission_SOURCES = \
makemeta-ui.c \
msgwin.c \
notify.c \
relocate.c \
sexy-icon-entry.c \
sexy-marshal.c \
stats.c \

View File

@ -101,7 +101,7 @@ static GtkToggleActionEntry pref_toggle_entries[] =
N_( "_Toolbar" ), NULL, NULL, G_CALLBACK( toggle_pref_cb ), FALSE }
};
static GtkActionEntry entries[] =
static GtkActionEntry entries[] =
{
{ "torrent-menu", NULL, N_( "_Torrent" ), NULL, NULL, NULL },
{ "view-menu", NULL, N_( "_View" ), NULL, NULL, NULL },
@ -116,6 +116,7 @@ static GtkActionEntry entries[] =
{ "pause-torrent", GTK_STOCK_MEDIA_PAUSE, N_( "_Pause" ), "<control>P", N_( "Pause torrent" ), G_CALLBACK( action_cb ) },
{ "pause-all-torrents", GTK_STOCK_MEDIA_PAUSE, N_( "_Pause All" ), NULL, N_( "Pause all torrents" ), G_CALLBACK( action_cb ) },
{ "start-all-torrents", GTK_STOCK_MEDIA_PLAY, N_( "_Start All" ), NULL, N_( "Start all torrents" ), G_CALLBACK( action_cb ) },
{ "relocate-torrent", NULL, N_("Set _Location" ), NULL, NULL, G_CALLBACK( action_cb ) },
{ "remove-torrent", GTK_STOCK_REMOVE, NULL, "Delete", N_( "Remove torrent" ), G_CALLBACK( action_cb ) },
{ "delete-torrent", GTK_STOCK_DELETE, N_( "_Delete Files and Remove" ), "<shift>Delete", NULL, G_CALLBACK( action_cb ) },
{ "new-torrent", GTK_STOCK_NEW, N_( "_New..." ), NULL, N_( "Create a torrent" ), G_CALLBACK( action_cb ) },

View File

@ -262,13 +262,13 @@ get_mime_type_icon( const char * mime_type,
int n;
switch ( icon_size ) {
case GTK_ICON_SIZE_INVALID: n = 0; break;
case GTK_ICON_SIZE_MENU: n = 1; break;
case GTK_ICON_SIZE_SMALL_TOOLBAR: n = 2; break;
case GTK_ICON_SIZE_LARGE_TOOLBAR: n = 3; break;
case GTK_ICON_SIZE_BUTTON: n = 4; break;
case GTK_ICON_SIZE_DND: n = 5; break;
case GTK_ICON_SIZE_DIALOG: n = 6; break;
case GTK_ICON_SIZE_MENU: n = 1; break;
case GTK_ICON_SIZE_SMALL_TOOLBAR: n = 2; break;
case GTK_ICON_SIZE_LARGE_TOOLBAR: n = 3; break;
case GTK_ICON_SIZE_BUTTON: n = 4; break;
case GTK_ICON_SIZE_DND: n = 5; break;
case GTK_ICON_SIZE_DIALOG: n = 6; break;
default /*GTK_ICON_SIZE_INVALID*/: n = 0; break;
}
if( icon_cache[n] == NULL )

View File

@ -49,6 +49,7 @@
#include "makemeta-ui.h"
#include "msgwin.h"
#include "notify.h"
#include "relocate.h"
#include "stats.h"
#include "tr-core.h"
#include "tr-icon.h"
@ -203,6 +204,7 @@ refreshActions( struct cbdata * data )
action_sensitize( "delete-torrent", counts.totalCount != 0 );
action_sensitize( "verify-torrent", counts.totalCount != 0 );
action_sensitize( "open-torrent-folder", counts.totalCount == 1 );
action_sensitize( "relocate-torrent", counts.totalCount == 1 );
canUpdate = 0;
gtk_tree_selection_selected_foreach( s, accumulateCanUpdateForeach, &canUpdate );
@ -1281,6 +1283,24 @@ pauseAllTorrents( struct cbdata * data )
tr_rpc_request_exec_json( session, cmd, strlen( cmd ), NULL, NULL );
}
static tr_torrent*
getFirstSelectedTorrent( struct cbdata * data )
{
tr_torrent * tor = NULL;
GtkTreeSelection * s = tr_window_get_selection( data->wind );
GtkTreeModel * m;
GList * l = gtk_tree_selection_get_selected_rows( s, &m );
if( l != NULL ) {
GtkTreePath * p = l->data;
GtkTreeIter i;
if( gtk_tree_model_get_iter( m, &i, p ) )
gtk_tree_model_get( m, &i, MC_TORRENT_RAW, &tor, -1 );
}
g_list_foreach( l, (GFunc)gtk_tree_path_free, NULL );
g_list_free( l );
return tor;
}
void
doAction( const char * action_name, gpointer user_data )
{
@ -1311,6 +1331,16 @@ doAction( const char * action_name, gpointer user_data )
{
startAllTorrents( data );
}
else if( !strcmp( action_name, "relocate-torrent" ) )
{
tr_torrent * tor = getFirstSelectedTorrent( data );
if( tor )
{
GtkWindow * parent = GTK_WINDOW( data->wind );
GtkWidget * w = gtr_relocate_dialog_new( parent, tor );
gtk_widget_show( w );
}
}
else if( !strcmp( action_name, "pause-torrent" ) )
{
GtkTreeSelection * s = tr_window_get_selection( data->wind );

135
gtk/relocate.c Normal file
View File

@ -0,0 +1,135 @@
/*
* This file Copyright (C) 2009 Charles Kerr <charles@transmissionbt.com>
*
* This file is licensed by the GPL version 2. Works owned by the
* Transmission project are granted a special exemption to clause 2(b)
* so that the bulk of its code can remain under the MIT license.
* This exemption does not extend to derived works not owned by
* the Transmission project.
*
* $Id:$
*/
#include <libtransmission/transmission.h>
#include <glib/gi18n.h>
#include <gtk/gtk.h>
#include "conf.h" /* pref_string_get */
#include "hig.h"
#include "relocate.h"
#include "util.h"
static char * previousLocation = NULL;
struct UpdateData
{
GtkDialog * dialog;
tr_bool done;
};
/* every once in awhile, check to see if the move is done.
* if so, delete the dialog */
static gboolean
onTimer( gpointer gdata )
{
struct UpdateData * data = gdata;
const tr_bool done = data->done;
if( done )
{
gtk_widget_destroy( GTK_WIDGET( data->dialog ) );
g_free( data );
}
return !done;
}
static void
onResponse( GtkDialog * dialog, int response, gpointer unused UNUSED )
{
if( response == GTK_RESPONSE_APPLY )
{
struct UpdateData * updateData;
GtkWidget * w;
GObject * d = G_OBJECT( dialog );
tr_torrent * tor = g_object_get_data( d, "torrent" );
GtkFileChooser * chooser = g_object_get_data( d, "chooser" );
GtkToggleButton * move_tb = g_object_get_data( d, "move_rb" );
char * location = gtk_file_chooser_get_filename( chooser );
const gboolean do_move = gtk_toggle_button_get_active( move_tb );
/* pop up a dialog saying that the work is in progress */
w = gtk_message_dialog_new( GTK_WINDOW( dialog ),
GTK_DIALOG_DESTROY_WITH_PARENT | GTK_DIALOG_MODAL,
GTK_MESSAGE_INFO,
GTK_BUTTONS_CLOSE,
_( "Moving \"%s\"" ),
tr_torrentInfo(tor)->name );
gtk_message_dialog_format_secondary_text( GTK_MESSAGE_DIALOG( w ), _( "This may take a moment..." ) );
gtk_dialog_set_response_sensitive( GTK_DIALOG( w ), GTK_RESPONSE_CLOSE, FALSE );
gtk_widget_show( w );
/* start the move and periodically check its status */
updateData = g_new( struct UpdateData, 1 );
updateData->dialog = dialog;
updateData->done = FALSE;
tr_torrentSetLocation( tor, location, do_move, &updateData->done );
gtr_timeout_add_seconds( 1, onTimer, updateData );
/* remember this location so that it can be the default next time */
g_free( previousLocation );
previousLocation = location;
}
else
{
gtk_widget_destroy( GTK_WIDGET( dialog ) );
}
}
GtkWidget*
gtr_relocate_dialog_new( GtkWindow * parent, tr_torrent * tor )
{
int row;
GtkWidget * w;
GtkWidget * d;
GtkWidget * t;
d = gtk_dialog_new_with_buttons( _( "Set Torrent Location" ), parent,
GTK_DIALOG_DESTROY_WITH_PARENT |
GTK_DIALOG_MODAL |
GTK_DIALOG_NO_SEPARATOR,
GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
GTK_STOCK_APPLY, GTK_RESPONSE_APPLY,
NULL );
g_object_set_data( G_OBJECT( d ), "torrent", tor );
gtk_dialog_set_default_response( GTK_DIALOG( d ),
GTK_RESPONSE_CANCEL );
gtk_dialog_set_alternative_button_order( GTK_DIALOG( d ),
GTK_RESPONSE_APPLY,
GTK_RESPONSE_CANCEL,
-1 );
g_signal_connect( d, "response", G_CALLBACK( onResponse ), NULL );
row = 0;
t = hig_workarea_create( );
hig_workarea_add_section_title( t, &row, _( "Location" ) );
if( previousLocation == NULL )
previousLocation = g_strdup( pref_string_get( TR_PREFS_KEY_DOWNLOAD_DIR ) );
w = gtk_file_chooser_button_new( _( "Set Torrent Location" ), GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER );
gtk_file_chooser_set_filename( GTK_FILE_CHOOSER( w ), previousLocation );
g_object_set_data( G_OBJECT( d ), "chooser", w );
hig_workarea_add_row( t, &row, _( "Torrent _location:" ), w, NULL );
w = gtk_radio_button_new_with_mnemonic( NULL, _( "_Move from the current folder" ) );
g_object_set_data( G_OBJECT( d ), "move_rb", w );
hig_workarea_add_wide_control( t, &row, w );
w = gtk_radio_button_new_with_mnemonic_from_widget( GTK_RADIO_BUTTON( w ), _( "Local data is _already there" ) );
hig_workarea_add_wide_control( t, &row, w );
hig_workarea_finish( t, &row );
gtk_widget_show_all( t );
gtk_box_pack_start( GTK_BOX( GTK_DIALOG( d )->vbox ), t, TRUE, TRUE, 0 );
return d;
}

21
gtk/relocate.h Normal file
View File

@ -0,0 +1,21 @@
/*
* This file Copyright (C) 2009 Charles Kerr <charles@transmissionbt.com>
*
* This file is licensed by the GPL version 2. Works owned by the
* Transmission project are granted a special exemption to clause 2(b)
* so that the bulk of its code can remain under the MIT license.
* This exemption does not extend to derived works not owned by
* the Transmission project.
*
* $Id:$
*/
#ifndef GTR_RELOCATE_H
#define GTR_RELOCATE_H
#include <gtk/gtk.h>
#include <libtransmission/transmission.h>
GtkWidget * gtr_relocate_dialog_new( GtkWindow * parent, tr_torrent * tor );
#endif

View File

@ -50,7 +50,7 @@ getProgressString( const tr_torrent * tor,
char buf1[32], buf2[32], buf3[32], buf4[32];
char * str;
double seedRatio;
gboolean hasSeedRatio;
gboolean hasSeedRatio = FALSE;
if( !isDone )
{

View File

@ -5,8 +5,9 @@ static const char * fallback_ui_file =
" <menuitem action='add-torrent-menu'/>\n"
" <menuitem action='new-torrent'/>\n"
" <separator/>\n"
" <menuitem action='show-torrent-properties'/>\n"
" <menuitem action='open-torrent-folder'/>\n"
" <menuitem action='show-torrent-properties'/>\n"
" <menuitem action='relocate-torrent'/>\n"
" <separator/>\n"
" <menuitem action='start-torrent'/>\n"
" <menuitem action='update-tracker'/>\n"
@ -64,8 +65,9 @@ static const char * fallback_ui_file =
" </toolbar>\n"
"\n"
" <popup name='main-window-popup'>\n"
" <menuitem action='show-torrent-properties'/>\n"
" <menuitem action='open-torrent-folder'/>\n"
" <menuitem action='show-torrent-properties'/>\n"
" <menuitem action='relocate-torrent'/>\n"
" <separator/>\n"
" <menuitem action='start-torrent'/>\n"
" <menuitem action='pause-torrent'/>\n"

View File

@ -99,7 +99,7 @@ void gtr_widget_set_tooltip_text( GtkWidget * w, const char * tip );
GtkWidget * gtr_button_new_from_stock( const char * stock,
const char * mnemonic );
guint gtr_timeout_add_seconds( guint interval,
guint gtr_timeout_add_seconds( guint seconds,
GSourceFunc function,
gpointer data );

View File

@ -201,6 +201,25 @@ preallocateFileFull( const char * filename, uint64_t length )
return success;
}
tr_bool
tr_preallocate_file( const char * filename, uint64_t length )
{
return preallocateFileFull( filename, length );
}
int
tr_open_file_for_writing( const char * filename )
{
int flags = O_WRONLY | O_CREAT;
#ifdef O_BINARY
flags |= O_BINARY;
#endif
#ifdef O_LARGEFILE
flags |= O_LARGEFILE;
#endif
return open( filename, flags, 0666 );
}
int
tr_open_file_for_scanning( const char * filename )
{

View File

@ -34,8 +34,12 @@ void tr_fdInit( size_t openFileLimit,
int tr_open_file_for_scanning( const char * filename );
int tr_open_file_for_writing( const char * filename );
void tr_close_file( int fd );
tr_bool tr_preallocate_file( const char * filename, uint64_t length );
int64_t tr_lseek( int fd, int64_t offset, int whence );

View File

@ -764,6 +764,42 @@ torrentSet( tr_session * session,
return errmsg;
}
static const char*
torrentSetLocation( tr_session * session,
tr_benc * args_in,
tr_benc * args_out UNUSED,
struct tr_rpc_idle_data * idle_data )
{
const char * errmsg = NULL;
const char * location = NULL;
assert( idle_data == NULL );
if( !tr_bencDictFindStr( args_in, "location", &location ) )
{
errmsg = "no location";
}
else
{
tr_bool move = FALSE;
int i, torrentCount;
tr_torrent ** torrents = getTorrents( session, args_in, &torrentCount );
tr_bencDictFindBool( args_in, "move", &move );
for( i=0; i<torrentCount; ++i )
{
tr_torrent * tor = torrents[i];
tr_torrentSetLocation( tor, location, move, NULL );
notify( session, TR_RPC_TORRENT_CHANGED, tor );
}
tr_free( torrents );
}
return errmsg;
}
/***
****
***/
@ -1201,7 +1237,7 @@ sessionGet( tr_session * s,
tr_bencDictAddInt ( d, TR_PREFS_KEY_PEER_PORT, tr_sessionGetPeerPort( s ) );
tr_bencDictAddInt ( d, TR_PREFS_KEY_PEER_PORT_RANDOM_ON_START, tr_sessionGetPeerPortRandomOnStart( s ) );
tr_bencDictAddBool( d, TR_PREFS_KEY_PORT_FORWARDING, tr_sessionIsPortForwardingEnabled( s ) );
tr_bencDictAddInt ( d, "rpc-version", 5 );
tr_bencDictAddInt ( d, "rpc-version", 6 );
tr_bencDictAddInt ( d, "rpc-version-minimum", 1 );
tr_bencDictAddReal( d, "seedRatioLimit", tr_sessionGetRatioLimit( s ) );
tr_bencDictAddBool( d, "seedRatioLimited", tr_sessionIsRatioLimited( s ) );
@ -1234,19 +1270,20 @@ static struct method
}
methods[] =
{
{ "port-test", FALSE, portTest },
{ "blocklist-update", FALSE, blocklistUpdate },
{ "session-get", TRUE, sessionGet },
{ "session-set", TRUE, sessionSet },
{ "session-stats", TRUE, sessionStats },
{ "torrent-add", FALSE, torrentAdd },
{ "torrent-get", TRUE, torrentGet },
{ "torrent-remove", TRUE, torrentRemove },
{ "torrent-set", TRUE, torrentSet },
{ "torrent-start", TRUE, torrentStart },
{ "torrent-stop", TRUE, torrentStop },
{ "torrent-verify", TRUE, torrentVerify },
{ "torrent-reannounce", TRUE, torrentReannounce }
{ "port-test", FALSE, portTest },
{ "blocklist-update", FALSE, blocklistUpdate },
{ "session-get", TRUE, sessionGet },
{ "session-set", TRUE, sessionSet },
{ "session-stats", TRUE, sessionStats },
{ "torrent-add", FALSE, torrentAdd },
{ "torrent-get", TRUE, torrentGet },
{ "torrent-remove", TRUE, torrentRemove },
{ "torrent-set", TRUE, torrentSet },
{ "torrent-set-location", TRUE, torrentSetLocation },
{ "torrent-start", TRUE, torrentStart },
{ "torrent-stop", TRUE, torrentStop },
{ "torrent-verify", TRUE, torrentVerify },
{ "torrent-reannounce", TRUE, torrentReannounce }
};
static void

View File

@ -972,7 +972,7 @@ tr_sessionGetActiveSpeedLimit( const tr_session * session, tr_direction dir, int
static void
updateBandwidth( tr_session * session, tr_direction dir )
{
int limit;
int limit = 0;
const tr_bool isLimited = tr_sessionGetActiveSpeedLimit( session, dir, &limit );
const tr_bool zeroCase = isLimited && !limit;

View File

@ -171,9 +171,9 @@ tr_ctorSetFilePriorities( tr_ctor * ctor,
tr_file_index_t * mycount;
switch( priority ) {
case TR_PRI_NORMAL: myfiles = &ctor->normal; mycount = &ctor->normalSize; break;
case TR_PRI_LOW: myfiles = &ctor->low; mycount = &ctor->lowSize; break;
case TR_PRI_HIGH: myfiles = &ctor->high; mycount = &ctor->highSize; break;
default /*TR_PRI_NORMAL*/: myfiles = &ctor->normal; mycount = &ctor->normalSize; break;
}
tr_free( *myfiles );

View File

@ -239,7 +239,7 @@ tr_torrentGetSeedRatio( const tr_torrent * tor, double * ratio )
*ratio = tr_sessionGetRatioLimit( tor->session );
break;
case TR_RATIOLIMIT_UNLIMITED:
default: /* TR_RATIOLIMIT_UNLIMITED */
isLimited = FALSE;
break;
}
@ -734,8 +734,7 @@ tr_torrentNew( const tr_ctor * ctor,
**/
void
tr_torrentSetDownloadDir( tr_torrent * tor,
const char * path )
tr_torrentSetDownloadDir( tr_torrent * tor, const char * path )
{
assert( tr_isTorrent( tor ) );
@ -1235,8 +1234,7 @@ checkAndStartCB( tr_torrent * tor )
}
static void
torrentStart( tr_torrent * tor,
int reloadProgress )
torrentStart( tr_torrent * tor, int reloadProgress )
{
assert( tr_isTorrent( tor ) );
@ -1272,6 +1270,12 @@ torrentRecheckDoneImpl( void * vtor )
assert( tr_isTorrent( tor ) );
tr_torrentRecheckCompleteness( tor );
if( tor->startAfterVerify )
{
tor->startAfterVerify = FALSE;
tr_torrentStart( tor );
}
}
static void
@ -1286,11 +1290,18 @@ void
tr_torrentVerify( tr_torrent * tor )
{
assert( tr_isTorrent( tor ) );
tr_verifyRemove( tor );
tr_globalLock( tor->session );
/* if the torrent's already being verified, stop it */
tr_verifyRemove( tor );
/* if the torrent's running, stop it & set the restart-after-verify flag */
if( tor->isRunning ) {
tr_torrentStop( tor );
tor->startAfterVerify = TRUE;
}
/* add the torrent to the recheck queue */
tr_torrentUncheck( tor );
tr_verifyAdd( tor, torrentRecheckDoneCB );
@ -1316,7 +1327,6 @@ tr_torrentCloseLocalFiles( const tr_torrent * tor )
evbuffer_free( buf );
}
static void
stopTorrent( void * vtor )
{
@ -2198,6 +2208,105 @@ tr_torrentDeleteLocalData( tr_torrent * tor, tr_fileFunc fileFunc )
****
***/
struct LocationData
{
tr_bool move_from_old_location;
tr_bool * setme_done;
char * location;
tr_torrent * tor;
};
static void
setLocation( void * vdata )
{
struct LocationData * data = vdata;
tr_torrent * tor = data->tor;
const tr_bool do_move = data->move_from_old_location;
const char * location = data->location;
assert( tr_isTorrent( tor ) );
if( strcmp( location, tor->downloadDir ) )
{
tr_file_index_t i;
/* bad idea to move files while they're being verified... */
tr_verifyRemove( tor );
/* if the torrent is running, stop it and set a flag to
* restart after we're done */
if( tor->isRunning )
{
tr_torrentStop( tor );
tor->startAfterVerify = TRUE;
}
/* try to move the files.
* FIXME: there are still all kinds of nasty cases, like what
* if the target directory runs out of space halfway through... */
for( i=0; i<tor->info.fileCount; ++i )
{
struct stat sb;
char * oldpath = tr_buildPath( tor->downloadDir, tor->info.files[i].name, NULL );
char * newpath = tr_buildPath( location, tor->info.files[i].name, NULL );
if( do_move && !stat( oldpath, &sb ) )
{
tr_moveFile( oldpath, newpath );
tr_torinf( tor, "moving \"%s\" to \"%s\"", oldpath, newpath );
}
else if( !stat( newpath, &sb ) )
{
tr_torinf( tor, "found \"%s\"", newpath );
}
tr_free( newpath );
tr_free( oldpath );
}
/* blow away the leftover subdirectories in the old location */
tr_torrentDeleteLocalData( tor, unlink );
/* set the new location and reverify */
tr_torrentSetDownloadDir( tor, location );
tr_torrentVerify( tor );
}
if( data->setme_done )
*data->setme_done = TRUE;
/* cleanup */
tr_free( data->location );
tr_free( data );
}
void
tr_torrentSetLocation( tr_torrent * tor,
const char * location,
tr_bool move_from_old_location,
tr_bool * setme_done )
{
struct LocationData * data;
assert( tr_isTorrent( tor ) );
if( setme_done )
*setme_done = FALSE;
/* run this in the libtransmission thread */
data = tr_new( struct LocationData, 1 );
data->tor = tor;
data->location = tr_strdup( location );
data->move_from_old_location = move_from_old_location;
data->setme_done = setme_done;
tr_runInEventThread( tor->session, setLocation, data );
}
/***
****
***/
void
tr_torrentCheckSeedRatio( tr_torrent * tor )
{

View File

@ -197,6 +197,7 @@ struct tr_torrent
tr_bool isRunning;
tr_bool isDeleting;
tr_bool needsSeedRatioCheck;
tr_bool startAfterVerify;
uint16_t maxConnectedPeers;

View File

@ -935,6 +935,12 @@ void tr_torrentStop( tr_torrent * torrent );
typedef int tr_fileFunc( const char * filename );
/** @brief Tell transmsision where to find this torrent's local data */
void tr_torrentSetLocation( tr_torrent * torrent,
const char * location,
tr_bool move_from_previous_location,
tr_bool * setme_done );
/**
* @brief Deletes the torrent's local data.
* @param torrent
@ -1054,8 +1060,10 @@ void tr_torrentSetFileDLs( tr_torrent * torrent,
const tr_info * tr_torrentInfo( const tr_torrent * torrent );
void tr_torrentSetDownloadDir( tr_torrent * torrent,
const char * path );
/* Raw function to change the torrent's downloadDir field.
This should only be used by libtransmission or to bootstrap
a newly-instantiated tr_torrent object. */
void tr_torrentSetDownloadDir( tr_torrent * torrent, const char * path );
const char * tr_torrentGetDownloadDir( const tr_torrent * torrent );

View File

@ -1566,3 +1566,72 @@ tr_strratio( char * buf, size_t buflen, double ratio, const char * infinity )
tr_snprintf( buf, buflen, "%'.0f", ratio );
return buf;
}
/***
****
***/
int
tr_moveFile( const char * oldpath, const char * newpath )
{
int in;
int out;
char * buf;
struct stat st;
size_t bytesLeft;
size_t buflen;
/* make sure the old file exists */
if( stat( oldpath, &st ) ) {
const int err = errno;
errno = err;
return -1;
}
if( !S_ISREG( st.st_mode ) ) {
errno = ENOENT;
return -1;
}
bytesLeft = st.st_size;
/* make sure the target directory exists */
{
char * newdir = tr_dirname( newpath );
int i = tr_mkdirp( newdir, 0777 );
tr_free( newdir );
if( i )
return i;
}
/* they might be on the same filesystem... */
if( !rename( oldpath, newpath ) )
return 0;
/* copy the file */
in = tr_open_file_for_scanning( oldpath );
tr_preallocate_file( newpath, bytesLeft );
out = tr_open_file_for_writing( newpath );
buflen = stat( newpath, &st ) ? 4096 : st.st_blksize;
buf = tr_new( char, buflen );
while( bytesLeft > 0 )
{
ssize_t bytesWritten;
const size_t bytesThisPass = MIN( bytesLeft, buflen );
const int numRead = read( in, buf, bytesThisPass );
if( numRead < 0 )
break;
bytesWritten = write( out, buf, numRead );
if( bytesWritten < 0 )
break;
bytesLeft -= bytesWritten;
}
/* cleanup */
tr_free( buf );
tr_close_file( out );
tr_close_file( in );
if( bytesLeft != 0 )
return -1;
unlink( oldpath );
return 0;
}

View File

@ -469,6 +469,9 @@ char* tr_strratio( char * buf, size_t buflen, double ratio, const char * infinit
struct tm * tr_localtime_r( const time_t *_clock, struct tm *_result );
/** on success, return 0. on failure, return -1 and set errno */
int tr_moveFile( const char * oldpath, const char * newpath );
#ifdef __cplusplus
}

View File

@ -35,6 +35,7 @@
#include "options.h"
#include "prefs.h"
#include "prefs-dialog.h"
#include "relocate.h"
#include "session.h"
#include "session-dialog.h"
#include "speed.h"
@ -157,15 +158,20 @@ TrMainWindow :: TrMainWindow( Session& session, Prefs& prefs, TorrentModel& mode
connect( ui.action_About, SIGNAL(triggered()), myAboutDialog, SLOT(show()));
connect( ui.action_Contents, SIGNAL(triggered()), this, SLOT(openHelp()));
connect( ui.action_OpenFolder, SIGNAL(triggered()), this, SLOT(openFolder()));
connect( ui.action_SetLocation, SIGNAL(triggered()), this, SLOT(setLocation()));
connect( ui.action_Properties, SIGNAL(triggered()), this, SLOT(openProperties()));
connect( ui.action_SessionDialog, SIGNAL(triggered()), mySessionDialog, SLOT(show()));
connect( ui.listView, SIGNAL(activated(const QModelIndex&)), ui.action_Properties, SLOT(trigger()));
QAction * sep2 = new QAction( this );
sep2->setSeparator( true );
// context menu
QList<QAction*> actions;
actions << ui.action_Properties
<< ui.action_OpenFolder
<< sep
actions << ui.action_OpenFolder
<< ui.action_Properties
<< ui.action_SetLocation
<< sep2
<< ui.action_Start
<< ui.action_Pause
<< ui.action_Verify
@ -613,6 +619,14 @@ TrMainWindow :: openProperties( )
myDetailsDialog->show( );
}
void
TrMainWindow :: setLocation( )
{
const int torrentId( *getSelectedTorrents().begin() );
QDialog * d = new RelocateDialog( mySession, torrentId, this );
d->show( );
}
void
TrMainWindow :: openFolder( )
{
@ -733,6 +747,7 @@ TrMainWindow :: refreshActionSensitivity( )
const bool oneSelection( selected == 1 );
ui.action_OpenFolder->setEnabled( oneSelection && mySession.isLocal( ) );
ui.action_SetLocation->setEnabled( oneSelection );
ui.action_SelectAll->setEnabled( selected < rowCount );
ui.action_StartAll->setEnabled( paused > 0 );

View File

@ -113,6 +113,7 @@ class TrMainWindow: public QMainWindow
void addTorrents( const QStringList& filenames );
void openHelp( );
void openFolder( );
void setLocation( );
void openProperties( );
void toggleSpeedMode( );
void dataReadProgress( );

View File

@ -51,7 +51,7 @@
<x>0</x>
<y>0</y>
<width>792</width>
<height>23</height>
<height>25</height>
</rect>
</property>
<property name="sizePolicy">
@ -68,8 +68,9 @@
<addaction name="action_Add"/>
<addaction name="action_New"/>
<addaction name="separator"/>
<addaction name="action_Properties"/>
<addaction name="action_OpenFolder"/>
<addaction name="action_Properties"/>
<addaction name="action_SetLocation"/>
<addaction name="separator"/>
<addaction name="action_Start"/>
<addaction name="action_Announce"/>
@ -531,6 +532,11 @@
<string extracomment="Start a local session or connect to a running session">Choose Session</string>
</property>
</action>
<action name="action_SetLocation">
<property name="text">
<string>Set &amp;Location...</string>
</property>
</action>
</widget>
<resources>
<include location="application.qrc"/>

View File

@ -24,10 +24,10 @@ FORMS += mainwin.ui
RESOURCES += application.qrc
SOURCES += about.cc app.cc details.cc file-tree.cc filters.cc hig.cc \
license.cc mainwin.cc make-dialog.cc options.cc prefs.cc \
prefs-dialog.cc qticonloader.cc session.cc session-dialog.cc \
squeezelabel.cc stats-dialog.cc torrent.cc torrent-delegate.cc \
torrent-delegate-min.cc torrent-filter.cc torrent-model.cc \
triconpushbutton.cc utils.cc watchdir.cc
prefs-dialog.cc qticonloader.cc relocate.cc session.cc \
session-dialog.cc squeezelabel.cc stats-dialog.cc torrent.cc \
torrent-delegate.cc torrent-delegate-min.cc torrent-filter.cc \
torrent-model.cc triconpushbutton.cc utils.cc watchdir.cc
HEADERS += $$replace(SOURCES, .cc, .h)
HEADERS += speed.h types.h

95
qt/relocate.cc Normal file
View File

@ -0,0 +1,95 @@
/*
* This file Copyright (C) 2009 Charles Kerr <charles@transmissionbt.com>
*
* This file is licensed by the GPL version 2. Works owned by the
* Transmission project are granted a special exemption to clause 2(b)
* so that the bulk of its code can remain under the MIT license.
* This exemption does not extend to derived works not owned by
* the Transmission project.
*
* $Id:$
*/
#include <QLabel>
#include <QPushButton>
#include <QVBoxLayout>
#include <QRadioButton>
#include <QDir>
#include <QFileDialog>
#include <QSet>
#include <QDialogButtonBox>
#include <QWidget>
#include "hig.h"
#include "relocate.h"
#include "session.h"
QString RelocateDialog :: myPath;
bool RelocateDialog :: myMoveFlag = true;
void
RelocateDialog :: onSetLocation( )
{
QSet<int> ids;
ids << myTorrentId;
mySession.torrentSetLocation( ids, myPath, myMoveFlag );
deleteLater( );
}
void
RelocateDialog :: onFileSelected( const QString& path )
{
myPath = path;
myDirButton->setText( myPath );
}
void
RelocateDialog :: onDirButtonClicked( )
{
QFileDialog * d = new QFileDialog( this );
d->setFileMode( QFileDialog::Directory );
d->selectFile( myPath );
d->show( );
connect( d, SIGNAL(fileSelected(const QString&)), this, SLOT(onFileSelected(const QString&)));
}
void
RelocateDialog :: onMoveToggled( bool b )
{
myMoveFlag = b;
}
RelocateDialog :: RelocateDialog( Session& session, int torrentId, QWidget * parent ):
QDialog( parent ),
mySession( session ),
myTorrentId( torrentId )
{
QRadioButton * find_rb;
setWindowTitle( tr( "Set Torrent Location" ) );
if( myPath.isEmpty( ) )
myPath = QDir::homePath( );
HIG * hig = new HIG( );
hig->addSectionTitle( tr( "Location" ) );
hig->addRow( tr( "Torrent &location:" ), myDirButton = new QPushButton( myPath ) );
hig->addWideControl( myMoveRadio = new QRadioButton( tr( "&Move from the current folder" ), this ) );
hig->addWideControl( find_rb = new QRadioButton( tr( "Local data is &already there" ), this ) );
hig->finish( );
if( myMoveFlag )
myMoveRadio->setChecked( true );
else
find_rb->setChecked( true );
connect( myMoveRadio, SIGNAL(toggled(bool)), this, SLOT(onMoveToggled(bool)));
connect( myDirButton, SIGNAL(clicked(bool)), this, SLOT(onDirButtonClicked()));
QLayout * layout = new QVBoxLayout( this );
layout->addWidget( hig );
QDialogButtonBox * buttons = new QDialogButtonBox( QDialogButtonBox::Ok|QDialogButtonBox::Cancel );
connect( buttons, SIGNAL(rejected()), this, SLOT(deleteLater()));
connect( buttons, SIGNAL(accepted()), this, SLOT(onSetLocation()));
layout->addWidget( buttons );
}

48
qt/relocate.h Normal file
View File

@ -0,0 +1,48 @@
/*
* This file Copyright (C) 2009 Charles Kerr <charles@transmissionbt.com>
*
* This file is licensed by the GPL version 2. Works owned by the
* Transmission project are granted a special exemption to clause 2(b)
* so that the bulk of its code can remain under the MIT license.
* This exemption does not extend to derived works not owned by
* the Transmission project.
*
* $Id:$
*/
#ifndef RELOCATE_DIALOG_H
#define RELOCATE_DIALOG_H
#include <QDialog>
#include <QString>
class QPushButton;
class QRadioButton;
class Session;
class RelocateDialog: public QDialog
{
Q_OBJECT
private:
static QString myPath;
static bool myMoveFlag;
private:
Session & mySession;
const int myTorrentId;
QPushButton * myDirButton;
QRadioButton * myMoveRadio;
private slots:
void onFileSelected( const QString& path );
void onDirButtonClicked( );
void onSetLocation( );
void onMoveToggled( bool );
public:
RelocateDialog( Session&, int torrentId, QWidget * parent = 0 );
~RelocateDialog( ) { }
};
#endif

View File

@ -375,6 +375,19 @@ Session :: torrentSet( const QSet<int>& ids, const QString& key, const QList<int
tr_bencFree( &top );
}
void
Session :: torrentSetLocation( const QSet<int>& ids, const QString& location, bool doMove )
{
tr_benc top;
tr_bencInitDict( &top, 2 );
tr_bencDictAddStr( &top, "method", "torrent-set-location" );
tr_benc * args( tr_bencDictAddDict( &top, "arguments", 3 ) );
addOptionalIds( args, ids );
tr_bencDictAddStr( args, "location", location.toUtf8().constData() );
tr_bencDictAddBool( args, "move", doMove );
exec( &top );
tr_bencFree( &top );
}
void
Session :: refreshTorrents( const QSet<int>& ids )
@ -536,15 +549,15 @@ Session :: exec( const char * request )
}
else if( !myUrl.isEmpty( ) )
{
const QByteArray data( request, strlen( request ) );
static const QString path( "/transmission/rpc" );
QHttpRequestHeader header( "POST", path );
header.setValue( "User-Agent", QCoreApplication::instance()->applicationName() + "/" + LONG_VERSION_STRING );
header.setValue( "Content-Type", "application/json; charset=UTF-8" );
if( !mySessionId.isEmpty( ) )
header.setValue( TR_RPC_SESSION_ID_HEADER, mySessionId );
myHttp.setProperty( "current-request", data );
myHttp.request( header, data, &myBuffer );
QBuffer * reqbuf = new QBuffer;
reqbuf->setData( QByteArray( request ) );
myHttp.request( header, reqbuf, &myBuffer );
#ifdef DEBUG_HTTP
std::cerr << "sending " << qPrintable(header.toString()) << "\nBody:\n" << request << std::endl;
#endif
@ -563,6 +576,7 @@ void
Session :: onRequestFinished( int id, bool error )
{
Q_UNUSED( id );
QIODevice * sourceDevice = myHttp.currentSourceDevice( );
QHttpResponseHeader response = myHttp.lastResponse();
@ -579,7 +593,7 @@ Session :: onRequestFinished( int id, bool error )
// we got a 409 telling us our session id has expired.
// update it and resubmit the request.
mySessionId = response.value( TR_RPC_SESSION_ID_HEADER );
exec( myHttp.property("current-request").toByteArray().constData() );
exec( qobject_cast<QBuffer*>(sourceDevice)->buffer().constData() );
}
else if( error )
{

View File

@ -91,6 +91,7 @@ class Session: public QObject
void torrentSet( const QSet<int>& ids, const QString& key, int val );
void torrentSet( const QSet<int>& ids, const QString& key, double val );
void torrentSet( const QSet<int>& ids, const QString& key, const QList<int>& val );
void torrentSetLocation( const QSet<int>& ids, const QString& path, bool doMove );
public slots: