Merge lp:~openshift/picard/file_system into lp:~musicbrainz-developers/picard/trunk

Proposed by Carlin Mangar
Status: Needs review
Proposed branch: lp:~openshift/picard/file_system
Merge into: lp:~musicbrainz-developers/picard/trunk
Diff against target: 764 lines (+390/-175)
6 files modified
picard/file.py (+63/-14)
picard/ui/options/moving.py (+12/-3)
picard/ui/ui_options_moving.py (+57/-22)
picard/util/__init__.py (+68/-39)
test/test_utils.py (+19/-17)
ui/options_moving.ui (+171/-80)
To merge this branch: bzr merge lp:~openshift/picard/file_system
Reviewer Review Type Date Requested Status
MusicBrainz Developers Pending
Review via email: mp+16070@code.launchpad.net
To post a comment you must log in.

Unmerged revisions

1011. By Carlin Mangar

Merge with main.

1010. By Carlin Mangar

Code streamline.

1009. By Carlin Mangar

Undid a change in priority.

1008. By Carlin Mangar

Recompiled ui file and line ending to unix.

1007. By Carlin Mangar

Merge with main

1006. By Carlin Mangar

After some extensive revision. This is the new file copying algorithm. It does multiple checks to file copying will not fail, and if it fails, it does not leave orphans lying around, aka duplicate files. make_short_filename's behavior has been fine tuned to work in most instances.

1005. By Carlin Mangar

Fixes to file saving.

1004. By Carlin Mangar

Fix make_short_filename

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'picard/file.py'
2--- picard/file.py 2009-10-19 06:23:50 +0000
3+++ picard/file.py 2009-12-12 02:52:17 +0000
4@@ -3,6 +3,7 @@
5 # Picard, the next-generation MusicBrainz tagger
6 # Copyright (C) 2004 Robert Kaye
7 # Copyright (C) 2006 Lukáš Lalinský
8+# Copyright (C) 2009 Carlin Mangar
9 #
10 # This program is free software; you can redistribute it and/or
11 # modify it under the terms of the GNU General Public License
12@@ -24,6 +25,7 @@
13 import sys
14 import re
15 import traceback
16+import fnmatch
17 from PyQt4 import QtCore
18 from picard.metadata import Metadata
19 from picard.ui.item import Item
20@@ -43,6 +45,8 @@
21 LockableObject,
22 pathcmp,
23 mimetype,
24+ copy_adv,
25+ move_adv,
26 )
27
28
29@@ -134,19 +138,33 @@
30 def save(self, next, thread_pool, settings):
31 metadata = Metadata()
32 metadata.copy(self.metadata)
33- self.tagger.load_queue.put((
34- partial(self._save_and_rename, self.filename, metadata, settings),
35- partial(self._saving_finished, next),
36- QtCore.Qt.LowEventPriority + 2))
37+ if self.filename in self.tagger.load_queue.queue:
38+ self.tagger.load_queue.put((
39+ partial(self._save_and_rename, self.filename, metadata, settings),
40+ partial(self._saving_finished, next),
41+ QtCore.Qt.LowEventPriority + 2))
42+ elif self.filename in self.tagger.analyze_queue.queue:
43+ self.tagger.analyze_queue.put((
44+ partial(self._save_and_rename, self.filename, metadata, settings),
45+ partial(self._saving_finished, next),
46+ QtCore.Qt.LowEventPriority + 2))
47+ else:
48+ self.tagger.save_queue.put((
49+ partial(self._save_and_rename, self.filename, metadata, settings),
50+ partial(self._saving_finished, next),
51+ QtCore.Qt.LowEventPriority + 2))
52
53 def _save_and_rename(self, old_filename, metadata, settings):
54 """Save the metadata."""
55 new_filename = old_filename
56- if not settings["dont_write_tags"]:
57- self._save(old_filename, metadata, settings)
58 # Rename files
59 if settings["rename_files"] or settings["move_files"]:
60 new_filename = self._rename(old_filename, metadata, settings)
61+ else:
62+ new_filename = old_filename
63+ # Write tags
64+ if not settings["dont_write_tags"]:
65+ self._save(new_filename, metadata, settings)
66 # Move extra files (images, playlists, etc.)
67 if settings["move_files"] and settings["move_additional_files"]:
68 self._move_additional_files(old_filename, new_filename,
69@@ -251,9 +269,9 @@
70 os.path.exists(encode_filename(new_filename + ext))):
71 new_filename = "%s (%d)" % (tmp_filename, i)
72 i += 1
73- new_filename = new_filename + ext
74+ new_filename = new_filename + ext
75 self.log.debug("Moving file %r => %r", old_filename, new_filename)
76- shutil.move(encode_filename(old_filename), encode_filename(new_filename))
77+ move_adv(encode_filename(old_filename), encode_filename(new_filename),settings["keep_copy"])
78 return new_filename
79 else:
80 return old_filename
81@@ -288,29 +306,60 @@
82 image_filename = "%s (%d)" % (filename, i)
83 i += 1
84 else:
85+ try:
86+ os.unlink(image_filename + ext)
87+ except:
88+ pass
89 self.log.debug("Saving cover images to %r", image_filename)
90 f = open(image_filename + ext, "wb")
91 f.write(data)
92 f.close()
93
94+ def _get_supported(self):
95+ from picard.formats import supported_formats
96+ filters = []
97+ for exts, name in supported_formats():
98+ filters.extend("*" + e for e in exts)
99+ return filters
100+
101 def _move_additional_files(self, old_filename, new_filename, settings):
102 """Move extra files, like playlists..."""
103 old_path = encode_filename(os.path.dirname(old_filename))
104 new_path = encode_filename(os.path.dirname(new_filename))
105+ (gone, ext) = os.path.splitext(old_filename)
106 patterns = encode_filename(settings["move_additional_files_pattern"])
107 patterns = filter(bool, [p.strip() for p in patterns.split()])
108+ ignore_patterns = filter(bool, self._get_supported())
109 files = []
110+ # 'glob1' is a helper function that non-recursively glob inside a literal directory.
111+ # It returns a list of basenames. 'glob1' accepts a pattern.
112+ # Current version ensures no music files are dragged along until they get tagged
113 for pattern in patterns:
114- # FIXME glob1 is not documented, maybe we need our own implemention?
115+ ignore_file_list = []
116+ for ignore_pattern in ignore_patterns:
117+ for ignore_file in glob.glob1(old_path, ignore_pattern):
118+ ignore_file_list.append(ignore_file)
119 for old_file in glob.glob1(old_path, pattern):
120- new_file = os.path.join(new_path, old_file)
121- old_file = os.path.join(old_path, old_file)
122- # FIXME we shouldn't do this from a thread!
123+ if (old_file in ignore_file_list):
124+ continue
125 if self.tagger.get_file_by_filename(decode_filename(old_file)):
126 self.log.debug("File loaded in the tagger, not moving %r", old_file)
127 continue
128- self.log.debug("Moving %r to %r", old_file, new_file)
129- shutil.move(old_file, new_file)
130+ filenameonly, ext = os.path.splitext(old_file)
131+ new_file = os.path.normpath(os.path.join(new_path,make_short_filename(new_path, filenameonly)+ext))
132+ old_file = os.path.normpath(os.path.join(old_path, old_file))
133+ if encode_filename(old_file) != encode_filename(new_file):
134+ self.log.debug("Moving %r to %r", old_file, new_file)
135+ #TODO: make keep additional files separate
136+ try:
137+ move_adv(encode_filename(old_file), encode_filename(new_file) ,settings["keep_copy"])
138+ except:
139+ # Required when some filenames have a tendency to be locked by the system
140+ if fnmatch.fnmatch(old_file,"*Album*") or fnmatch.fnmatch(old_file,"*.ini") \
141+ or fnmatch.fnmatch(old_file,"*Folder*"):
142+ pass
143+ else:
144+ raise
145
146 def remove(self, from_parent=True):
147 if from_parent and self.parent:
148
149=== modified file 'picard/ui/options/moving.py'
150--- picard/ui/options/moving.py 2009-01-04 19:08:29 +0000
151+++ picard/ui/options/moving.py 2009-12-12 02:52:17 +0000
152@@ -25,7 +25,7 @@
153 from picard.script import ScriptParser
154 from picard.ui.options import OptionsPage, OptionsCheckError, register_options_page
155 from picard.ui.ui_options_moving import Ui_MovingOptionsPage
156-from picard.util import decode_filename
157+from picard.util import decode_filename, make_short_filename
158
159 class MovingOptionsPage(OptionsPage):
160
161@@ -37,6 +37,7 @@
162
163 options = [
164 BoolOption("setting", "move_files", False),
165+ BoolOption("setting", "keep_copy", False),
166 TextOption("setting", "move_files_to", ""),
167 BoolOption("setting", "move_additional_files", False),
168 TextOption("setting", "move_additional_files_pattern", "*.jpg *.png"),
169@@ -58,11 +59,18 @@
170 self.ui.move_additional_files_pattern.setText(self.config.setting["move_additional_files_pattern"])
171 self.update_move_additional_files()
172 self.ui.delete_empty_dirs.setChecked(self.config.setting["delete_empty_dirs"])
173+ self.ui.keep_copy.setChecked(self.config.setting["keep_copy"])
174
175
176 def check(self):
177- if self.ui.move_files.isChecked() and not unicode(self.ui.move_files_to.text()).strip():
178- raise OptionsCheckError(_("Error"), _("The location to move files to must not be empty."))
179+ if self.ui.move_files.isChecked():
180+ move_to_path = unicode(self.ui.move_files_to.text()).strip()
181+ if not move_to_path:
182+ raise OptionsCheckError(_("Error"), _("The location to move files to must not be empty."))
183+ try:
184+ make_short_filename(move_to_path, "ULTRALONG/AVERAGE FILENAME TEST - 01 - YEAH YEAH YEAH.mpLONG")
185+ except IOError, e:
186+ raise OptionsCheckError(_("Error"), _("The location to move files to is too long, please choose a shorter path."))
187
188 def save(self):
189 self.config.setting["move_files"] = self.ui.move_files.isChecked()
190@@ -70,6 +78,7 @@
191 self.config.setting["move_additional_files"] = self.ui.move_additional_files.isChecked()
192 self.config.setting["move_additional_files_pattern"] = unicode(self.ui.move_additional_files_pattern.text())
193 self.config.setting["delete_empty_dirs"] = self.ui.delete_empty_dirs.isChecked()
194+ self.config.setting["keep_copy"] = self.ui.keep_copy.isChecked()
195 self.tagger.window.enable_moving_action.setChecked(self.config.setting["move_files"])
196
197 def move_files_to_browse(self):
198
199=== modified file 'picard/ui/ui_options_moving.py'
200--- picard/ui/ui_options_moving.py 2009-01-30 20:46:55 +0000
201+++ picard/ui/ui_options_moving.py 2009-12-12 02:52:17 +0000
202@@ -1,9 +1,9 @@
203 # -*- coding: utf-8 -*-
204
205-# Form implementation generated from reading ui file 'ui/options_moving.ui'
206+# Form implementation generated from reading ui file 'ui\options_moving.ui'
207 #
208-# Created: Fri Jan 30 21:45:34 2009
209-# by: PyQt4 UI code generator 4.4.3
210+# Created: Thu Oct 29 05:20:03 2009
211+# by: PyQt4 UI code generator 4.6.1
212 #
213 # WARNING! All changes made in this file will be lost!
214
215@@ -12,31 +12,64 @@
216 class Ui_MovingOptionsPage(object):
217 def setupUi(self, MovingOptionsPage):
218 MovingOptionsPage.setObjectName("MovingOptionsPage")
219- MovingOptionsPage.resize(504, 563)
220+ MovingOptionsPage.resize(500, 333)
221+ sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Expanding)
222+ sizePolicy.setHorizontalStretch(0)
223+ sizePolicy.setVerticalStretch(0)
224+ sizePolicy.setHeightForWidth(MovingOptionsPage.sizePolicy().hasHeightForWidth())
225+ MovingOptionsPage.setSizePolicy(sizePolicy)
226 self.gridlayout = QtGui.QGridLayout(MovingOptionsPage)
227 self.gridlayout.setObjectName("gridlayout")
228- spacerItem = QtGui.QSpacerItem(378, 16, QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Expanding)
229- self.gridlayout.addItem(spacerItem, 8, 0, 1, 1)
230- self.move_additional_files_pattern = QtGui.QLineEdit(MovingOptionsPage)
231+ spacerItem = QtGui.QSpacerItem(20, 40, QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Expanding)
232+ self.gridlayout.addItem(spacerItem, 15, 0, 1, 1)
233+ self.MoveFiles = QtGui.QGroupBox(MovingOptionsPage)
234+ self.MoveFiles.setEnabled(True)
235+ sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Preferred)
236+ sizePolicy.setHorizontalStretch(0)
237+ sizePolicy.setVerticalStretch(0)
238+ sizePolicy.setHeightForWidth(self.MoveFiles.sizePolicy().hasHeightForWidth())
239+ self.MoveFiles.setSizePolicy(sizePolicy)
240+ self.MoveFiles.setMinimumSize(QtCore.QSize(100, 120))
241+ self.MoveFiles.setObjectName("MoveFiles")
242+ self.delete_empty_dirs = QtGui.QCheckBox(self.MoveFiles)
243+ self.delete_empty_dirs.setGeometry(QtCore.QRect(10, 40, 486, 18))
244+ self.delete_empty_dirs.setObjectName("delete_empty_dirs")
245+ self.keep_copy = QtGui.QCheckBox(self.MoveFiles)
246+ self.keep_copy.setGeometry(QtCore.QRect(10, 20, 486, 18))
247+ self.keep_copy.setObjectName("keep_copy")
248+ self.move_additional_files_pattern = QtGui.QLineEdit(self.MoveFiles)
249+ self.move_additional_files_pattern.setEnabled(True)
250+ self.move_additional_files_pattern.setGeometry(QtCore.QRect(30, 80, 400, 20))
251+ sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Preferred, QtGui.QSizePolicy.Maximum)
252+ sizePolicy.setHorizontalStretch(0)
253+ sizePolicy.setVerticalStretch(0)
254+ sizePolicy.setHeightForWidth(self.move_additional_files_pattern.sizePolicy().hasHeightForWidth())
255+ self.move_additional_files_pattern.setSizePolicy(sizePolicy)
256+ self.move_additional_files_pattern.setFrame(True)
257+ self.move_additional_files_pattern.setAlignment(QtCore.Qt.AlignLeading|QtCore.Qt.AlignLeft|QtCore.Qt.AlignVCenter)
258 self.move_additional_files_pattern.setObjectName("move_additional_files_pattern")
259- self.gridlayout.addWidget(self.move_additional_files_pattern, 5, 0, 1, 1)
260- self.move_additional_files = QtGui.QCheckBox(MovingOptionsPage)
261+ self.move_additional_files = QtGui.QCheckBox(self.MoveFiles)
262+ self.move_additional_files.setGeometry(QtCore.QRect(10, 60, 486, 18))
263 self.move_additional_files.setObjectName("move_additional_files")
264- self.gridlayout.addWidget(self.move_additional_files, 4, 0, 1, 1)
265- self.hboxlayout = QtGui.QHBoxLayout()
266- self.hboxlayout.setSpacing(2)
267- self.hboxlayout.setMargin(0)
268- self.hboxlayout.setObjectName("hboxlayout")
269+ self.gridlayout.addWidget(self.MoveFiles, 6, 0, 1, 2)
270 self.move_files_to = QtGui.QLineEdit(MovingOptionsPage)
271+ sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.MinimumExpanding, QtGui.QSizePolicy.Fixed)
272+ sizePolicy.setHorizontalStretch(0)
273+ sizePolicy.setVerticalStretch(0)
274+ sizePolicy.setHeightForWidth(self.move_files_to.sizePolicy().hasHeightForWidth())
275+ self.move_files_to.setSizePolicy(sizePolicy)
276+ self.move_files_to.setMaximumSize(QtCore.QSize(750, 16777215))
277+ self.move_files_to.setFrame(True)
278 self.move_files_to.setObjectName("move_files_to")
279- self.hboxlayout.addWidget(self.move_files_to)
280+ self.gridlayout.addWidget(self.move_files_to, 1, 0, 1, 1)
281 self.move_files_to_browse = QtGui.QPushButton(MovingOptionsPage)
282+ sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Fixed, QtGui.QSizePolicy.Fixed)
283+ sizePolicy.setHorizontalStretch(0)
284+ sizePolicy.setVerticalStretch(0)
285+ sizePolicy.setHeightForWidth(self.move_files_to_browse.sizePolicy().hasHeightForWidth())
286+ self.move_files_to_browse.setSizePolicy(sizePolicy)
287 self.move_files_to_browse.setObjectName("move_files_to_browse")
288- self.hboxlayout.addWidget(self.move_files_to_browse)
289- self.gridlayout.addLayout(self.hboxlayout, 2, 0, 1, 1)
290- self.delete_empty_dirs = QtGui.QCheckBox(MovingOptionsPage)
291- self.delete_empty_dirs.setObjectName("delete_empty_dirs")
292- self.gridlayout.addWidget(self.delete_empty_dirs, 3, 0, 1, 1)
293+ self.gridlayout.addWidget(self.move_files_to_browse, 1, 1, 1, 1)
294 self.move_files = QtGui.QCheckBox(MovingOptionsPage)
295 sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Fixed, QtGui.QSizePolicy.Fixed)
296 sizePolicy.setHorizontalStretch(0)
297@@ -44,14 +77,16 @@
298 sizePolicy.setHeightForWidth(self.move_files.sizePolicy().hasHeightForWidth())
299 self.move_files.setSizePolicy(sizePolicy)
300 self.move_files.setObjectName("move_files")
301- self.gridlayout.addWidget(self.move_files, 1, 0, 1, 1)
302+ self.gridlayout.addWidget(self.move_files, 0, 0, 1, 1)
303
304 self.retranslateUi(MovingOptionsPage)
305 QtCore.QMetaObject.connectSlotsByName(MovingOptionsPage)
306
307 def retranslateUi(self, MovingOptionsPage):
308+ self.MoveFiles.setTitle(_("Additional options"))
309+ self.delete_empty_dirs.setText(_("Delete empty directories"))
310+ self.keep_copy.setText(_("Keep copies of original files"))
311 self.move_additional_files.setText(_("Move additional files:"))
312 self.move_files_to_browse.setText(_("Browse..."))
313- self.delete_empty_dirs.setText(_("Delete empty directories"))
314 self.move_files.setText(_("Move files to this directory when saving:"))
315
316
317=== modified file 'picard/util/__init__.py'
318--- picard/util/__init__.py 2009-10-04 11:28:24 +0000
319+++ picard/util/__init__.py 2009-12-12 02:52:17 +0000
320@@ -22,6 +22,9 @@
321 import re
322 import sys
323 import unicodedata
324+import shutil
325+import time
326+import stat
327 from PyQt4 import QtCore
328 from encodings import rot_13;
329 from string import Template
330@@ -203,49 +206,32 @@
331 def sanitize_filename(string, repl="_"):
332 return _re_slashes.sub(repl, string)
333
334-def make_short_filename(prefix, filename, max_path_length=240, max_length=200,
335- mid_length=32, min_length=2):
336+def make_short_filename(prefix, filename, length_limit=242, path_length_limit=237, part_limit=125):
337 """
338 Attempts to shorten the file name to the maximum allowed length.
339
340- max_path_length: The maximum length of the complete path.
341- max_length: The maximum length of a single file or directory name.
342- mid_length: The medium preferred length of a single file or directory.
343- min_length: The minimum allowed length of a single file or directory.
344+ length_limit: The maximum length of the complete path.
345+ path_length_limit: The maxmimum length of prefix + dirname(filename)
346+ part_length: The maximum length of a single file or directory name.
347 """
348- parts = [part.strip() for part in _re_slashes.split(filename)]
349- parts.reverse()
350- filename = os.path.join(*parts)
351- left = len(prefix) + len(filename) + 1 - max_path_length
352-
353- for i in range(len(parts)):
354- left -= max(0, len(parts[i]) - max_length)
355- parts[i] = parts[i][:max_length]
356-
357- if left > 0:
358- for i in range(len(parts)):
359- length = len(parts[i]) - mid_length
360- if length > 0:
361- length = min(left, length)
362- parts[i] = parts[i][:-length]
363- left -= length
364- if left <= 0:
365- break
366-
367- if left > 0:
368- for i in range(len(parts)):
369- length = len(parts[i]) - min_length
370- if length > 0:
371- length = min(left, length)
372- parts[i] = parts[i][:-length]
373- left -= length
374- if left <= 0:
375- break
376-
377- if left > 0:
378- raise IOError, "File name is too long."
379-
380- return os.path.join(*[a.strip() for a in reversed(parts)])
381+ (path, filename) = os.path.split(filename)
382+ try:
383+ if sys.platform == "win32":
384+ path = os.path.join(*[p.strip()[:part_limit].strip() for p in _re_slashes.split(path)])
385+ prefix_length = len(prefix)
386+ if prefix_length > path_length_limit:
387+ raise IOError, "Path is too long. You can try changing the directory to move files to."
388+ path_length = path_length_limit - prefix_length
389+ path = path.strip()
390+ if len(path) > path_length:
391+ path = path[:path_length].strip() + "_"
392+ file_length_limit = length_limit - (prefix_length + len(path) + 1)
393+ filename = filename.strip()
394+ if len(filename) > file_length_limit:
395+ filename = filename[:file_length_limit].strip() + "_"
396+ except:
397+ raise
398+ return os.path.join(path, filename)
399
400
401 def _reverse_sortname(sortname):
402@@ -325,3 +311,46 @@
403
404 def rot13(input):
405 return u''.join(unichr(rot_13.encoding_map.get(ord(c), ord(c))) for c in input)
406+
407+
408+def copy_adv(src, dst):
409+ shutil.copyfile(src, dst)
410+ try:
411+ shutil.copystat(src, dst)
412+ except OSError, err:
413+ if WindowsError is not None and isinstance(err, WindowsError):
414+ pass
415+ else:
416+ raise
417+
418+
419+def move_adv(src, dst, keep=False):
420+ real_dst = dst
421+ if os.path.isdir(dst):
422+ real_dst = os.path.join(dst, shutil._basename(src))
423+ if os.path.exists(real_dst): pass
424+ try:
425+ os.rename(src, real_dst)
426+ if keep: copy_adv(real_dst,src)
427+ except OSError:
428+ #self.log.debug("Using alternate copy methods" ,src, dst)
429+ if os.path.isdir(src):
430+ if shutil.destinsrc(src, dst):
431+ #self.log.debug("Cannot move a directory '%r' into itself '%r'.", src, dst)
432+ raise Error, "Cannot move a directory '%s' into itself '%s'." % (src, dst)
433+ shutil.copytree(src, real_dst, symlinks=True)
434+ shutil.rmtree(src)
435+ else:
436+ copy_adv(src,dst)
437+ if not keep:
438+ for wait in range (1, 200):
439+ if os.path.exists(dst) or int(os.path.getsize(src)) != int(os.path.getsize(dst)):
440+ continue
441+ else:
442+ time.sleep(0.01)
443+ if int(os.path.getsize(src)) == int(os.path.getsize(dst)):
444+ os.unlink(src)
445+ else:
446+ try: os.unlink(dst)
447+ except: pass
448+ raise IOError, "File could not be copied."
449
450=== modified file 'test/test_utils.py'
451--- test/test_utils.py 2007-03-25 12:08:59 +0000
452+++ test/test_utils.py 2009-12-12 02:52:17 +0000
453@@ -36,12 +36,12 @@
454 class ReplaceWin32IncompatTest(unittest.TestCase):
455
456 def test_correct(self):
457- self.failUnlessEqual(util.replace_win32_incompat("c:\\test\\te\"st2"),
458- "c__test_te_st2")
459+ self.failUnlessEqual(util.replace_win32_incompat("c:\\te*st\\t<e>\"st2"),
460+ "c_\\te_st\\t_e__st2")
461
462 def test_incorrect(self):
463- self.failIfEqual(util.replace_win32_incompat("c:\\test\\te\"st2"),
464- "c:\\test\\te\"st2")
465+ self.failIfEqual(util.replace_win32_incompat("c:\\te*st\\t<e>\"st2"),
466+ "c:\\te*st\\t<e>\"st2")
467
468
469 class SanitizeDateTest(unittest.TestCase):
470@@ -61,23 +61,25 @@
471 class ShortFilenameTest(unittest.TestCase):
472
473 def test_short(self):
474- fn = util.make_short_filename("/home/me/", os.path.join("a1234567890", "b1234567890"), 255)
475+ fn = util.make_short_filename("/home/me/", os.path.join("a1234567890", "b1234567890"), length_limit=240)
476 self.failUnlessEqual(fn, os.path.join("a1234567890", "b1234567890"))
477
478 def test_long(self):
479- fn = util.make_short_filename("/home/me/", os.path.join("a1234567890", "b1234567890"), 20)
480- self.failUnlessEqual(fn, os.path.join("a123456", "b1"))
481-
482- def test_long_2(self):
483- fn = util.make_short_filename("/home/me/", os.path.join("a1234567890", "b1234567890"), 22)
484- self.failUnlessEqual(fn, os.path.join("a12345678", "b1"))
485-
486- def test_too_long(self):
487- self.failUnlessRaises(IOError, util.make_short_filename, "/home/me/", os.path.join("a1234567890", "b1234567890"), 10)
488-
489+ fn = util.make_short_filename("/home/me/", os.path.join("a1234567890", "b1234567890", "c1234567890"), length_limit=30, path_length_limit=24, part_limit=10)
490+ self.failUnlessEqual(fn, os.path.join("a123456789", "b12", "c1234"))
491+
492+ def test_prefix_exceeds_path_length_limit(self):
493+ self.failUnlessRaises(IOError, util.make_short_filename, "/home/me/", os.path.join("a1234567890", "b1234567890"), path_length_limit=9)
494+
495+ def test_prefix_equals_path_length_limit(self):
496+ try:
497+ util.make_short_filename("/home/me/", os.path.join("a1234567890", "b1234567890"), path_length_limit=10)
498+ except IOError:
499+ self.fail("make_short_filename must not throw IOError")
500+
501 def test_whitespace(self):
502- fn = util.make_short_filename("/home/me/", os.path.join("a1234567890 ", " b1234567890 "), 22)
503- self.failUnlessEqual(fn, os.path.join("a12345678", "b1"))
504+ fn = util.make_short_filename("/home/me/", os.path.join("a1234567890 ", " b1234567890 "), length_limit=24, path_length_limit=20, part_limit=10)
505+ self.failUnlessEqual(fn, os.path.join("a123456789", "b12"))
506
507
508 class TranslateArtistTest(unittest.TestCase):
509
510=== modified file 'ui/options_moving.ui'
511--- ui/options_moving.ui 2009-01-30 20:46:55 +0000
512+++ ui/options_moving.ui 2009-12-12 02:52:17 +0000
513@@ -1,80 +1,171 @@
514-<ui version="4.0" >
515- <class>MovingOptionsPage</class>
516- <widget class="QWidget" name="MovingOptionsPage" >
517- <property name="geometry" >
518- <rect>
519- <x>0</x>
520- <y>0</y>
521- <width>504</width>
522- <height>563</height>
523- </rect>
524- </property>
525- <layout class="QGridLayout" >
526- <item row="8" column="0" >
527- <spacer>
528- <property name="orientation" >
529- <enum>Qt::Vertical</enum>
530- </property>
531- <property name="sizeHint" stdset="0" >
532- <size>
533- <width>378</width>
534- <height>16</height>
535- </size>
536- </property>
537- </spacer>
538- </item>
539- <item row="5" column="0" >
540- <widget class="QLineEdit" name="move_additional_files_pattern" />
541- </item>
542- <item row="4" column="0" >
543- <widget class="QCheckBox" name="move_additional_files" >
544- <property name="text" >
545- <string>Move additional files:</string>
546- </property>
547- </widget>
548- </item>
549- <item row="2" column="0" >
550- <layout class="QHBoxLayout" >
551- <property name="spacing" >
552- <number>2</number>
553- </property>
554- <property name="margin" >
555- <number>0</number>
556- </property>
557- <item>
558- <widget class="QLineEdit" name="move_files_to" />
559- </item>
560- <item>
561- <widget class="QPushButton" name="move_files_to_browse" >
562- <property name="text" >
563- <string>Browse...</string>
564- </property>
565- </widget>
566- </item>
567- </layout>
568- </item>
569- <item row="3" column="0" >
570- <widget class="QCheckBox" name="delete_empty_dirs" >
571- <property name="text" >
572- <string>Delete empty directories</string>
573- </property>
574- </widget>
575- </item>
576- <item row="1" column="0" >
577- <widget class="QCheckBox" name="move_files" >
578- <property name="sizePolicy" >
579- <sizepolicy vsizetype="Fixed" hsizetype="Fixed" >
580- <horstretch>0</horstretch>
581- <verstretch>0</verstretch>
582- </sizepolicy>
583- </property>
584- <property name="text" >
585- <string>Move files to this directory when saving:</string>
586- </property>
587- </widget>
588- </item>
589- </layout>
590- </widget>
591- <resources/>
592- <connections/>
593-</ui>
594+<?xml version="1.0" encoding="UTF-8"?>
595+<ui version="4.0">
596+ <class>MovingOptionsPage</class>
597+ <widget class="QWidget" name="MovingOptionsPage">
598+ <property name="geometry">
599+ <rect>
600+ <x>0</x>
601+ <y>0</y>
602+ <width>500</width>
603+ <height>333</height>
604+ </rect>
605+ </property>
606+ <property name="sizePolicy">
607+ <sizepolicy hsizetype="Expanding" vsizetype="Expanding">
608+ <horstretch>0</horstretch>
609+ <verstretch>0</verstretch>
610+ </sizepolicy>
611+ </property>
612+ <layout class="QGridLayout">
613+ <item row="15" column="0">
614+ <spacer name="verticalSpacer">
615+ <property name="orientation">
616+ <enum>Qt::Vertical</enum>
617+ </property>
618+ <property name="sizeType">
619+ <enum>QSizePolicy::Expanding</enum>
620+ </property>
621+ <property name="sizeHint" stdset="0">
622+ <size>
623+ <width>20</width>
624+ <height>40</height>
625+ </size>
626+ </property>
627+ </spacer>
628+ </item>
629+ <item row="6" column="0" colspan="2">
630+ <widget class="QGroupBox" name="MoveFiles">
631+ <property name="enabled">
632+ <bool>true</bool>
633+ </property>
634+ <property name="sizePolicy">
635+ <sizepolicy hsizetype="Expanding" vsizetype="Preferred">
636+ <horstretch>0</horstretch>
637+ <verstretch>0</verstretch>
638+ </sizepolicy>
639+ </property>
640+ <property name="minimumSize">
641+ <size>
642+ <width>100</width>
643+ <height>120</height>
644+ </size>
645+ </property>
646+ <property name="title">
647+ <string>Additional options</string>
648+ </property>
649+ <widget class="QCheckBox" name="delete_empty_dirs">
650+ <property name="geometry">
651+ <rect>
652+ <x>10</x>
653+ <y>40</y>
654+ <width>486</width>
655+ <height>18</height>
656+ </rect>
657+ </property>
658+ <property name="text">
659+ <string>Delete empty directories</string>
660+ </property>
661+ </widget>
662+ <widget class="QCheckBox" name="keep_copy">
663+ <property name="geometry">
664+ <rect>
665+ <x>10</x>
666+ <y>20</y>
667+ <width>486</width>
668+ <height>18</height>
669+ </rect>
670+ </property>
671+ <property name="text">
672+ <string>Keep copies of original files</string>
673+ </property>
674+ </widget>
675+ <widget class="QLineEdit" name="move_additional_files_pattern">
676+ <property name="enabled">
677+ <bool>true</bool>
678+ </property>
679+ <property name="geometry">
680+ <rect>
681+ <x>30</x>
682+ <y>80</y>
683+ <width>400</width>
684+ <height>20</height>
685+ </rect>
686+ </property>
687+ <property name="sizePolicy">
688+ <sizepolicy hsizetype="Preferred" vsizetype="Maximum">
689+ <horstretch>0</horstretch>
690+ <verstretch>0</verstretch>
691+ </sizepolicy>
692+ </property>
693+ <property name="frame">
694+ <bool>true</bool>
695+ </property>
696+ <property name="alignment">
697+ <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter</set>
698+ </property>
699+ </widget>
700+ <widget class="QCheckBox" name="move_additional_files">
701+ <property name="geometry">
702+ <rect>
703+ <x>10</x>
704+ <y>60</y>
705+ <width>486</width>
706+ <height>18</height>
707+ </rect>
708+ </property>
709+ <property name="text">
710+ <string>Move additional files:</string>
711+ </property>
712+ </widget>
713+ </widget>
714+ </item>
715+ <item row="1" column="0">
716+ <widget class="QLineEdit" name="move_files_to">
717+ <property name="sizePolicy">
718+ <sizepolicy hsizetype="MinimumExpanding" vsizetype="Fixed">
719+ <horstretch>0</horstretch>
720+ <verstretch>0</verstretch>
721+ </sizepolicy>
722+ </property>
723+ <property name="maximumSize">
724+ <size>
725+ <width>750</width>
726+ <height>16777215</height>
727+ </size>
728+ </property>
729+ <property name="frame">
730+ <bool>true</bool>
731+ </property>
732+ </widget>
733+ </item>
734+ <item row="1" column="1">
735+ <widget class="QPushButton" name="move_files_to_browse">
736+ <property name="sizePolicy">
737+ <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
738+ <horstretch>0</horstretch>
739+ <verstretch>0</verstretch>
740+ </sizepolicy>
741+ </property>
742+ <property name="text">
743+ <string>Browse...</string>
744+ </property>
745+ </widget>
746+ </item>
747+ <item row="0" column="0">
748+ <widget class="QCheckBox" name="move_files">
749+ <property name="sizePolicy">
750+ <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
751+ <horstretch>0</horstretch>
752+ <verstretch>0</verstretch>
753+ </sizepolicy>
754+ </property>
755+ <property name="text">
756+ <string>Move files to this directory when saving:</string>
757+ </property>
758+ </widget>
759+ </item>
760+ </layout>
761+ </widget>
762+ <resources/>
763+ <connections/>
764+</ui>

Subscribers

People subscribed via source and target branches

to status/vote changes: