]> granicus.if.org Git - transmission/commitdiff
(trunk) #1220 'change top folder names' -- add file-renaming to the Qt client
authorJordan Lee <jordan@transmissionbt.com>
Sun, 20 Jan 2013 01:31:58 +0000 (01:31 +0000)
committerJordan Lee <jordan@transmissionbt.com>
Sun, 20 Jan 2013 01:31:58 +0000 (01:31 +0000)
extras/rpc-spec.txt
libtransmission/rpcimpl.c
libtransmission/torrent.c
qt/details.cc
qt/details.h
qt/file-tree.cc
qt/file-tree.h
qt/session.cc
qt/session.h
qt/torrent.cc
qt/torrent.h

index 5da5bc3cd5d6676bf431c632547dd4670e16182b..d4d88e86a1dcf6d2e060438d5052c637fc574cf5 100644 (file)
    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
index 1d49784b81cdd82357a83d4b188867cc89c7942a..02fe04ee2c6695efaf942dede1380669a8d16267 100644 (file)
@@ -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);
     }
 }
 
index 9006bf3f10e2dee7544a69a5f9ebac0460f3e85b..19edcef1d65d3407d5303a93e61182afc1e08dc3 100644 (file)
@@ -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);
index ad42b8de8ae7f0c2c2cb4e12a18279045f3449e8..7d843b58b10be301094abb979b1fba6ff8352e18 100644 (file)
@@ -202,8 +202,10 @@ Details :: setIds( const QSet<int>& 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<int>& 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<int>&, bool)),
              this,           SLOT(  onFileWantedChanged(const QSet<int>&, 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<int>& indices, bool wanted)
   mySession.torrentSet (myIds, key, indices.toList());
   getNewData ();
 }
+
+void
+Details :: onPathEdited (const QString& oldpath, const QString& newname)
+{
+  mySession.torrentRenamePath (myIds, oldpath, newname);
+}
index 1a8aeb4eefda36b64f40fd5c6b92e8250782eccc..20d0da60e1dea70a3e72e8bdad31e771e52bac8a 100644 (file)
@@ -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<int>& fileIndices, int );
         void onFileWantedChanged( const QSet<int>& fileIndices, bool );
+        void onPathEdited (const QString& oldpath, const QString& newname);
         void onHonorsSessionLimitsToggled( bool );
         void onDownloadLimitedToggled( bool );
         void onSpinBoxEditingFinished( );
index dc22b040ecf0050cadccd0202ffa07f1d487be45..b48312e0e419d41d34a827bfd74053e6454f1b20 100644 (file)
@@ -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<FileTreeItem*>(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<int>&, bool)),
              this,     SIGNAL(wantedChanged(const QSet<int>&, bool)));
+
+    connect( &myModel, SIGNAL(pathEdited(const QString&, const QString&)),
+             this,     SIGNAL(pathEdited(const QString&, const QString&)));
 }
 
 FileTreeView :: ~FileTreeView( )
index fbf3478f8b69cba2c9276e276774e3920657b920..5f0f875011ddb46bf34dfd15ae94d0e6733c6ba3 100644 (file)
@@ -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<int>& fileIndices, int );
         void wantedChanged( const QSet<int>& 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<int>& fileIndices, int );
         void wantedChanged( const QSet<int>& fileIndices, bool );
+        void pathEdited (const QString& oldpath, const QString& newname);
 
     protected:
         bool eventFilter( QObject *, QEvent * );
index 1ab0f526fc7e5bbcd44526933bd2627d82181014..21a14cbe15ac021232e632b8d5b19d7bbdcaeacc 100644 (file)
@@ -27,6 +27,7 @@
 #include <QStringList>
 #include <QStyle>
 #include <QTextStream>
+#include <QTimer>
 
 #include <curl/curl.h>
 
@@ -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<int>& ids, const QString& location, bo
   tr_variantFree (&top);
 }
 
+void
+Session :: torrentRenamePath (const QSet<int>& 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<int>& 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<Session*>(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("<p><b>Unable to rename \"%1\" as \"%2\": %3.</b></p> <p>Please correct the errors and try again.</p>").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: {
index e1dba485baba3e90808c22ad6639de7bdf80fca9..15bb3c7ec4fa523ffd423457dd54e3c36f48508f 100644 (file)
@@ -19,6 +19,8 @@
 #include <QFileInfoList>
 #include <QNetworkAccessManager>
 #include <QString>
+#include <QStringList>
+#include <QTimer>
 #include <QUrl>
 
 class QStringList;
@@ -101,7 +103,7 @@ class Session: public QObject
         void torrentSet( const QSet<int>& ids, const tr_quark key, const QStringList& val );
         void torrentSet( const QSet<int>& ids, const tr_quark key, const QPair<int,QString>& val);
         void torrentSetLocation( const QSet<int>& ids, const QString& path, bool doMove );
-
+        void torrentRenamePath( const QSet<int>& ids, const QString& oldpath, const QString& newname );
 
     public slots:
         void pauseTorrents( const QSet<int>& torrentIds = QSet<int>() );
@@ -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
index 703f8932ab1e20635341c890d774297556bc028d..2b9b1120aa58006a2a8e694c618093f382ec1b65 100644 (file)
@@ -573,6 +573,7 @@ Torrent :: update (tr_variant * d)
 
       updateMimeIcon ();
       changed = true;
+      emit torrentFileListRebuilt (id ());
     }
 
   if (tr_variantDictFindList (d, TR_KEY_fileStats, &files))
index e94d327f79e1fd6359359404a354d83f41bf746f..7261d61a7755e3f279fca642de32c139869099aa 100644 (file)
@@ -188,6 +188,7 @@ class Torrent: public QObject
     signals:
         void torrentChanged( int id );
         void torrentCompleted( int id );
+        void torrentFileListRebuilt( int id );
 
     private: