Merge lp:~openshift/picard/file_system into lp:~musicbrainz-developers/picard/trunk
- file_system
- Merge into 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 |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
MusicBrainz Developers | Pending | ||
Review via email: mp+16070@code.launchpad.net |
Commit message
Description of the change
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> |