X-Git-Url: https://code.delx.au/notipod/blobdiff_plain/9b9d4091c893e58ee0f3a39b243fac098f11a4d6..2a63113c140f7b0d41c5980fab90af8510129d34:/libsyncitunes.py diff --git a/libsyncitunes.py b/libsyncitunes.py index 3bccfec..80f6a0d 100644 --- a/libsyncitunes.py +++ b/libsyncitunes.py @@ -5,6 +5,7 @@ import logging import os import shutil +import sys import urllib from Foundation import * @@ -36,19 +37,19 @@ class Playlist(NSObject): parent.children.append(self) class ITunesLibrary(NSObject): - def init(self): - return self.initWithFilename_("~/Music/iTunes/iTunes Music Library.xml") - - def initWithFilename_(self, filename): + def load_(self, filename): + if filename is None: + filename = "~/Music/iTunes/iTunes Music Library.xml" filename = os.path.expanduser(filename) + yield "Reading library..." plist = read_plist(os.path.expanduser(filename)) self.folder = self.loc2name(plist["Music Folder"]) pl_tracks = plist["Tracks"] self.playlists = {} for pl_playlist in plist["Playlists"]: playlist = self.make_playlist(pl_playlist, pl_tracks) + yield "Read playlist: " + playlist.name self.playlists[playlist.pid] = playlist - return self def loc2name(self, location): return urllib.splithost(urllib.splittype(urllib.unquote(location))[1])[1] @@ -67,13 +68,11 @@ class ITunesLibrary(NSObject): trackID = item["Track ID"] filename = str(pl_tracks[str(trackID)]["Location"]) filename = self.loc2name(filename) - filename = eval(repr(filename).lstrip("u")).decode("utf-8") + filename = filename.decode("utf-8") if not filename.startswith(self.folder): logging.warn("Skipping: " + filename) continue - filename = filename[len(self.folder):] - if filename.startswith("/"): - filename = filename[1:] + filename = strip_prefix(filename, self.folder) tracks.append(filename) playlist = Playlist.alloc().init() playlist.set(name, pid, tracks, parent) @@ -117,18 +116,22 @@ class ITunesLibrary(NSObject): return item.name -def export_m3u(dry_run, dest, path_prefix, playlist_name, files): - if dry_run: - return - playlist_file = os.path.join(dest, playlist_name) + ".m3u" - logging.info("Writing: " + playlist_file) - f = open(playlist_file, "w") - for filename in files: - if path_prefix.find("\\") > 0: - filename = filename.replace("/", "\\") - filename = filename.encode("utf-8") - f.write("%s%s\n" % (path_prefix, filename)) - f.close() +encoded_names = {} +valid_chars = frozenset("\\/-_.() abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789") +def encode_filename(filename): + try: + return encoded_names[filename] + except KeyError: + pass + orig_filename = filename + filename = filename.encode("ascii", "ignore") + filename = "".join(c for c in filename if c in valid_chars) + if filename in encoded_names: + a, b = os.path.splitext(filename) + a += "-dup" + filename = a + b + encoded_names[orig_filename] = filename + return filename def strip_prefix(s, prefix): assert s.startswith(prefix) @@ -150,51 +153,81 @@ def mkdirhier(path): except OSError: pass -def sync(dry_run, source, dest, files): +def export_m3u(dry_run, dest, path_prefix, playlist_name, files): + if dry_run: + return + if not path_prefix: + path_prefix = "../" + playlist_file = os.path.join(dest, "-Playlists-", playlist_name) + ".m3u" + mkdirhier(os.path.dirname(playlist_file)) + logging.info("Writing: " + playlist_file) + f = open(playlist_file, "w") + for filename in files: + if path_prefix.find("\\") > 0: + filename = filename.replace("/", "\\") + filename = encode_filename(filename) + f.write("%s%s\n" % (path_prefix, filename)) + f.close() + +def sync(dry_run, source, dest, files_to_copy): join = os.path.join logging.info("Calculating files to sync and deleting old files") - files = set(files) + source = source.encode("utf-8") + dest = dest.encode("utf-8") + filemap = {} + class SyncFile(object): pass + for f in files_to_copy: + sf = SyncFile() + sf.orig_filename = f.encode("utf-8") + sf.encoded_filename = encode_filename(f) + filemap[sf.encoded_filename.lower()] = sf + files_to_copy = set(filemap) + for dirpath, dirnames, filenames in os.walk(dest): full_dirpath = dirpath dirpath = strip_prefix(dirpath, dest) for filename in filenames: - filename = join(dirpath, filename).decode("utf-8") + filename = join(dirpath, filename) # Whenever 'file' is deleted OSX will helpfully remove '._file' if not os.path.exists(join(dest, filename)): continue - if filename in files: - sourcestat = os.stat(join(source, filename)) + if filename.lower() in files_to_copy: + source_filename = filemap[filename.lower()].orig_filename + sourcestat = os.stat(join(source, source_filename)) deststat = os.stat(join(dest, filename)) same_time = abs(sourcestat.st_mtime - deststat.st_mtime) < 5 same_size = sourcestat.st_size == deststat.st_size if same_time and same_size: - files.remove(filename) - logging.debug("keep: " + filename) + files_to_copy.remove(filename.lower()) + yield "Keep: " + filename else: - logging.debug("update: " + filename) + yield "Update: " + filename - elif not filename.endswith(".m3u"): - logging.debug("delete: " + filename) + elif not filename.startswith("-Playlists-"): + yield "Delete: " + filename if not dry_run: os.unlink(join(dest, filename)) if len(os.listdir(full_dirpath)) == 0: - logging.debug("rmdir: " + dirpath) + yield "Delete: " + dirpath if not dry_run: os.rmdir(full_dirpath) logging.info("Copying new files") - files = list(files) - files.sort() - for filename in files: - logging.debug("copy: " + filename) + files_to_copy = list(files_to_copy) + files_to_copy.sort() + for filename in files_to_copy: + yield "Copy: " + filemap[filename].orig_filename if not dry_run: mkdirhier(os.path.dirname(join(dest, filename))) - shutil.copy2(join(source, filename), join(dest, filename)) + shutil.copy2( + join(source, filemap[filename].orig_filename), + join(dest, filemap[filename].encoded_filename) + )