Explorar o código

Better auto-renaming

Up until now aria2 file auto renaming worked by just appending a new
unique number to the file path, behind the file name extension, if any,
changing what most other programs consider the file extension in the
process.
Now, aria2 will attempt to insert the number before the file extension,
leaving the extension intact, so that e.g. a ".jpg" still is a ".jpg"
file and opens in your configured image viewer when clicking it.
If a file has no extension (incl. so called "dot files"), the number
will be appended to the file name as usual.

Note: This is a potentially breaking change that might break third party
scripts that rely on aria2 auto file renaming producing a certain format
for renamed files. Please fix your scripts accordingly.

Closes GH-709
Nils Maier %!s(int64=9) %!d(string=hai) anos
pai
achega
665a1d1b35
Modificáronse 4 ficheiros con 78 adicións e 4 borrados
  1. 2 1
      doc/manual-src/en/aria2c.rst
  2. 21 1
      src/RequestGroup.cc
  3. 2 2
      src/RequestGroup.h
  4. 53 0
      test/RequestGroupTest.cc

+ 2 - 1
doc/manual-src/en/aria2c.rst

@@ -1175,7 +1175,8 @@ Advanced Options
 
   Rename file name if the same file already exists.
   This option works only in HTTP(S)/FTP download.
-  The new file name has a dot and a number(1..9999) appended.
+  The new file name has a dot and a number(1..9999) appended after the
+  name, but before the file extension, if any.
   Default: ``true``
 
 .. option:: --auto-save-interval=<SEC>

+ 21 - 1
src/RequestGroup.cc

@@ -778,8 +778,28 @@ void RequestGroup::tryAutoFileRenaming()
         fmt("File renaming failed: %s", getFirstFilePath().c_str()),
         error_code::FILE_RENAMING_FAILED);
   }
+  auto fn = filepath;
+  std::string ext;
+  const auto idx = fn.find_last_of(".");
+  const auto slash = fn.find_last_of("\\/");
+  // Do extract the extension, as in "file.ext" = "file" and ".ext",
+  // but do not consider ".file" to be a file name without extension instead
+  // of a blank file name and an extension of ".file"
+  if (idx != std::string::npos &&
+      // fn has no path component and starts with a dot, but has no extension
+      // otherwise
+      idx != 0 &&
+      // has a file path component if we found a slash.
+      // if slash == idx - 1 this means a form of "*/.*", so the file name
+      // starts with a dot, has no extension otherwise, and therefore do not
+      // extract an extension either
+      (slash == std::string::npos || slash < idx - 1)
+      ) {
+    ext = fn.substr(idx);
+    fn = fn.substr(0, idx);
+  }
   for (int i = 1; i < 10000; ++i) {
-    auto newfilename = fmt("%s.%d", filepath.c_str(), i);
+    auto newfilename = fmt("%s.%d%s", fn.c_str(), i, ext.c_str());
     File newfile(newfilename);
     File ctrlfile(newfile.getPath() + DefaultBtProgressInfoFile::getSuffix());
     if (!newfile.exists() || (newfile.exists() && ctrlfile.exists())) {

+ 2 - 2
src/RequestGroup.h

@@ -199,8 +199,6 @@ private:
 
   void initializePostDownloadHandler();
 
-  void tryAutoFileRenaming();
-
   // Returns the result code of this RequestGroup.  If the download
   // finished, then returns error_code::FINISHED.  If the
   // download didn't finish and error result is available in
@@ -219,6 +217,8 @@ public:
 
   bool isCheckIntegrityReady();
 
+  void tryAutoFileRenaming();
+
   const std::shared_ptr<SegmentMan>& getSegmentMan() const
   {
     return segmentMan_;

+ 53 - 0
test/RequestGroupTest.cc

@@ -14,6 +14,7 @@ class RequestGroupTest : public CppUnit::TestFixture {
 
   CPPUNIT_TEST_SUITE(RequestGroupTest);
   CPPUNIT_TEST(testGetFirstFilePath);
+  CPPUNIT_TEST(testTryAutoFileRenaming);
   CPPUNIT_TEST(testCreateDownloadResult);
   CPPUNIT_TEST_SUITE_END();
 
@@ -24,6 +25,7 @@ public:
   void setUp() { option_.reset(new Option()); }
 
   void testGetFirstFilePath();
+  void testTryAutoFileRenaming();
   void testCreateDownloadResult();
 };
 
@@ -44,6 +46,57 @@ void RequestGroupTest::testGetFirstFilePath()
   CPPUNIT_ASSERT_EQUAL(std::string("[MEMORY]myfile"), group.getFirstFilePath());
 }
 
+void RequestGroupTest::testTryAutoFileRenaming()
+{
+  std::shared_ptr<DownloadContext> ctx(
+      new DownloadContext(1_k, 1_k, "/tmp/myfile"));
+
+  RequestGroup group(GroupId::create(), option_);
+  group.setDownloadContext(ctx);
+
+  option_->put(PREF_AUTO_FILE_RENAMING, "false");
+  try {
+    group.tryAutoFileRenaming();
+  }
+  catch (const Exception& ex) {
+    CPPUNIT_ASSERT_EQUAL(error_code::FILE_ALREADY_EXISTS, ex.getErrorCode());
+
+  }
+
+  option_->put(PREF_AUTO_FILE_RENAMING, "true");
+  group.tryAutoFileRenaming();
+  CPPUNIT_ASSERT_EQUAL(std::string("/tmp/myfile.1"), group.getFirstFilePath());
+
+  ctx->getFirstFileEntry()->setPath("/tmp/myfile.txt");
+  group.tryAutoFileRenaming();
+  CPPUNIT_ASSERT_EQUAL(std::string("/tmp/myfile.1.txt"), group.getFirstFilePath());
+
+  ctx->getFirstFileEntry()->setPath("/tmp.txt/myfile");
+  group.tryAutoFileRenaming();
+  CPPUNIT_ASSERT_EQUAL(std::string("/tmp.txt/myfile.1"), group.getFirstFilePath());
+
+  ctx->getFirstFileEntry()->setPath("/tmp.txt/myfile.txt");
+  group.tryAutoFileRenaming();
+  CPPUNIT_ASSERT_EQUAL(std::string("/tmp.txt/myfile.1.txt"), group.getFirstFilePath());
+
+  ctx->getFirstFileEntry()->setPath(".bashrc");
+  group.tryAutoFileRenaming();
+  CPPUNIT_ASSERT_EQUAL(std::string(".bashrc.1"), group.getFirstFilePath());
+
+  ctx->getFirstFileEntry()->setPath(".bashrc.txt");
+  group.tryAutoFileRenaming();
+  CPPUNIT_ASSERT_EQUAL(std::string(".bashrc.1.txt"), group.getFirstFilePath());
+
+  ctx->getFirstFileEntry()->setPath("/tmp.txt/.bashrc");
+  group.tryAutoFileRenaming();
+  CPPUNIT_ASSERT_EQUAL(std::string("/tmp.txt/.bashrc.1"), group.getFirstFilePath());
+
+  ctx->getFirstFileEntry()->setPath("/tmp.txt/.bashrc.txt");
+  group.tryAutoFileRenaming();
+  CPPUNIT_ASSERT_EQUAL(std::string("/tmp.txt/.bashrc.1.txt"), group.getFirstFilePath());
+
+}
+
 void RequestGroupTest::testCreateDownloadResult()
 {
   std::shared_ptr<DownloadContext> ctx(