From e2ef77bbf4b992f454c840a0e8682e70bde0aa31 Mon Sep 17 00:00:00 2001 From: Frederic Back Date: Thu, 28 Sep 2006 08:57:12 +0000 Subject: * "merged" c_walker "branch" to trunk. git-svn-id: file:///home/lennart/svn/public/fring/trunk@41 d0d2c35f-0a1e-0410-abeb-dabff30a67ee --- TODO | 1 + src/fringlib/fringrenderer.py | 40 +++--- src/fringlib/fringui.py | 61 +++++---- src/fringlib/fringwalker.py | 290 ++++++++++++++++++++++++++++-------------- src/fringlib/launch.py | 7 +- 5 files changed, 259 insertions(+), 140 deletions(-) diff --git a/TODO b/TODO index af2ab5e..0c7a1f5 100644 --- a/TODO +++ b/TODO @@ -12,6 +12,7 @@ TODO [x] click on folder -> open it [x] show busy cursor while walking dir tree [x] do not show black backrgound when app starts up +[ ] don't reload the busy cursor when it's already set ========================= DISPLAY [x] display total in ring center diff --git a/src/fringlib/fringrenderer.py b/src/fringlib/fringrenderer.py index 1f27de0..0f71f8a 100644 --- a/src/fringlib/fringrenderer.py +++ b/src/fringlib/fringrenderer.py @@ -1,7 +1,6 @@ import cairo import pango from math import * -from fringwalker import sum_list from fringutil import * import os @@ -51,7 +50,11 @@ class FringRenderer: def draw_segment(self,ctx, ring, start_angle, end_angle, start_hue, end_hue, data, previouspath=""): - assert isinstance(data, sum_list) + if data is None: return + + dataname = data[0] + datacontents = data[1] + datasize = data[2] if ring == 0: self.lookup_data = [] @@ -63,24 +66,29 @@ class FringRenderer: CENTERX, CENTERY = self.WIDTH/2, self.HEIGHT/2 ctx.move_to(CENTERX, CENTERY) - n = len(data.data) + n = len(datacontents) i = 0 accumulated = 0 last = start_angle - for fn, d in data.data: + if datacontents is None: return + for fn, contents, d in datacontents: + + if contents == None: hasContents = False + else: hasContents = True + start = last - value = self._list_value(d) + value = d # third tuple element now *always* contains the size accumulated += value - - if data.the_sum == 0: continue - end = start_angle+(end_angle - start_angle)*1.0*accumulated/data.the_sum + + if datasize == 0: continue + end = start_angle+(end_angle - start_angle)*1.0*accumulated/datasize if end-start >= .01: p = previouspath+os.sep+fn - self.lookup_data[ring].append(Segment(isinstance(d, sum_list), p, value, start, end)) + self.lookup_data[ring].append( Segment(hasContents , p, value, start, end) ) v = start_hue + (end_hue-start_hue)*1.0*i/n color = self._choose_color(start_hue + (end_hue-start_hue)*1.0*i/n, ring) @@ -98,9 +106,9 @@ class FringRenderer: ctx.set_source_rgb(0, 0, 0) ctx.stroke() - if isinstance(d, sum_list) and ring+1 < self.RINGS_MAX: + if hasContents and ring+1 < self.RINGS_MAX: self.draw_segment(ctx, ring+1, start, end, v, - start_hue + (end_hue-start_hue)*1.0*(i+1)/n, d, + start_hue + (end_hue-start_hue)*1.0*(i+1)/n, (fn, contents, d), previouspath+os.sep+fn) r += self.RING_RADIUS/2 @@ -135,7 +143,7 @@ class FringRenderer: ctx.move_to(x+xmod,y) # draw the side link label - if isinstance(d, sum_list): + if hasContents: ctx.set_source_rgb(0,0,1) width,height = self._draw_centered_text(ctx, fn + "/", align_x) else: @@ -145,7 +153,7 @@ class FringRenderer: if align_x == 0: width *= -1 - self.__register_side_link(isinstance(d, sum_list),p,value,end-start,x,y+(height/2),x+(width),y-(height/2)) + self.__register_side_link(hasContents,p,value,end-start,x,y+(height/2),x+(width),y-(height/2)) # write disk usage on segments if self.RING_RADIUS >= ctx.text_extents("55%")[3]: @@ -161,7 +169,7 @@ class FringRenderer: if ring == 0: ctx.set_source_rgb(.3,.3,.3) - i = format_disk_space(data.the_sum) + i = format_disk_space(datasize) ctx.move_to(CENTERX, CENTERY) width,height = self._draw_centered_text(ctx, i[0], .5, 1 ) ctx.move_to(CENTERX, CENTERY+height) @@ -243,10 +251,6 @@ class FringRenderer: (t,p,v), (v,p,q))[int(h)] - def _list_value(self,l): - if isinstance(l, sum_list): - return l.the_sum - return l def _choose_color(self,v, ring): color = self.hsv2rgb(v, .61, 1-ring*0.1) diff --git a/src/fringlib/fringui.py b/src/fringlib/fringui.py index ca634d1..a6c606d 100644 --- a/src/fringlib/fringui.py +++ b/src/fringlib/fringui.py @@ -3,7 +3,7 @@ import cairo import sys import os -from fringwalker import FringWalker +from fringwalker import * from fringrenderer import FringRenderer from fringutil import * @@ -37,6 +37,14 @@ ui = """ + + + + + + + + @@ -56,7 +64,7 @@ ui = """ class UI( gtk.Window ): - def __init__(self, path): + def __init__(self, uri): self.busy_cursor = 0 self.backgroundColour = (1,1,1) self.data = None @@ -67,14 +75,15 @@ class UI( gtk.Window ): # create gui gtk.Window.__init__(self) self.set_title("fring"); - self.__init_gui(path) + self.__init_gui() # walk directory self.walker.connect("list-changed",self.__list_changed) self.walker.connect("finished", self.__walker_finished) - self.open_folder(path) + self.walker.connect("manually-stopped", lambda w: self.__show_busy_cursor(-1)) + self.open_folder(uri) - def __init_gui(self, path): + def __init_gui(self): # create menubar uimanager = gtk.UIManager() @@ -82,6 +91,7 @@ class UI( gtk.Window ): self.add_accel_group(accelgroup) ag_global = gtk.ActionGroup('ag_global') ag_global.add_actions([ + ('TreeWalker', None, "TreeWalker"), ('FRing', None, "_Folder"), ('View', None, "_View"), ('Help', None, "_Help"), @@ -91,12 +101,22 @@ class UI( gtk.Window ): ('Zoom In', gtk.STOCK_ZOOM_IN, "Zoom _In", "Up", "Zoom In", lambda w: self.zoom(w,0.2)), ('Zoom Out', gtk.STOCK_ZOOM_OUT, "Zoom _Out", "Down", "Zoom Out", lambda w: self.zoom(w,-0.2)), ('Save Image...', gtk.STOCK_SAVE_AS, "_Save Image...", None, "Save Image...", self.save_image), - ('Refresh', gtk.STOCK_REFRESH, "_Refresh Tree", "R", "Refresh Tree", lambda w: self.__set_path(self.path)), + ('Refresh', gtk.STOCK_REFRESH, "_Refresh Tree", "R", "Refresh Tree", lambda w: self.__set_uri(self.uri)), ('About', gtk.STOCK_ABOUT, "_About", None, "About", self.about_dialog), ]) action = gtk.ToggleAction("Show Hidden Files", "Show Hidden Files", None, None) + action.set_active(True) action.connect("toggled",self.__hidden_files_toggled) ag_global.add_action(action) + + # add a menu with radio buttons to choose the walking method + ag_global.add_radio_actions([ + ('Python', None, "Python", None, None, WALKER_CLASSIC), + ('Python gnomevfs', None, "Python gnomevfs", None, None, WALKER_GNOMEVFS), + ('c++', None, "c++", None, None, WALKER_CPP), + ], 1) + self.methodaction = ag_global.get_action("c++") + uimanager.insert_action_group(ag_global, 0) uimanager.add_ui_from_string(ui) menubar = uimanager.get_widget('/MenuBar') @@ -113,7 +133,8 @@ class UI( gtk.Window ): b = gtk.FileChooserButton('Select a Folder') b.set_action(gtk.FILE_CHOOSER_ACTION_SELECT_FOLDER) - b.connect("current-folder-changed", lambda w: self.__set_path(w.get_current_folder())) + b.set_local_only(False) + b.connect("current-folder-changed", lambda w: self.__set_uri(w.get_uri())) toolbar.pack_start( b, True ) self.filechooserbutton = b @@ -155,12 +176,9 @@ class UI( gtk.Window ): self.eventbox.connect("size-allocate",self.__on_resize) self.connect("delete_event", self.close) - - - def open_folder(self,path): - print "open",path + def open_folder(self,uri): # change folder by changing the chooser button - self.filechooserbutton.set_current_folder(path) + self.filechooserbutton.set_current_folder_uri(uri) def about_dialog(self,widget): d = gtk.AboutDialog() @@ -176,7 +194,7 @@ class UI( gtk.Window ): gtk.main_quit() def open_parent(self,widget): - p = os.path.split(self.path) + p = os.path.split(self.uri) p = os.path.join(p[:-1]) self.open_folder(p[0]) @@ -199,10 +217,9 @@ class UI( gtk.Window ): self.renderer.HEIGHT = self.height self.renderer.INNER_RADIUS = self.height/12 self.renderer.RING_RADIUS = self.height/10 - #self.renderer.INNER_RADIUS *= self.zoomfactor self.renderer.RING_RADIUS *= self.zoomfactor self.renderer.RINGS_MAX = 3 - self.renderer.draw_segment(self.ctx, 0, 0, 1, 0, 1, self.data, self.path) + self.renderer.draw_segment(self.ctx, 0, 0, 1, 0, 1, self.data, self.uri) self.image.queue_draw() self.__show_busy_cursor(-1) @@ -225,26 +242,26 @@ class UI( gtk.Window ): #----------------------------------------------------------- private methods - def __set_path(self,path): + def __set_uri(self,uri): """ Set a path and start parsing. Also used to refresh or reset. """ - self.path = path + self.uri = uri self.walker.stop() - if path is None: return - self.walker.walk(path); - self.__show_busy_cursor(1) + if uri: + self.walker.walk(uri,self.methodaction.get_current_value()) + self.__show_busy_cursor(1) def __selectfolder(self,widget): """ Open a dialog to select a folder """ d = gtk.FileChooserDialog(None, self, gtk.FILE_CHOOSER_ACTION_SELECT_FOLDER, (gtk.STOCK_CANCEL,0,gtk.STOCK_OK,1)) - if d.run() == 1: self.open_folder(d.get_current_folder()) + if d.run() == 1: self.open_folder(d.get_uri()) d.destroy() def __hidden_files_toggled(self,widget): if self.walker.showhidden == widget.get_active(): return self.walker.showhidden = widget.get_active() - self.__set_path(self.path) + self.__set_uri(self.uri) def __move_event(self, widget, event): diff --git a/src/fringlib/fringwalker.py b/src/fringlib/fringwalker.py index bb6195b..a341955 100644 --- a/src/fringlib/fringwalker.py +++ b/src/fringlib/fringwalker.py @@ -1,147 +1,239 @@ -import os, os.path, stat, sys, time +import os, os.path, stat, sys +import threading +import posixpath # instead of os.path for gnomevfs operations +import time + import gobject, gtk -from threading import Thread +from gnomevfs import * -class sum_list: - def __init__(self, l, name = None): +try: import fringtools +except: print 'Error: Could not find "fringtools" extension module' - self.the_sum = 0 - self.data = list(l) - self.name = name +WALKER_CLASSIC = 0 +WALKER_GNOMEVFS = 1 +WALKER_CPP = 2 - for fn, i in self.data: +def print_tree( treearray, tab=0 ): + """ An utility function to print out a tree array """ - if isinstance(i, sum_list): - self.the_sum += i.the_sum - else: - self.the_sum += i + fn, data, size = treearray + print " "*tab,"%s (%i)"%(fn,size) + if not data: return + if tab > 1: return + for e in data: + print_tree(e,tab+1) - def __str__(self): - return self.name+": "+str(self.data) - def sort(self): +def treearray_cmp_fn( a, b ): + """ a and b are tuples describing a directory tree. Compare first by directory + status, then by size, and finally by name. """ - def cmp_fn(a, b): - a_dir = isinstance(a[1], sum_list) - b_dir = isinstance(b[1], sum_list) + a_dir = a[1] != None + b_dir = b[1] != None - if a_dir and not b_dir: - return 1 - elif b_dir and not a_dir: - return -1 - elif a_dir: - return cmp(a[1].the_sum, b[1].the_sum) - else: - return cmp(a[1], b[1]) + if a_dir and not b_dir: + return 1 + elif b_dir and not a_dir: + return -1 + elif a_dir and b_dir: + return cmp(a[2], b[2]) # compare sizes + else: + return cmp(a[0], b[0]) # compare names - self.data.sort(cmp_fn) class FringWalker( gobject.GObject ): + """ Manages requests for walking directories + + Directory entries are represented by tuples of the following form: + (name, contents, size) + + name: the file name of the directory + contents: a list of entries within the entry. If None, the entry is not a directory + size: the size of the entry. + """ __gsignals__ = { 'list-changed': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (gobject.TYPE_PYOBJECT,)), 'finished': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (gobject.TYPE_PYOBJECT,)), + 'manually-stopped': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, () ), 'progress': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (gobject.TYPE_INT,gobject.TYPE_INT)), } def __init__(self): gobject.GObject.__init__(self) self.thread = None - self.stopsignal = False self.showhidden = False - def walk(self,path): - self.thread = Thread(None,self._parse,None,(path,)) - self.stopsignal = False - print "start thread (%s)"%path + def walk(self,uri,method=WALKER_GNOMEVFS): + self.stop() + self.thread = WalkThread(self,uri,self.showhidden,method) self.thread.start() def stop(self): - if self.thread is None: - return - - if not self.thread.isAlive(): + if self.thread: + self.emit("manually-stopped") + self.thread.stopsignal = True self.thread = None - return - - self.stopsignal = True - print "stopping thread:" - #self.thread.join() - print "ok" - - def _parse(self,path): - """ Parse the root directory """ - def progress_fn(c, l, r): - # emit signals + def _progress_fn(self, walkthread, c, l, r): + # only emit if called from the current request + if walkthread == self.thread: gtk.gdk.threads_enter() self.emit("progress", c, l) self.emit("list-changed", r) gtk.gdk.threads_leave() - - r = self._build_tree(path, progress_fn) - gtk.gdk.threads_enter() - self.emit("finished", r) - gtk.gdk.threads_leave() + def _finished_fn(self, walkthread, r): + if walkthread == self.thread: + gtk.gdk.threads_enter() + self.emit("finished", r) + gtk.gdk.threads_leave() + self.thread = None - print "finished walking", path - def _build_tree(self, path, progress_fn = None): - """ Parse directories recursively """ - - ret = [] - tmp_dirs = [] - +class WalkThread( threading.Thread ): + """ A separate class for the thread. """ + + + def __init__(self, master, uri, showhidden, method=WALKER_CLASSIC): + """ Parameters: A FringWalker instance, a string with the path and a bool """ + threading.Thread.__init__(self) + self.stopsignal = False + self.master = master + self.uri = URI(uri) + self.showhidden = showhidden + self.method = method + + def __uri_tail(self, uri): + """ Return the tail (the filename) of a gnomevfs uri """ + f = format_uri_for_display(str(uri)) + f = posixpath.split( f )[-1] + return f + + def build_tree_python(self, path): + l = [] + total = 0 + for fn in os.listdir(path): + if self.stopsignal: return (None,None,0) + if not self.showhidden and fn[0] == ".": continue + try: p = os.path.join(path, fn) + except: continue + s = os.lstat(p) + if stat.S_ISDIR(s.st_mode): + sub = self.build_tree_python(p) + l.append( sub ) + total += sub[2] + elif stat.S_ISREG(s.st_mode): + l.append( (fn, None, s.st_size) ) + total += s.st_size + return (os.path.split(path)[-1], l, total) + + + + def build_tree_gnomevfs(self, uri): + + try: h = DirectoryHandle(uri) + except InvalidURIError: + print uri,"is not a valid uri, skipped" + return (str(uri), None, 0) + except NotFoundError: + print uri,"not found, skipped" + return (str(uri), None, 0) + + l = [] + total = 0 + d = h.next() try: - # walk files in directory - for fn in os.listdir(path): + while True: + if self.stopsignal: return (None,None,0) + d = h.next() + if not self.showhidden and d.name[0] == ".": continue - if self.stopsignal: - return sum_list([]) + if d.type == 2: # directory + sub = self.build_tree_gnomevfs(uri.append_path(d.name)) + l.append( sub ) + total += sub[2] + else: + l.append( (d.name, None, d.size) ) + total += d.size - if not self.showhidden and fn[0] == '.': - continue + except StopIteration: pass + return (self.__uri_tail(uri), l, total) - try: - p = os.path.join(path, fn) - s = os.lstat(p) - except: - continue - if stat.S_ISDIR(s.st_mode): - tmp_dirs.append(fn); - elif stat.S_ISREG(s.st_mode): - ret.append((fn, s.st_size)) - except OSError: - pass + def run(self): + """ Parse the root directory """ + # write some debug information + starttime = time.time() + print "start walking",self.uri, + if self.method == WALKER_CPP: print "(using c++ extension)" + elif self.method == WALKER_GNOMEVFS: print "(using python and gnomevfs)" + else: print "(using classic python)" + + # scan root directory first (using gnomevfs) + try: h = DirectoryHandle(self.uri) + except InvalidURIError: + print uri,"is not a valid uri, skipped" + return (str(self.uri), None, 0) + except NotFoundError: + print uri,"not found, skipped" + return (str(self.uri), None, 0) + + subdirectories = [] + l = [] + total = 0 + d = h.next() try: - c = 0 - - for fn in tmp_dirs: - c += 1 - - if self.stopsignal: - return sum_list([]) - - try: - p = os.path.join(path, fn) - except: - continue + while True: + if self.stopsignal: return + d = h.next() + if not self.showhidden and d.name[0] == ".": continue + + if d.type == 2: # directory + subdirectories.append( d.name ); + else: + l.append( (d.name, None, d.size) ) + total += d.size + except StopIteration: pass + + # emit an intermediate version to fill up the screen while waiting + self.master._progress_fn(self, + 0, len(subdirectories), + (self.__uri_tail(self.uri), l, total)) + + # now walk the subdirectories with the faster extension function + c = 0 + + for directory in subdirectories: + c += 1 + + if self.method == WALKER_CPP: + path = get_local_path_from_uri(str(self.uri))+os.sep+directory + sub = fringtools.build_tree(path, self.showhidden) + elif self.method == WALKER_GNOMEVFS: + uri = self.uri + sub = self.build_tree_gnomevfs(uri.append_path(directory)) + else: + path = get_local_path_from_uri(str(self.uri))+os.sep+directory + sub = self.build_tree_python(path) - ret.append((fn, self._build_tree(p))) + if self.stopsignal: return - if not (progress_fn is None): - r = sum_list(ret, os.path.split(path)[-1]) - r.sort() - progress_fn(c, len(tmp_dirs), r) - - except OSError: - pass + total += sub[2] + l.append( (directory,sub[1],sub[2]) ); - r = sum_list(ret, os.path.split(path)[-1]) - r.sort() + l.sort(treearray_cmp_fn) - return r + # emit an intermediate version after each directory + self.master._progress_fn(self, + c, len(subdirectories), + (self.__uri_tail(self.uri), l, total)) + + + l.sort(treearray_cmp_fn) + # emit final signal + self.master._finished_fn(self,(self.__uri_tail(self.uri), l, total)) + print "finished walking",self.uri,"(time=%s)"%round(time.time()-starttime,2) + diff --git a/src/fringlib/launch.py b/src/fringlib/launch.py index 8e3b107..f420768 100755 --- a/src/fringlib/launch.py +++ b/src/fringlib/launch.py @@ -17,11 +17,12 @@ # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 # USA. -# Copyright Lennart Poettering, 2006 +# Copyright Lennart Poettering, Frederic Back 2006 import sys, os import gtk import fringui +import gnomevfs if __name__ == "__main__": @@ -30,6 +31,10 @@ if __name__ == "__main__": else: tree_path = os.path.expanduser("~") + # first parameter can be a gnomevfs uri or a local path + #try: uri = URI(tree_path) + #except: uri = get_uri_from_local_path(tree_path) + ui = fringui.UI(tree_path) gtk.gdk.threads_init() -- cgit