示例#1
0
 def __save_files(self, parent, model, library):
     win = WritingWindow(parent, len(model))
     was_changed = []
     for row in model:
         song = row[0]
         track = row[2]
         if song.get("tracknumber") == track:
             win.step()
             continue
         if not song.valid() and not qltk.ConfirmAction(
             win, _("Tag may not be accurate"),
             _("<b>%s</b> changed while the program was running. "
               "Saving without refreshing your library may "
               "overwrite other changes to the song.\n\n"
               "Save this song anyway?") %(
             util.escape(util.fsdecode(song("~basename"))))
             ).run():
             break
         song["tracknumber"] = track
         try: song.write()
         except:
             util.print_exc()
             qltk.ErrorMessage(
                 win, _("Unable to save song"),
                 _("Saving <b>%s</b> failed. The file may be "
                   "read-only, corrupted, or you do not have "
                   "permission to edit it.")%(
                 util.escape(util.fsdecode(song('~basename'))))).run()
             library.reload(song, changed=was_changed)
             break
         was_changed.append(song)
         if win.step(): break
     library.changed(was_changed)
     win.destroy()
示例#2
0
    def __init__(self, parent, files, asktrash=True, askonly=False):
        super(DeleteDialog, self).__init__(
            _("Delete Files"), get_top_parent(parent))
        self.set_border_width(6)
        self.vbox.set_spacing(6)
        self.set_has_separator(False)
        self.action_area.set_border_width(0)
        self.set_resizable(False)

        self.__files = files

        if asktrash and trash.can_trash():
            b = Button(_("_Move to Trash"), gtk.STOCK_DELETE)
            self.add_action_widget(b, 0)

        self.__askonly = askonly

        self.add_button(gtk.STOCK_CANCEL, 1)
        self.add_button(gtk.STOCK_DELETE, 2)

        hbox = gtk.HBox()
        hbox.set_border_width(6)
        i = gtk.Image()
        i.set_from_stock(gtk.STOCK_DIALOG_WARNING, gtk.ICON_SIZE_DIALOG)
        i.set_padding(12, 0)
        i.set_alignment(0.5, 0.0)
        hbox.pack_start(i, expand=False)
        vbox = gtk.VBox(spacing=6)

        base = os.path.basename(files[0])
        if len(files) == 1: l = _("Permanently delete this file?")
        else: l = _("Permanently delete these files?")
        if len(files) == 1:
            exp = gtk.Expander("%s" % util.fsdecode(base))
        else:
            exp = gtk.Expander(ngettext("%(title)s and %(count)d more...",
                "%(title)s and %(count)d more...", len(files)-1) %
                {'title': util.fsdecode(base), 'count': len(files) - 1})

        lab = gtk.Label()
        lab.set_markup("<big><b>%s</b></big>" % l)
        lab.set_alignment(0.0, 0.5)
        vbox.pack_start(lab, expand=False)

        lab = gtk.Label("\n".join(
            map(util.fsdecode, map(util.unexpand, files))))
        lab.set_alignment(0.1, 0.0)
        exp.add(gtk.ScrolledWindow())
        exp.child.add_with_viewport(lab)
        exp.child.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
        exp.child.child.set_shadow_type(gtk.SHADOW_NONE)
        vbox.pack_start(exp)
        hbox.pack_start(vbox)
        self.vbox.pack_start(hbox)
        self.vbox.show_all()
示例#3
0
    def __save(self, addreplace, library):
        pattern_text = self.combo.child.get_text().decode('utf-8')
        pattern = TagsFromPattern(pattern_text)
        model = self.view.get_model()
        add = bool(addreplace.get_active())
        win = WritingWindow(self, len(model))

        was_changed = []

        for row in (model or []):
            song = row[0]
            changed = False
            if not song.valid() and not qltk.ConfirmAction(
                self, _("Tag may not be accurate"),
                _("<b>%s</b> changed while the program was running. "
                  "Saving without refreshing your library may "
                  "overwrite other changes to the song.\n\n"
                  "Save this song anyway?") %(
                util.escape(util.fsdecode(song("~basename"))))
                ).run():
                break

            for i, h in enumerate(pattern.headers):
                if row[i + 2]:
                    text = row[i + 2].decode("utf-8")
                    if not add or h not in song or not song.multiple_values:
                        song[h] = text
                        changed = True
                    else:
                        for val in text.split("\n"):
                            if val not in song.list(h):
                                song.add(h, val)
                                changed = True

            if changed:
                try: song.write()
                except:
                    qltk.ErrorMessage(
                        self, _("Unable to edit song"),
                        _("Saving <b>%s</b> failed. The file "
                          "may be read-only, corrupted, or you "
                          "do not have permission to edit it.")%(
                        util.escape(util.fsdecode(song('~basename'))))
                        ).run()
                    library.reload(song, changed=was_changed)
                    break
                was_changed.append(song)

            if win.step(): break

        win.destroy()
        library.changed(was_changed)
        self.save.set_sensitive(False)
示例#4
0
 def scan(self, paths, exclude=[], cofuncid=None):
     added = []
     exclude = [util.expanduser(path) for path in exclude if path]
     for fullpath in paths:
         print_d("Scanning %r." % fullpath, self)
         desc = _("Scanning %s") % (util.unexpand(util.fsdecode(fullpath)))
         with Task(_("Library"), desc) as task:
             if cofuncid: task.copool(cofuncid)
             fullpath = util.expanduser(fullpath)
             if filter(fullpath.startswith, exclude):
                 continue
             for path, dnames, fnames in os.walk(util.fsnative(fullpath)):
                 for filename in fnames:
                     fullfilename = os.path.join(path, filename)
                     if filter(fullfilename.startswith, exclude):
                         continue
                     if fullfilename not in self._contents:
                         fullfilename = os.path.realpath(fullfilename)
                         if filter(fullfilename.startswith, exclude):
                             continue
                         if fullfilename not in self._contents:
                             item = self.add_filename(fullfilename, False)
                             if item is not None:
                                 added.append(item)
                                 if len(added) > 20:
                                     self.add(added)
                                     added = []
                                     task.pulse()
                                     yield True
                 if added:
                     self.add(added)
                     added = []
                     task.pulse()
                     yield True
示例#5
0
def ParseM3U(filename, library=None):
    plname = util.fsdecode(os.path.basename(
        os.path.splitext(filename)[0])).encode('utf-8')
    filenames = []
    for line in file(filename):
        line = line.strip()
        if line.startswith("#"): continue
        else: filenames.append(line)
    return __ParsePlaylist(plname, filename, filenames, library)
示例#6
0
    def __rename(self, library):
        model = self.view.get_model()
        win = WritingWindow(self, len(model))
        was_changed = []
        skip_all = False
        self.view.freeze_child_notify()

        rows = [(row[0], row[1], row[2].decode('utf-8')) for row in model]
        for song, oldname, newname in rows:
            try:
                newname = util.fsnative(newname)
                library.rename(song, newname, changed=was_changed)
            except StandardError:
                util.print_exc()
                if skip_all: continue
                RESPONSE_SKIP_ALL = 1
                buttons = (_("Ignore _All Errors"), RESPONSE_SKIP_ALL,
                           gtk.STOCK_STOP, gtk.RESPONSE_CANCEL,
                           _("_Continue"), gtk.RESPONSE_OK)
                msg = qltk.Message(
                    gtk.MESSAGE_ERROR, win, _("Unable to rename file"),
                    _("Renaming <b>%s</b> to <b>%s</b> failed. "
                      "Possibly the target file already exists, "
                      "or you do not have permission to make the "
                      "new file or remove the old one.") %(
                    util.escape(util.fsdecode(oldname)),
                    util.escape(util.fsdecode(newname))),
                    buttons=gtk.BUTTONS_NONE)
                msg.add_buttons(*buttons)
                msg.set_default_response(gtk.RESPONSE_OK)
                resp = msg.run()
                skip_all |= (resp == RESPONSE_SKIP_ALL)
                # Preserve old behavior: shift-click is Ignore All
                mods = gtk.gdk.display_get_default().get_pointer()[3]
                skip_all |= mods & gtk.gdk.SHIFT_MASK
                library.reload(song, changed=was_changed)
                if resp != gtk.RESPONSE_OK and resp != RESPONSE_SKIP_ALL:
                    break
            if win.step(): break

        self.view.thaw_child_notify()
        win.destroy()
        library.changed(was_changed)
        self.save.set_sensitive(False)
示例#7
0
    def run(self):
        resp = super(DeleteDialog, self).run()
        if self.__askonly:
            self.destroy()
            return resp

        if resp == 1 or resp == gtk.RESPONSE_DELETE_EVENT: return []
        elif resp == 0: s = _("Moving %(current)d/%(total)d.")
        elif resp == 2: s = _("Deleting %(current)d/%(total)d.")
        else: return []
        files = self.__files
        w = WaitLoadWindow(self, len(files), s)
        removed = []

        if resp == 0:
            for filename in files:
                try:
                    trash.trash(filename)
                except trash.TrashError:
                    fn = util.escape(util.fsdecode(util.unexpand(filename)))
                    ErrorMessage(self, _("Unable to move to trash"),
                        (_("Moving <b>%s</b> to the trash failed.") %
                        fn)).run()
                    break
                removed.append(filename)
                w.step()
        else:
            for filename in files:
                try:
                    os.unlink(filename)
                except EnvironmentError, s:
                    try: s = unicode(s.strerror, const.ENCODING, 'replace')
                    except TypeError:
                        s = unicode(s.strerror[1], const.ENCODING, 'replace')
                    s = "\n\n" + s
                    fn = util.escape(util.fsdecode(util.unexpand(filename)))
                    ErrorMessage(
                        self, _("Unable to delete file"),
                        (_("Deleting <b>%s</b> failed.") % fn) + s).run()
                    break
                removed.append(filename)
                w.step()
示例#8
0
 def comma(self, key):
     value = self.__song.comma(key)
     if isinstance(value, str):
         value = util.fsdecode(value)
     elif not isinstance(value, unicode):
         if isinstance(value, float):
             value = "%.2f" % value
         value = unicode(value)
     for f in self.__formatters:
         value = f(key, value)
     return value
示例#9
0
 def __dict(self, song):
     dict = {}
     for key, value in (song or {}).items():
         if not isinstance(value, basestring):
             value = unicode(value)
         elif isinstance(value, str):
             value = util.fsdecode(value)
         dict[key] = dbusutils.dbus_unicode_validate(value)
     if song:
         dict["~uri"] = song("~uri")
     return dict
示例#10
0
    def match(self, song):
        if isinstance(song, dict):
            song = util.fsdecode(song['~filename'])
        # only match on the last n pieces of a filename, dictated by pattern
        # this means no pattern may effectively cross a /, despite .* doing so
        sep = os.path.sep
        matchon = sep+sep.join(song.split(sep)[-self.slashes:])
        match = self.pattern.search(matchon)

        # dicts for all!
        if match is None: return {}
        else: return match.groupdict()
示例#11
0
def ParsePLS(filename, name="", library=None):
    plname = util.fsdecode(os.path.basename(
        os.path.splitext(filename)[0])).encode('utf-8')
    filenames = []
    for line in file(filename):
        line = line.strip()
        if not line.lower().startswith("file"): continue
        else:
            try: line = line[line.index("=")+1:].strip()
            except ValueError: pass
            else: filenames.append(line)
    return __ParsePlaylist(plname, filename, filenames, library)
示例#12
0
 def search(self, data):
     for name in self.__names:
         val = data.get(name) or data.get("~" + name, "")
         if self.res.search(val):
             return True
     for name in self.__intern:
         if self.res.search(data(name)):
             return True
     for name in self.__fs:
         if self.res.search(fsdecode(data(name))):
             return True
     return False
示例#13
0
    def __preview(self, songs):
        model = self.view.get_model()
        if songs is None:
            songs = [row[0] for row in model]
        pattern = self.combo.child.get_text().decode("utf-8")

        try:
            pattern = FileFromPattern(pattern)
        except ValueError:
            qltk.ErrorMessage(
                self, _("Path is not absolute"),
                _("The pattern\n\t<b>%s</b>\ncontains / but "
                  "does not start from root. To avoid misnamed "
                  "folders, root your pattern by starting "
                  "it with / or ~/.")%(
                util.escape(pattern))).run()
            return
        else:
            if self.combo.child.get_text():
                self.combo.prepend_text(self.combo.child.get_text())
                self.combo.write(const.NBP)

        orignames = [song["~filename"] for song in songs]
        newnames = [util.fsdecode(util.fsencode(pattern.format(song)))
                    for song in songs]
        for f in self.filters:
            if f.active: newnames = f.filter_list(orignames, newnames)

        model.clear()
        for song, newname in zip(songs, newnames):
            basename = util.fsdecode(song("~basename"))
            model.append(row=[song, basename, newname])
        self.preview.set_sensitive(False)
        self.save.set_sensitive(bool(self.combo.child.get_text()))
        for song in songs:
            if not song.is_file:
                self.set_sensitive(False)
                break
        else: self.set_sensitive(True)
示例#14
0
    def sort_by_func(tag):
        """Returns a fast sort function for a specific tag (or pattern).
        Some keys are already in the sort cache, so we can use them."""
        def artist_sort(song):
            return (song.sort_key[1][2])

        if callable(tag):
            return lambda song: human(tag(song))
        elif tag == "artistsort":
            return artist_sort
        elif tag in FILESYSTEM_TAGS:
            return lambda song: util.fsdecode(song(tag), note=False)
        elif tag.startswith("~#") and "~" not in tag[2:]:
            return lambda song: song(tag)
        return lambda song: human(song(tag))
示例#15
0
    def open_chooser(self, action):
        if not os.path.exists(self.last_dir):
            self.last_dir = const.HOME

        if action.get_name() == "AddFolders":
            chooser = FolderChooser(self, _("Add Music"), self.last_dir)
            cb = gtk.CheckButton(_("Watch this folder for new songs"))
            cb.set_active(not config.get("settings", "scan"))
            cb.show()
            chooser.set_extra_widget(cb)
        else:
            chooser = FileChooser(
                self, _("Add Music"), formats.filter, self.last_dir)
            cb = None

        fns = chooser.run()
        chooser.destroy()
        if fns:
            if action.get_name() == "AddFolders":
                self.last_dir = fns[0]
                copool.add(self.__library.scan, fns, funcid="library")
            else:
                self.last_dir = os.path.basename(fns[0])
                for filename in map(os.path.realpath, map(util.fsnative, fns)):
                    if filename in self.__library: continue
                    song = self.__library.add_filename(filename)
                    if not song:
                        from traceback import format_exception_only as feo
                        tb = feo(sys.last_type, sys.last_value)
                        msg = _("%s could not be added to your library.\n\n")
                        msg %= util.escape(util.fsdecode(
                            os.path.basename(filename)))
                        msg += util.escape("".join(tb).decode(
                            const.ENCODING, "replace"))
                        d = ErrorMessage(self, _("Unable to add song"), msg)
                        d.label.set_selectable(True)
                        d.run()
                        continue

        if cb and cb.get_active():
            dirs = util.split_scan_dirs(config.get("settings", "scan"))
            for fn in fns:
                if fn not in dirs: dirs.append(fn)
            dirs = ":".join(dirs)
            config.set("settings", "scan", dirs)
示例#16
0
 def __update(self, songs, total, model, save, revert):
     if songs is None:
         songs = [row[0] for row in model]
     else:
         songs = list(songs)
     songs.sort(
         key=lambda song: (song("~#track"), song("~basename"), song))
     model.clear()
     total.set_value(len(songs))
     for song in songs:
         if not song.can_change("tracknumber"):
             self.set_sensitive(False)
             break
     else: self.set_sensitive(True)
     for song in songs:
         basename = util.fsdecode(song("~basename"))
         model.append(row=[song, basename, song("tracknumber")])
     save.set_sensitive(False)
     revert.set_sensitive(False)
示例#17
0
    def _file(self, song, box):
        def ftime(t):
            if t == 0:
                return _("Unknown")
            else:
                timestr = time.strftime("%c", time.localtime(t))
                return timestr.decode(const.ENCODING)

        fn = util.fsdecode(util.unexpand(song["~filename"]))
        length = util.format_time_long(song.get("~#length", 0))
        size = util.format_size(
            song.get("~#filesize") or util.size(song["~filename"]))
        mtime = ftime(util.mtime(song["~filename"]))
        bitrate = song.get("~#bitrate", 0)
        if bitrate != 0:
            bitrate = _("%d kbps") % int(bitrate)
        else: bitrate = False

        t = gtk.Table(4, 2)
        t.set_col_spacings(6)
        t.set_homogeneous(False)
        table = [(_("length"), length),
                 (_("file size"), size),
                 (_("modified"), mtime)]
        if bitrate:
            table.insert(1, (_("bitrate"), bitrate))
        fnlab = Label(fn)
        fnlab.set_ellipsize(pango.ELLIPSIZE_MIDDLE)
        t.attach(fnlab, 0, 2, 0, 1, xoptions=gtk.FILL)
        for i, (l, r) in enumerate(table):
            l = "<b>%s</b>" % util.capitalize(util.escape(l) + ":")
            lab = Label()
            lab.set_markup(l)
            t.attach(lab, 0, 1, i + 1, i + 2, xoptions=gtk.FILL)
            t.attach(Label(r), 1, 2, i + 1, i + 2)

        box.pack_start(Frame(_("File"), t), expand=False, fill=False)
示例#18
0
    def _post(self, value, song):
        if value:
            fn = song.get("~filename", ".")
            ext = fn[fn.rfind("."):].lower()
            val_ext = value[-len(ext):].lower()
            if not ext == val_ext: value += ext.lower()
            if os.name == "nt":
                value = util.strip_win32_incompat_from_path(value)

            value = util.expanduser(value)

            # Limit each path section to 255 (bytes on linux, chars on win).
            # http://en.wikipedia.org/wiki/Comparison_of_file_systems#Limits
            path, ext = os.path.splitext(value)
            path = map(util.fsnative, path.split(os.sep))
            limit = [255] * len(path)
            limit[-1] -= len(util.fsnative(ext))
            elip = lambda (p, l): (len(p) > l and p[:l-2] + "..") or p
            path = os.sep.join(map(elip, zip(path, limit)))
            value = util.fsdecode(path) + ext

            if os.sep in value and not os.path.isabs(value):
                raise ValueError("Pattern is not rooted")
        return value
示例#19
0
    def __preview(self, songs):
        if songs is None:
            songs = [row[0] for row in (self.view.get_model() or [])]

        if songs: pattern_text = self.combo.child.get_text().decode("utf-8")
        else: pattern_text = ""
        try: pattern = TagsFromPattern(pattern_text)
        except re.error:
            qltk.ErrorMessage(
                self, _("Invalid pattern"),
                _("The pattern\n\t<b>%s</b>\nis invalid. "
                  "Possibly it contains the same tag twice or "
                  "it has unbalanced brackets (&lt; / &gt;).")%(
                util.escape(pattern_text))).run()
            return
        else:
            if pattern_text:
                self.combo.prepend_text(pattern_text)
                self.combo.write(const.TBP)

        invalid = []

        for header in pattern.headers:
            if not min([song.can_change(header) for song in songs]):
                invalid.append(header)
        if len(invalid) and songs:
            if len(invalid) == 1:
                title = _("Invalid tag")
                msg = _("Invalid tag <b>%s</b>\n\nThe files currently"
                        " selected do not support editing this tag.")
            else:
                title = _("Invalid tags")
                msg = _("Invalid tags <b>%s</b>\n\nThe files currently"
                        " selected do not support editing these tags.")
            qltk.ErrorMessage(
                self, title, msg % ", ".join(invalid)).run()
            pattern = TagsFromPattern("")

        self.view.set_model(None)
        model = gtk.ListStore(
            object, str, *([str] * len(pattern.headers)))
        for col in self.view.get_columns():
            self.view.remove_column(col)

        col = gtk.TreeViewColumn(_('File'), gtk.CellRendererText(),
                                 text=1)
        col.set_sizing(gtk.TREE_VIEW_COLUMN_AUTOSIZE)
        self.view.append_column(col)
        for i, header in enumerate(pattern.headers):
            render = gtk.CellRendererText()
            render.set_property('editable', True)
            render.connect('edited', self.__row_edited, model, i + 2)
            escaped_title = header.replace("_", "__")
            col = gtk.TreeViewColumn(escaped_title, render, text=i + 2)
            col.set_sizing(gtk.TREE_VIEW_COLUMN_AUTOSIZE)
            self.view.append_column(col)

        for song in songs:
            basename = util.fsdecode(song("~basename"))
            row = [song, basename]
            match = pattern.match(song)
            for h in pattern.headers:
                text = match.get(h, '')
                for f in self.filters:
                    if f.active: text = f.filter(h, text)
                if not song.multiple_values:
                    text = u", ".join(text.split("\n"))
                row.append(text)
            model.append(row=row)

        # save for last to potentially save time
        if songs: self.view.set_model(model)
        self.preview.set_sensitive(False)
        self.save.set_sensitive(len(pattern.headers) > 0)
示例#20
0
    def copy(self, songlist, song):
        if self.__load_db() is None: return False
        track = gpod.itdb_track_new()

        # All values should be utf-8 encoded strings
        # Filepaths should be encoded with the fs encoding

        # Either combine tags with comma, or only take the first value
        if self['all_tags']: tag = song.comma
        else: tag = lambda key: (song.list(key) or ('',))[0]

        title = tag('title')
        if self['title_version'] and song('version'):
            title = " - ".join([title, song('version')])
        track.title = util.encode(title)

        album = tag('album')
        if self['album_part'] and song('discsubtitle'):
            album = " - ".join([album, song('discsubtitle')])
        track.album = util.encode(album)

        # String keys
        for key in ['artist', 'genre', 'grouping', 'composer', 'albumartist']:
            if hasattr(track, key): # albumartist since libgpod-0.4.2
                setattr(track, key, util.encode(tag(key)))
        # Sort keys (since libgpod-0.5.0)
        for key in ['artist', 'album', 'albumartist']:
            if hasattr(track, 'sort_' + key):
                setattr(track, 'sort_' + key, util.encode(tag(key + 'sort')))
        # Numeric keys
        for key in ['bitrate', 'playcount', 'year']:
            try: setattr(track, key, int(song('~#'+key)))
            except ValueError: continue
        # Numeric keys where the names differ
        for key, value in {
            'cd_nr':         song('~#disc'),
            'cds':           song('~#discs'),
            'rating':        min(100, song('~#rating') * 100),
            'time_added':    self.__mactime(time.time()),
            'time_modified': self.__mactime(util.mtime(song('~filename'))),
            'track_nr':      song('~#track'),
            'tracklen':      song('~#length') * 1000,
            'tracks':        song('~#tracks'),
            'size':          util.size(song('~filename')),
            'soundcheck':    self.__soundcheck(song),
        }.items():
            try: setattr(track, key, int(value))
            except ValueError: continue

        track.filetype = song('~format')
        track.comment = util.encode(util.fsdecode(song('~filename')))

        # Associate a cover with the track
        if self['covers']:
            cover = song.find_cover()
            if cover:
                # libgpod will copy the file later when the iTunesDB
                # is saved, so we have to keep a reference around in
                # case the cover is a temporary file.
                self.__covers.append(cover)
                gpod.itdb_track_set_thumbnails(
                    track, util.fsencode(cover.name))

        # Add the track to the master playlist
        gpod.itdb_track_add(self.__itdb, track, -1)
        master = gpod.itdb_playlist_mpl(self.__itdb)
        gpod.itdb_playlist_add_track(master, track, -1)

        # Copy the actual file
        if gpod.itdb_cp_track_to_ipod(track, song['~filename'], None) == 1:
            return IPodSong(track)
        else:
            return False
示例#21
0
 def _cdf(self, column, cell, model, iter, tag):
     value = model[iter][0].comma(tag)
     if not self._needs_update(value): return
     cell.set_property('text', util.unexpand(util.fsdecode(value)))
示例#22
0
 def cell_data(column, cell, model, iter):
     value = model[iter][0]
     if value is not None:
         cell.set_property('text', util.fsdecode(
             os.path.basename(value) or value))
示例#23
0
    def __save_files(self, save, revert, model, library):
        updated = {}
        deleted = {}
        added = {}
        renamed = {}
        for row in model:
            if row[EDITED] and not (row[DELETED] or row[RENAMED]):
                if row[ORIGVALUE] is not None:
                    updated.setdefault(row[TAG], [])
                    updated[row[TAG]].append((util.decode(row[VALUE]), util.decode(row[ORIGVALUE])))
                else:
                    added.setdefault(row[TAG], [])
                    added[row[TAG]].append(util.decode(row[VALUE]))
            if row[EDITED] and row[DELETED]:
                if row[ORIGVALUE] is not None:
                    deleted.setdefault(row[TAG], [])
                    deleted[row[TAG]].append(util.decode(row[ORIGVALUE]))

            if row[EDITED] and row[RENAMED] and not row[DELETED]:
                renamed.setdefault(row[TAG], [])
                renamed[row[TAG]].append(
                    (util.decode(row[ORIGTAG]), util.decode(row[VALUE]), util.decode(row[ORIGVALUE]))
                )

        was_changed = []
        songs = self.__songinfo.songs
        win = WritingWindow(self, len(songs))
        for song in songs:
            if (
                not song.valid()
                and not qltk.ConfirmAction(
                    self,
                    _("Tag may not be accurate"),
                    _(
                        "<b>%s</b> changed while the program was running. "
                        "Saving without refreshing your library may "
                        "overwrite other changes to the song.\n\n"
                        "Save this song anyway?"
                    )
                    % util.escape(util.fsdecode(song("~basename"))),
                ).run()
            ):
                break

            changed = False
            for key, values in updated.iteritems():
                for (new_value, old_value) in values:
                    new_value = util.unescape(new_value)
                    if song.can_change(key):
                        if old_value is None:
                            song.add(key, new_value)
                        else:
                            song.change(key, old_value, new_value)
                        changed = True
            for key, values in added.iteritems():
                for value in values:
                    value = util.unescape(value)
                    if song.can_change(key):
                        song.add(key, value)
                        changed = True
            for key, values in deleted.iteritems():
                for value in values:
                    value = util.unescape(value)
                    if song.can_change(key) and key in song:
                        song.remove(key, value)
                        changed = True
            save_rename = []
            for new_tag, values in renamed.iteritems():
                for old_tag, new_value, old_value in values:
                    old_tag = util.unescape(old_tag)
                    old_value = util.unescape(old_value)
                    new_value = util.unescape(new_value)
                    if song.can_change(new_tag) and song.can_change(old_tag) and old_tag in song:
                        if not is_special(new_value):
                            song.remove(old_tag, old_value)
                            save_rename.append((new_tag, new_value))
                        elif is_missing(new_value):
                            value = strip_missing(old_value)
                            song.remove(old_tag, old_value)
                            save_rename.append((new_tag, new_value))
                        else:
                            save_rename.append((new_tag, song[old_tag]))
                            song.remove(old_tag, None)
                        changed = True
            for tag, value in save_rename:
                song.add(tag, value)

            if changed:
                try:
                    song.write()
                except:
                    util.print_exc()
                    qltk.ErrorMessage(
                        self,
                        _("Unable to save song"),
                        _(
                            "Saving <b>%s</b> failed. The file "
                            "may be read-only, corrupted, or you "
                            "do not have permission to edit it."
                        )
                        % (util.escape(util.fsdecode(song("~basename")))),
                    ).run()
                    library.reload(song, changed=was_changed)
                    break
                was_changed.append(song)

            if win.step():
                break

        win.destroy()
        library.changed(was_changed)
        for b in [save, revert]:
            b.set_sensitive(False)
示例#24
0
    def __save_files(self, save, revert, model, library):
        updated = {}
        deleted = {}
        added = {}
        renamed = {}
        for row in model:
            if row[EDITED] and not (row[DELETED] or row[RENAMED]):
                if row[ORIGVALUE] is not None:
                    updated.setdefault(row[TAG], [])
                    updated[row[TAG]].append(
                        (decode(row[VALUE]), decode(row[ORIGVALUE])))
                else:
                    added.setdefault(row[TAG], [])
                    added[row[TAG]].append(decode(row[VALUE]))
            if row[EDITED] and row[DELETED]:
                if row[ORIGVALUE] is not None:
                    deleted.setdefault(row[TAG], [])
                    deleted[row[TAG]].append(decode(row[ORIGVALUE]))

            if row[EDITED] and row[RENAMED] and not row[DELETED]:
                renamed.setdefault(row[TAG], [])
                renamed[row[TAG]].append(
                    (decode(row[ORIGTAG]), decode(row[VALUE]),
                     decode(row[ORIGVALUE])))

        was_changed = set()
        songs = self.__songinfo.songs
        win = WritingWindow(self, len(songs))
        win.show()
        for song in songs:
            if not song.valid() and not qltk.ConfirmAction(
                    self, _("Tag may not be accurate"),
                    _("<b>%s</b> changed while the program was running. "
                      "Saving without refreshing your library may "
                      "overwrite other changes to the song.\n\n"
                      "Save this song anyway?") %
                    util.escape(util.fsdecode(song("~basename")))).run():
                break

            changed = False
            for key, values in updated.iteritems():
                for (new_value, old_value) in values:
                    new_value = util.unescape(new_value)
                    if song.can_change(key):
                        if old_value is None:
                            song.add(key, new_value)
                        else:
                            song.change(key, old_value, new_value)
                        changed = True
            for key, values in added.iteritems():
                for value in values:
                    value = util.unescape(value)
                    if song.can_change(key):
                        song.add(key, value)
                        changed = True
            for key, values in deleted.iteritems():
                for value in values:
                    value = util.unescape(value)
                    if song.can_change(key) and key in song:
                        song.remove(key, value)
                        changed = True
            save_rename = []
            for new_tag, values in renamed.iteritems():
                for old_tag, new_value, old_value in values:
                    old_tag = util.unescape(old_tag)
                    old_value = util.unescape(old_value)
                    new_value = util.unescape(new_value)
                    if (song.can_change(new_tag) and song.can_change(old_tag)
                            and old_tag in song):
                        if not is_special(new_value):
                            song.remove(old_tag, old_value)
                            save_rename.append((new_tag, new_value))
                        elif is_missing(new_value):
                            value = strip_missing(old_value)
                            song.remove(old_tag, old_value)
                            save_rename.append((new_tag, new_value))
                        else:
                            save_rename.append((new_tag, song[old_tag]))
                            song.remove(old_tag, None)
                        changed = True
            for tag, value in save_rename:
                song.add(tag, value)

            if changed:
                try:
                    song.write()
                except:
                    util.print_exc()
                    qltk.ErrorMessage(
                        self, _("Unable to save song"),
                        _("Saving <b>%s</b> failed. The file "
                          "may be read-only, corrupted, or you "
                          "do not have permission to edit it.") %
                        (util.escape(fsdecode(song('~basename'))))).run()
                    library.reload(song, changed=was_changed)
                    break
                was_changed.add(song)

            if win.step():
                break

        win.destroy()
        library.changed(was_changed)
        for b in [save, revert]:
            b.set_sensitive(False)
示例#25
0
    def __init__(self, library, songs, parent=None):
        super(SongProperties, self).__init__(dialog=False)
        self.set_transient_for(qltk.get_top_parent(parent))

        default_width = 600
        config_suffix = ""
        if len(songs) <= 1:
            default_width -= 200
            config_suffix += "single"
        self.set_default_size(default_width, 400)

        self.enable_window_tracking("quodlibet_properties",
                                    size_suffix=config_suffix)

        self.auto_save_on_change = config.getboolean(
                'editing', 'auto_save_changes', False)

        paned = gtk.HPaned()
        notebook = qltk.Notebook()
        pages = []
        pages.extend([Ctr(self, library) for Ctr in
                      [EditTags, TagsFromPath, RenameFiles]])
        if len(songs) > 1:
            pages.append(TrackNumbers(self, library))
        for page in pages: notebook.append_page(page)
        self.set_border_width(12)

        fbasemodel = gtk.ListStore(object, str)
        fmodel = gtk.TreeModelSort(fbasemodel)
        fview = HintedTreeView(fmodel)
        fview.connect('button-press-event', self.__pre_selection_changed)
        fview.set_rules_hint(True)
        selection = fview.get_selection()
        selection.set_mode(gtk.SELECTION_MULTIPLE)
        self.__save = None

        if len(songs) > 1:
            render = gtk.CellRendererText()
            c1 = gtk.TreeViewColumn(_('File'), render, text=1)
            render.set_property('ellipsize', pango.ELLIPSIZE_END)
            c1.set_sort_column_id(1)
            fview.append_column(c1)
            sw = gtk.ScrolledWindow()
            sw.add(fview)
            sw.set_shadow_type(gtk.SHADOW_IN)
            sw.set_policy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC)
            sw.show_all()
            paned.pack1(sw, shrink=True, resize=True)

        # Invisible selections behave a little strangely. So, when
        # handling this selection, there's a lot of if len(model) == 1
        # checks that "hardcode" the first row being selected.

        for song in songs:
            fbasemodel.append(row=[song, util.fsdecode(song("~basename"))])

        self.connect_object('changed', SongProperties.__set_title, self)

        selection.select_all()
        paned.pack2(notebook, shrink=False, resize=True)

        csig = selection.connect('changed', self.__selection_changed)
        s1 = library.connect(
            'changed', self.__refresh, fbasemodel, fview)
        s2 = library.connect(
            'removed', self.__remove, fbasemodel, selection, csig)
        self.connect_object('destroy', library.disconnect, s1)
        self.connect_object('destroy', library.disconnect, s2)
        self.connect_object('changed', self.set_pending, None)

        self.emit('changed', songs)
        self.add(paned)
        paned.set_position(175)
        notebook.show()
        paned.show()
        self.show()