def go_to(self, path_to_go): # FIXME: be stricter here.. # assert util.is_fsnative(path_to_go) # FIXME: what about non-normalized paths? path_to_go = util.fsnative(path_to_go) model = self.get_model() # Find the top level row which has the largest common # path with the path we want to go to roots = dict([(p, i) for (i, p) in model.iterrows(None)]) head, tail = path_to_go, util.fsnative("") to_find = [] while head and head not in roots: new_head, tail = os.path.split(head) # this can happen for invalid paths on Windows if head == new_head: break head = new_head to_find.append(tail) if head not in roots: return start_iter = roots[head] # expand until we find the right directory or the last valid one # and select/scroll to it def search(view, model, iter_, to_find): tree_path = model.get_path(iter_) # we are where we want, select and scroll if not to_find: view.set_cursor(tree_path) view.scroll_to_cell(tree_path) return # expand the row view.expand_row(tree_path, False) next_ = to_find.pop(-1) for sub_iter, path in model.iterrows(iter_): if os.path.basename(path) == next_: search(view, model, sub_iter, to_find) break else: # we haven't found the right sub folder, select the parent # and stop search(view, model, iter_, []) search(self, model, start_iter, to_find)
def test_long_filename(s): if os.name == "nt": a = s.AudioFile({"title": "x" * 300, "~filename": u"C:\\f.mp3"}) path = FileFromPattern(u'C:\\foobar\\ä<title>\\<title>').format(a) s.failUnlessEqual(len(util.fsnative(path)), 3 + 6 + 1 + 255 + 1 + 255) path = FileFromPattern(u'äüö<title><title>').format(a) s.failUnlessEqual(len(util.fsnative(path)), 255) else: a = s.AudioFile({"title": "x" * 300, "~filename": "/f.mp3"}) path = FileFromPattern(u'/foobar/ä<title>/<title>').format(a) s.failUnlessEqual(len(util.fsnative(path)), 1 + 6 + 1 + 255 + 1 + 255) path = FileFromPattern(u'äüö<title><title>').format(a) s.failUnlessEqual(len(util.fsnative(path)), 255)
def __find_songs(self, selection): model, rows = selection.get_selected_rows() dirs = [model[row][0] for row in rows] songs = [] to_add = [] for dir in dirs: try: for file in filter(formats.filter, sorted(os.listdir(util.fsnative(dir)))): raw_path = os.path.join(dir, file) fn = normalize_path(raw_path, canonicalise=True) if fn in self.__glibrary: songs.append(self.__glibrary[fn]) elif fn not in self.__library: song = formats.MusicFile(fn) if song: to_add.append(song) songs.append(song) yield songs if fn in self.__library: song = self.__library[fn] if not song.valid(): self.__library.reload(song) if song in self.__library: songs.append(song) except OSError: pass self.__library.add(to_add) yield songs
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 = strip_win32_incompat_from_path(value) value = 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 = fsdecode(path) + ext if os.sep in value and not os.path.isabs(value): raise ValueError("Pattern is not rooted") return value
def filename(self): """a local filename equivalent to the URI""" if self.scheme != "file": raise ValueError("only the file scheme supports filenames") elif self.netloc: raise ValueError("only local files have filenames") else: return util.fsnative(url2pathname(self.path))
def __rename(self, library): model = self.view.get_model() win = WritingWindow(self, len(model)) win.show() was_changed = set() skip_all = False self.view.freeze_child_notify() # FIXME: encoding/decoding mess 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.ResponseType.CANCEL, _("_Continue"), Gtk.ResponseType.OK) msg = qltk.Message( Gtk.MessageType.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( fsdecode(oldname)), util.escape(fsdecode(newname))), buttons=Gtk.ButtonsType.NONE) msg.add_buttons(*buttons) msg.set_default_response(Gtk.ResponseType.OK) resp = msg.run() skip_all |= (resp == RESPONSE_SKIP_ALL) # Preserve old behavior: shift-click is Ignore All mods = Gdk.Display.get_default().get_pointer()[3] skip_all |= mods & Gdk.ModifierType.SHIFT_MASK library.reload(song, changed=was_changed) if resp != Gtk.ResponseType.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)
def scan_library(library, force): """Start the global library re-scan If `force` is True, reload all existing valid items. """ paths = get_scan_dirs() exclude = util.split_scan_dirs(config.get("library", "exclude")) exclude = [util.fsnative(e) for e in exclude] copool.add(library.rebuild, paths, force, exclude, cofuncid="library", funcid="library")
def __init__(self, dir, name, library=None): super(Playlist, self).__init__() self.__instances.append(self) if isinstance(name, unicode) and os.name != "nt": name = name.encode('utf-8') self.name = name self.dir = dir self.library = library self._list = HashedList() basename = self.quote(name) try: for line in file(os.path.join(self.dir, basename), "r"): line = util.fsnative(line.rstrip()) if line in library: self._list.append(library[line]) elif library and library.masked(line): self._list.append(line) except IOError: if self.name: self.write()
def __changed(self, selector, selection, label): model, rows = selection.get_selected_rows() files = [] if len(rows) < 2: count = len(model or []) else: count = len(rows) label.set_text(ngettext("%d song", "%d songs", count) % count) for row in rows: filename = util.fsnative(model[row][0]) if not os.path.exists(filename): pass elif filename in self.__library: file = self.__library[filename] if file("~#mtime") + 1. < mtime(filename): try: file.reload() except StandardError: pass files.append(file) else: files.append(formats.MusicFile(filename)) files = filter(None, files) if len(files) == 0: self.set_title("Ex Falso") elif len(files) == 1: self.set_title("%s - Ex Falso" % files[0].comma("title")) else: self.set_title("%s - Ex Falso" % (ngettext( "%(title)s and %(count)d more", "%(title)s and %(count)d more", len(files) - 1) % ({ 'title': files[0].comma("title"), 'count': len(files) - 1 }))) self.__library.add(files) self.emit('changed', files)
def scan(self, paths, exclude=[], cofuncid=None): added = [] exclude = [expanduser(path) for path in exclude if path] for fullpath in paths: print_d("Scanning %r." % fullpath, self) desc = _("Scanning %s") % (unexpand(fsdecode(fullpath))) with Task(_("Library"), desc) as task: if cofuncid: task.copool(cofuncid) fullpath = 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) # skip unknown file extensions if not formats.filter(fullfilename): continue 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
def __mkdir(self, button): model, paths = self.get_selection().get_selected_rows() if len(paths) != 1: return path = paths[0] directory = model[path][0] dir_ = GetStringDialog(None, _("New Folder"), _("Enter a name for the new folder:")).run() if not dir_: return dir_ = util.fsnative(dir_.decode('utf-8')) fullpath = os.path.realpath(os.path.join(directory, dir_)) try: os.makedirs(fullpath) except EnvironmentError, err: error = "<b>%s</b>: %s" % (err.filename, err.strerror) qltk.ErrorMessage(None, _("Unable to create folder"), error).run() return
def __init__(self, initial=None, filter=filesel_filter, folders=None): """ initial -- a path to a file which should be shown initially filter -- a function which filters paths shown in the file list folders -- list of shown folders in the directory tree """ super(FileSelector, self).__init__() self.__filter = filter if initial is not None: initial = util.fsnative(initial) if initial and os.path.isfile(initial): initial = os.path.dirname(initial) dirlist = DirectoryTree(initial, folders=folders) model = ObjectStore() filelist = AllTreeView(model) column = TreeViewColumn(_("Songs")) column.set_sizing(Gtk.TreeViewColumnSizing.AUTOSIZE) render = Gtk.CellRendererPixbuf() render.set_property('stock_id', Gtk.STOCK_FILE) render.props.xpad = 3 column.pack_start(render, False) render = Gtk.CellRendererText() column.pack_start(render, True) def cell_data(column, cell, model, iter_, userdata): value = model.get_value(iter_) cell.set_property('text', fsdecode(os.path.basename(value))) column.set_cell_data_func(render, cell_data) filelist.append_column(column) filelist.set_rules_hint(True) filelist.get_selection().set_mode(Gtk.SelectionMode.MULTIPLE) filelist.set_search_equal_func(search_func, False) # Allow to drag and drop files from outside filelist.enable_model_drag_dest([], Gdk.DragAction.COPY) filelist.drag_dest_add_uri_targets() filelist.connect('drag-data-received', self.__file_dropped) self.__sig = filelist.get_selection().connect('changed', self.__changed) dirlist.get_selection().connect('changed', self.__dir_selection_changed, filelist) dirlist.get_selection().emit('changed') def select_all_files(view, path, col, fileselection): view.expand_row(path, False) fileselection.select_all() dirlist.connect('row-activated', select_all_files, filelist.get_selection()) sw = ScrolledWindow() sw.add(dirlist) sw.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC) sw.set_shadow_type(Gtk.ShadowType.IN) self.pack1(sw, resize=True) sw = ScrolledWindow() sw.add(filelist) sw.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC) sw.set_shadow_type(Gtk.ShadowType.IN) self.pack2(sw, resize=True)
def __init__(self, initial=None, folders=None): """ initial -- the path to select/scroll to folders -- a list of paths to show in the tree view, None will result in a separator. """ model = ObjectTreeStore() super(DirectoryTree, self).__init__(model=model) if initial is not None: initial = util.fsnative(initial) column = TreeViewColumn(_("Folders")) column.set_sizing(Gtk.TreeViewColumnSizing.AUTOSIZE) render = Gtk.CellRendererPixbuf() render.set_property('stock_id', Gtk.STOCK_DIRECTORY) column.pack_start(render, False) render = Gtk.CellRendererText() column.pack_start(render, True) def cell_data(column, cell, model, iter_, userdata): value = model.get_value(iter_) if value is not None: text = fsdecode(os.path.basename(value) or value) cell.set_property('text', text) column.set_cell_data_func(render, cell_data) self.append_column(column) self.set_search_equal_func(search_func, True) if folders is None: folders = [] for path in folders: niter = model.append(None, [path]) if path is not None: model.append(niter, ["dummy"]) self.get_selection().set_mode(Gtk.SelectionMode.MULTIPLE) self.connect('test-expand-row', DirectoryTree.__expanded, model) self.set_row_separator_func( lambda model, iter_, data: model.get_value(iter_) is None, None) if initial: self.go_to(initial) menu = Gtk.Menu() m = qltk.MenuItem(_("_New Folder..."), Gtk.STOCK_NEW) m.connect('activate', self.__mkdir) menu.append(m) m = Gtk.ImageMenuItem.new_from_stock(Gtk.STOCK_DELETE, None) m.connect('activate', self.__rmdir) menu.append(m) m = Gtk.ImageMenuItem.new_from_stock(Gtk.STOCK_REFRESH, None) m.connect('activate', self.__refresh) menu.append(m) m = qltk.MenuItem(_("_Select All Subfolders"), Gtk.STOCK_DIRECTORY) m.connect('activate', self.__expand) menu.append(m) menu.show_all() self.connect_object('popup-menu', self.__popup_menu, menu)
def get_scan_dirs(): dirs = util.split_scan_dirs(config.get("settings", "scan")) return [util.fsnative(d) for d in dirs if d]