From: Jordan Lee Date: Sun, 20 Jan 2013 01:31:58 +0000 (+0000) Subject: (trunk) #1220 'change top folder names' -- add file-renaming to the Qt client X-Git-Tag: 2.80~260 X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=dd955b121e12f1c6832ee2e7c1c6a75c608eaadd;p=transmission (trunk) #1220 'change top folder names' -- add file-renaming to the Qt client --- diff --git a/extras/rpc-spec.txt b/extras/rpc-spec.txt index 5da5bc3cd..d4d88e86a 100644 --- a/extras/rpc-spec.txt +++ b/extras/rpc-spec.txt @@ -429,11 +429,11 @@ 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) + | (must only be 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 + Response arguments: "path", "name", and "id", holding the torrent ID integer 4. Session Requests diff --git a/libtransmission/rpcimpl.c b/libtransmission/rpcimpl.c index 1d49784b8..02fe04ee2 100644 --- a/libtransmission/rpcimpl.c +++ b/libtransmission/rpcimpl.c @@ -353,59 +353,6 @@ torrentRemove (tr_session * session, return NULL; } -static void -torrentRenamePathDone (tr_torrent * tor UNUSED, - const char * oldpath UNUSED, - const char * newname UNUSED, - int error, - void * user_data) -{ - *(int*)user_data = error; -} - -static const char * -torrentRenamePath (tr_session * session, - tr_variant * args_in, - tr_variant * args_out UNUSED, - struct tr_rpc_idle_data * idle_data UNUSED) -{ - const char * oldpath; - const char * newname; - const char * ret = NULL; - - if (!tr_variantDictFindStr (args_in, TR_KEY_path, &oldpath, NULL)) - { - ret = "no path specified"; - } - else if (!tr_variantDictFindStr (args_in, TR_KEY_name, &newname, NULL)) - { - ret = "no name specified"; - } - else - { - int torrentCount; - tr_torrent ** torrents = getTorrents (session, args_in, &torrentCount); - - if (torrentCount != 1) - { - ret = "torent-rename-path requires 1 torrent"; - } - else - { - int error = -1; - tr_torrentRenamePath (torrents[0], oldpath, newname, torrentRenamePathDone, &error); - assert (error != -1); - - if (error != 0) - ret = tr_strerror (error); - } - - tr_free (torrents); - } - - return ret; -} - static const char* torrentReannounce (tr_session * session, tr_variant * args_in, @@ -478,8 +425,7 @@ addFileStats (const tr_torrent * tor, tr_variant * list) } static void -addFiles (const tr_torrent * tor, - tr_variant * list) +addFiles (const tr_torrent * tor, tr_variant * list) { tr_file_index_t i; tr_file_index_t n; @@ -1365,6 +1311,57 @@ torrentSetLocation (tr_session * session, **** ***/ +static void +torrentRenamePathDone (tr_torrent * tor, + const char * oldpath, + const char * newname, + int error, + void * user_data) +{ + const char * result; + struct tr_rpc_idle_data * data = user_data; + + tr_variantDictAddInt (data->args_out, TR_KEY_id, tr_torrentId(tor)); + tr_variantDictAddStr (data->args_out, TR_KEY_path, oldpath); + tr_variantDictAddStr (data->args_out, TR_KEY_name, newname); + + if (error == 0) + result = NULL; + else + result = tr_strerror (error); + + tr_idle_function_done (data, result); +} + +static const char* +torrentRenamePath (tr_session * session, + tr_variant * args_in, + tr_variant * args_out UNUSED, + struct tr_rpc_idle_data * idle_data) +{ + int torrentCount; + tr_torrent ** torrents; + const char * oldpath = NULL; + const char * newname = NULL; + + tr_variantDictFindStr (args_in, TR_KEY_path, &oldpath, NULL); + tr_variantDictFindStr (args_in, TR_KEY_name, &newname, NULL); + torrents = getTorrents (session, args_in, &torrentCount); + + if (torrentCount == 1) + tr_torrentRenamePath (torrents[0], oldpath, newname, torrentRenamePathDone, idle_data); + else + tr_idle_function_done (idle_data, "torrent-rename-path requires 1 torrent"); + + /* cleanup */ + tr_free (torrents); + return NULL; /* ignored */ +} + +/*** +**** +***/ + static void portTested (tr_session * session UNUSED, bool did_connect UNUSED, @@ -1394,8 +1391,8 @@ portTested (tr_session * session UNUSED, static const char* portTest (tr_session * session, - tr_variant * args_in UNUSED, - tr_variant * args_out UNUSED, + tr_variant * args_in UNUSED, + tr_variant * args_out UNUSED, struct tr_rpc_idle_data * idle_data) { const int port = tr_sessionGetPeerPort (session); @@ -1612,8 +1609,8 @@ fileListFromList (tr_variant * list, tr_file_index_t * setmeCount) static const char* torrentAdd (tr_session * session, - tr_variant * args_in, - tr_variant * args_out UNUSED, + tr_variant * args_in, + tr_variant * args_out UNUSED, struct tr_rpc_idle_data * idle_data) { const char * filename = NULL; @@ -1986,7 +1983,7 @@ methods[] = { "torrent-add", false, torrentAdd }, { "torrent-get", true, torrentGet }, { "torrent-remove", true, torrentRemove }, - { "torrent-rename-path", true, torrentRenamePath }, + { "torrent-rename-path", false, torrentRenamePath }, { "torrent-set", true, torrentSet }, { "torrent-set-location", true, torrentSetLocation }, { "torrent-start", true, torrentStart }, @@ -2069,7 +2066,7 @@ request_exec (tr_session * session, tr_variantDictAddInt (&response, TR_KEY_tag, tag); buf = tr_variantToBuf (&response, TR_VARIANT_FMT_JSON_LEAN); - (*callback)(session, buf, callback_user_data); + (*callback)(session, buf, callback_user_data); evbuffer_free (buf); tr_variantFree (&response); @@ -2086,7 +2083,7 @@ request_exec (tr_session * session, data->args_out = tr_variantDictAddDict (data->response, TR_KEY_arguments, 0); data->callback = callback; data->callback_user_data = callback_user_data; - (*methods[i].func)(session, args_in, data->args_out, data); + (*methods[i].func)(session, args_in, data->args_out, data); } } diff --git a/libtransmission/torrent.c b/libtransmission/torrent.c index 9006bf3f1..19edcef1d 100644 --- a/libtransmission/torrent.c +++ b/libtransmission/torrent.c @@ -3491,6 +3491,8 @@ torrentRenamePath (void * vdata) **** ***/ + tor->anyDate = tr_time (); + /* callback */ if (data->callback != NULL) (*data->callback)(tor, data->oldpath, data->newname, error, data->callback_user_data); diff --git a/qt/details.cc b/qt/details.cc index ad42b8de8..7d843b58b 100644 --- a/qt/details.cc +++ b/qt/details.cc @@ -202,8 +202,10 @@ Details :: setIds( const QSet& ids ) // stop listening to the old torrents foreach( int id, myIds ) { const Torrent * tor = myModel.getTorrentFromId( id ); - if( tor ) + if( tor ) { disconnect( tor, SIGNAL(torrentChanged(int)), this, SLOT(onTorrentChanged()) ); + disconnect( tor, SIGNAL(torrentFileListRebuilt(int)), this, SLOT(onTorrentFileListRebuilt()) ); + } } myFileTreeView->clear( ); @@ -213,8 +215,10 @@ Details :: setIds( const QSet& ids ) // listen to the new torrents foreach( int id, myIds ) { const Torrent * tor = myModel.getTorrentFromId( id ); - if( tor ) + if( tor ) { connect( tor, SIGNAL(torrentChanged(int)), this, SLOT(onTorrentChanged()) ); + connect( tor, SIGNAL(torrentFileListRebuilt(int)), this, SLOT(onTorrentFileListRebuilt()) ); + } } foreach( QWidget * w, myWidgets ) @@ -295,6 +299,13 @@ Details :: onTorrentChanged( ) } } +void +Details :: onTorrentFileListRebuilt( ) +{ + myFilesDirty = true; + onTorrentChanged( ); +} + namespace { void setIfIdle( QComboBox * box, int i ) @@ -829,10 +840,11 @@ Details :: refresh( ) } myPeers = peers2; + if( !single || myFilesDirty ) + myFileTreeView->clear( ); if( single ) myFileTreeView->update( torrents[0]->files( ) , myChangedTorrents ); - else - myFileTreeView->clear( ); + myFilesDirty = false; myChangedTorrents = false; myHavePendingRefresh = false; @@ -1307,6 +1319,9 @@ Details :: createFilesTab( ) connect( myFileTreeView, SIGNAL( wantedChanged(const QSet&, bool)), this, SLOT( onFileWantedChanged(const QSet&, bool))); + connect( myFileTreeView, SIGNAL( pathEdited(const QString&, const QString&)), + this, SLOT (onPathEdited(const QString&, const QString&))); + return myFileTreeView; } @@ -1341,3 +1356,9 @@ Details :: onFileWantedChanged (const QSet& indices, bool wanted) mySession.torrentSet (myIds, key, indices.toList()); getNewData (); } + +void +Details :: onPathEdited (const QString& oldpath, const QString& newname) +{ + mySession.torrentRenamePath (myIds, oldpath, newname); +} diff --git a/qt/details.h b/qt/details.h index 1a8aeb4ee..20d0da60e 100644 --- a/qt/details.h +++ b/qt/details.h @@ -49,6 +49,7 @@ class Details: public QDialog private slots: void onTorrentChanged( ); + void onTorrentFileListRebuilt( ); void onTimer( ); public: @@ -135,11 +136,14 @@ class Details: public QDialog FileTreeView * myFileTreeView; + bool myFilesDirty; + private slots: void refreshPref( int key ); void onBandwidthPriorityChanged( int ); void onFilePriorityChanged( const QSet& fileIndices, int ); void onFileWantedChanged( const QSet& fileIndices, bool ); + void onPathEdited (const QString& oldpath, const QString& newname); void onHonorsSessionLimitsToggled( bool ); void onDownloadLimitedToggled( bool ); void onSpinBoxEditingFinished( ); diff --git a/qt/file-tree.cc b/qt/file-tree.cc index dc22b040e..b48312e0e 100644 --- a/qt/file-tree.cc +++ b/qt/file-tree.cc @@ -318,12 +318,39 @@ FileTreeModel :: flags( const QModelIndex& index ) const { int i( Qt::ItemIsSelectable | Qt::ItemIsEnabled ); + if( index.column( ) == COL_NAME ) + i |= Qt::ItemIsEditable; + if( index.column( ) == COL_WANTED ) i |= Qt::ItemIsUserCheckable | Qt::ItemIsTristate; return (Qt::ItemFlags)i; } +bool +FileTreeModel :: setData (const QModelIndex& index, const QVariant& newname, int role) +{ + if (role == Qt::EditRole) + { + QString oldpath; + QModelIndex walk = index; + FileTreeItem * item = static_cast(index.internalPointer()); + + while (item && !item->name().isEmpty()) + { + if (oldpath.isEmpty()) + oldpath = item->name(); + else + oldpath = item->name() + "/" + oldpath; + item = item->parent (); + } + + emit pathEdited (oldpath, newname.toString()); + } + + return false; // don't update the view until the session confirms the change +} + QVariant FileTreeModel :: headerData( int column, Qt::Orientation orientation, int role ) const { @@ -681,6 +708,9 @@ FileTreeView :: FileTreeView( QWidget * parent ): connect( &myModel, SIGNAL(wantedChanged(const QSet&, bool)), this, SIGNAL(wantedChanged(const QSet&, bool))); + + connect( &myModel, SIGNAL(pathEdited(const QString&, const QString&)), + this, SIGNAL(pathEdited(const QString&, const QString&))); } FileTreeView :: ~FileTreeView( ) diff --git a/qt/file-tree.h b/qt/file-tree.h index fbf3478f8..5f0f87501 100644 --- a/qt/file-tree.h +++ b/qt/file-tree.h @@ -101,10 +101,12 @@ class FileTreeModel: public QAbstractItemModel QModelIndex parent( const QModelIndex& child, int column ) const; int rowCount( const QModelIndex& parent = QModelIndex( ) ) const; int columnCount( const QModelIndex &parent = QModelIndex( ) ) const; + virtual bool setData ( const QModelIndex & index, const QVariant & value, int role = Qt::EditRole ); signals: void priorityChanged( const QSet& fileIndices, int ); void wantedChanged( const QSet& fileIndices, bool ); + void pathEdited (const QString& oldpath, const QString& newname); public: void clear( ); @@ -154,6 +156,7 @@ class FileTreeView: public QTreeView signals: void priorityChanged( const QSet& fileIndices, int ); void wantedChanged( const QSet& fileIndices, bool ); + void pathEdited (const QString& oldpath, const QString& newname); protected: bool eventFilter( QObject *, QEvent * ); diff --git a/qt/session.cc b/qt/session.cc index 1ab0f526f..21a14cbe1 100644 --- a/qt/session.cc +++ b/qt/session.cc @@ -27,6 +27,7 @@ #include #include #include +#include #include @@ -60,6 +61,7 @@ namespace TAG_ADD_TORRENT, TAG_PORT_TEST, TAG_MAGNET_LINK, + TAG_RENAME_PATH, FIRST_UNIQUE_TAG }; @@ -246,7 +248,8 @@ Session :: Session( const char * configDir, Prefs& prefs ): myPrefs( prefs ), mySession( 0 ), myConfigDir( QString::fromUtf8( configDir ) ), - myNAM( 0 ) + myNAM( 0 ), + myResponseTimer (this) { myStats.ratio = TR_RATIO_NA; myStats.uploadedBytes = 0; @@ -257,6 +260,9 @@ Session :: Session( const char * configDir, Prefs& prefs ): myCumulativeStats = myStats; connect( &myPrefs, SIGNAL(changed(int)), this, SLOT(updatePref(int)) ); + + connect (&myResponseTimer, SIGNAL(timeout()), this, SLOT(onResponseTimer())); + myResponseTimer.setSingleShot (true); } Session :: ~Session( ) @@ -494,6 +500,21 @@ Session :: torrentSetLocation( const QSet& ids, const QString& location, bo tr_variantFree (&top); } +void +Session :: torrentRenamePath (const QSet& ids, const QString& oldpath, const QString& newname) +{ + tr_variant top; + tr_variantInitDict (&top, 2); + tr_variantDictAddStr (&top, TR_KEY_method, "torrent-rename-path"); + tr_variantDictAddInt (&top, TR_KEY_tag, TAG_RENAME_PATH); + tr_variant * args (tr_variantDictAddDict(&top, TR_KEY_arguments, 3)); + addOptionalIds (args, ids); + tr_variantDictAddStr (args, TR_KEY_path, oldpath.toUtf8().constData()); + tr_variantDictAddStr (args, TR_KEY_name, newname.toUtf8().constData()); + exec (&top); + tr_variantFree (&top); +} + void Session :: refreshTorrents( const QSet& ids ) { @@ -635,11 +656,16 @@ Session :: exec( const tr_variant * request ) } void -Session :: localSessionCallback( tr_session * session, struct evbuffer * json, void * self ) +Session :: localSessionCallback( tr_session * session, struct evbuffer * json, void * vself ) { - Q_UNUSED( session ); + Q_UNUSED (session); + + Session * self = static_cast(vself); - ((Session*)self)->parseResponse( (const char*) evbuffer_pullup( json, -1 ), evbuffer_get_length( json ) ); + self->myIdleJSON.append (QString ((const char*) evbuffer_pullup (json, -1))); + + if (!self->myResponseTimer.isActive()) + self->myResponseTimer.start(50); } #define REQUEST_DATA_PROPERTY_KEY "requestData" @@ -715,6 +741,19 @@ Session :: onFinished( QNetworkReply * reply ) reply->deleteLater(); } +void +Session :: onResponseTimer () +{ + QStringList responses = myIdleJSON; + myIdleJSON.clear(); + + foreach (QString response, responses) + { + const QByteArray utf8 (response.toUtf8()); + parseResponse (utf8.constData(), utf8.length()); + } +} + void Session :: parseResponse( const char * json, size_t jsonLength ) { @@ -767,11 +806,43 @@ Session :: parseResponse( const char * json, size_t jsonLength ) break; } + case TAG_RENAME_PATH: + { + int64_t id = 0; + const char * result = 0; + if( tr_variantDictFindStr (&top, TR_KEY_result, &result, 0) && strcmp (result, "success") ) + { + const char * path = ""; + const char * name = ""; + tr_variantDictFindStr (args, TR_KEY_path, &path, 0); + tr_variantDictFindStr (args, TR_KEY_name, &name, 0); + const QString title = tr("Error Renaming Path"); + const QString text = tr("

Unable to rename \"%1\" as \"%2\": %3.

Please correct the errors and try again.

").arg(path).arg(name).arg(result); + QMessageBox * d = new QMessageBox( QMessageBox::Information, title, text, + QMessageBox::Close, + QApplication::activeWindow()); + connect( d, SIGNAL(rejected()), d, SLOT(deleteLater()) ); + d->show( ); + } + else if (tr_variantDictFindInt (args, TR_KEY_id, &id) && id) + { + // let's get the updated file list + char * req = tr_strdup_printf ("{ \"arguments\": { \"fields\": [ \"files\", \"id\" ], \"ids\": %d }, \"method\": \"torrent-get\", \"tag\": %d }", + int(id), + int(TAG_SOME_TORRENTS)); + exec (req); + tr_free (req); + } + + break; + } + case TAG_PORT_TEST: { bool isOpen = 0; if( tr_variantDictFindDict( &top, TR_KEY_arguments, &args ) ) tr_variantDictFindBool( args, TR_KEY_port_is_open, &isOpen ); emit portTested( (bool)isOpen ); + break; } case TAG_MAGNET_LINK: { diff --git a/qt/session.h b/qt/session.h index e1dba485b..15bb3c7ec 100644 --- a/qt/session.h +++ b/qt/session.h @@ -19,6 +19,8 @@ #include #include #include +#include +#include #include class QStringList; @@ -101,7 +103,7 @@ class Session: public QObject void torrentSet( const QSet& ids, const tr_quark key, const QStringList& val ); void torrentSet( const QSet& ids, const tr_quark key, const QPair& val); void torrentSetLocation( const QSet& ids, const QString& path, bool doMove ); - + void torrentRenamePath( const QSet& ids, const QString& oldpath, const QString& newname ); public slots: void pauseTorrents( const QSet& torrentIds = QSet() ); @@ -129,6 +131,7 @@ class Session: public QObject private slots: void onFinished( QNetworkReply * reply ); + void onResponseTimer( ); signals: void executed( int64_t tag, const QString& result, struct tr_variant * arguments ); @@ -150,11 +153,13 @@ class Session: public QObject tr_session * mySession; QString myConfigDir; QString mySessionId; + QStringList myIdleJSON; QUrl myUrl; QNetworkAccessManager * myNAM; struct tr_session_stats myStats; struct tr_session_stats myCumulativeStats; QString mySessionVersion; + QTimer myResponseTimer; }; #endif diff --git a/qt/torrent.cc b/qt/torrent.cc index 703f8932a..2b9b1120a 100644 --- a/qt/torrent.cc +++ b/qt/torrent.cc @@ -573,6 +573,7 @@ Torrent :: update (tr_variant * d) updateMimeIcon (); changed = true; + emit torrentFileListRebuilt (id ()); } if (tr_variantDictFindList (d, TR_KEY_fileStats, &files)) diff --git a/qt/torrent.h b/qt/torrent.h index e94d327f7..7261d61a7 100644 --- a/qt/torrent.h +++ b/qt/torrent.h @@ -188,6 +188,7 @@ class Torrent: public QObject signals: void torrentChanged( int id ); void torrentCompleted( int id ); + void torrentFileListRebuilt( int id ); private: