From 1ddde29916184c22e76f28a89eec637613d5c694 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Lalinsk=C3=BD?= Date: Mon, 25 Sep 2006 13:16:47 +0200 Subject: [PATCH] File renaming. --- picard/tagger.py | 63 ++++++++++++++++++++++++++++++++++++----- picard/util/__init__.py | 40 ++++++++++++++++++++++++++ test/test_utils.py | 18 ++++++++++++ 3 files changed, 114 insertions(+), 7 deletions(-) diff --git a/picard/tagger.py b/picard/tagger.py index 637363ad9..3fdb9c45c 100644 --- a/picard/tagger.py +++ b/picard/tagger.py @@ -43,6 +43,7 @@ from picard.worker import WorkerThread from picard.util import LockableDict, strip_non_alnum, encode_filename, decode_filename from picard.util.cachedws import CachedWebService from picard.thread import ThreadAssist +from picard import util from musicbrainz2.utils import extractUuid from musicbrainz2.webservice import Query, TrackFilter @@ -325,21 +326,69 @@ class Tagger(QtGui.QApplication, ComponentManager, Component): self.thread_assist.proxy_to_main(self.__set_status_bar_message, (N_("Saving file %s ..."), file.filename)) + failed = False try: file.save() except: import traceback; traceback.print_exc() - unsaved.append(file) - else: - saved.append(file) + failed = True + + if not failed: + file.lock_for_read() + try: + filename = file.filename + metadata = Metadata() + metadata.copy(file.metadata) + finally: + file.unlock() + + if self.config.setting["move_files"]: + new_dirname = os.path.dirname(filename) + else: + new_dirname = os.path.dirname(filename) + + if self.config.setting["rename_files"]: + format = self.config.setting["file_naming_format"] + # Replace incompatible characters + for name in metadata.keys(): + value = metadata[name] + if isinstance(value, unicode): + value = util.sanitize_filename(value) + if sys.platform == "win32" or \ + self.config.setting["windows_compatible_filenames"]: + value = util.replace_win32_incompat(value) + if self.config.setting["ascii_filenames"]: + value = util.replace_non_ascii(value) + metadata[name] = value + # Make the new filename + new_filename = self.tagger.evaluate_script(format, metadata) + if not self.config.setting["move_files"]: + new_filename = os.path.basename(new_filename) + new_filename = os.path.join(new_dirname, + util.make_short_filename( + new_dirname, + new_filename)) + \ + os.path.splitext(filename)[1] + else: + new_filename = os.path.join(new_dirname, + os.path.basename(filename)) + + if filename != new_filename: + os.rename(filename, new_filename) + file.lock_for_write() + try: + file.filename = new_filename + finally: + file.unlock() + + self.thread_assist.proxy_to_main(self.__save_finished, + (file, failed)) self.thread_assist.proxy_to_main(self.__set_status_bar_message, (N_("Done"),)) - self.thread_assist.proxy_to_main(self.__save_finished, - (saved, unsaved)) - def __save_finished(self, saved, unsaved): + def __save_finished(self, file, failed): """Finalize file saving and notify views.""" - for file in saved: + if not failed: file.state = File.SAVED file.orig_metadata.copy(file.metadata) file.metadata.changed = False diff --git a/picard/util/__init__.py b/picard/util/__init__.py index c40c57ce9..139ace60d 100644 --- a/picard/util/__init__.py +++ b/picard/util/__init__.py @@ -135,3 +135,43 @@ def strip_non_alnum(string): """Remove all non-alphanumeric characters from ``string``.""" return _re_non_alphanum.sub(" ", string) +_re_slashes = re.compile(r'[\\/]', re.UNICODE) +def sanitize_filename(string, repl="_"): + return _re_slashes.sub(repl, string) + +def make_short_filename(prefix, filename, length=250, max_length=250, + mid_length=32, min_length=2): + parts = _re_slashes.split(filename) + parts.reverse() + left = len(prefix) + len(filename) + 1 - length + + for i in range(len(parts)): + left -= max(0, len(parts[i]) - max_length) + parts[i] = parts[i][:max_length] + + if left > 0: + for i in range(len(parts)): + length = len(parts[i]) - mid_length + if length > 0: + length = min(left, length) + parts[i] = parts[i][:-length] + left -= length + if left <= 0: + break + + if left > 0: + for i in range(len(parts)): + length = len(parts[i]) - min_length + if length > 0: + length = min(left, length) + parts[i] = parts[i][:-length] + left -= length + if left <= 0: + break + + if left > 0: + raise IOError, "File name is too long." + + parts.reverse() + return os.path.join(*parts) + diff --git a/test/test_utils.py b/test/test_utils.py index 9b7f3ce67..ded89c94f 100644 --- a/test/test_utils.py +++ b/test/test_utils.py @@ -1,5 +1,6 @@ # -*- coding: utf-8 -*- +import os.path import unittest from picard import util @@ -55,3 +56,20 @@ class SanitizeDateTest(unittest.TestCase): self.failIfEqual(util.sanitize_date("2006--02"), "2006-02") self.failIfEqual(util.sanitize_date("2006.03.02"), "2006-03-02") +class ShortFilenameTest(unittest.TestCase): + + def test_short(self): + fn = util.make_short_filename("/home/me/", os.path.join("a1234567890", "b1234567890"), 255) + self.failUnlessEqual(fn, os.path.join("a1234567890", "b1234567890")) + + def test_long(self): + fn = util.make_short_filename("/home/me/", os.path.join("a1234567890", "b1234567890"), 20) + self.failUnlessEqual(fn, os.path.join("a123456", "b1")) + + def test_long_2(self): + fn = util.make_short_filename("/home/me/", os.path.join("a1234567890", "b1234567890"), 22) + self.failUnlessEqual(fn, os.path.join("a12345678", "b1")) + + def test_too_long(self): + self.failUnlessRaises(IOError, util.make_short_filename, "/home/me/", os.path.join("a1234567890", "b1234567890"), 10) +