diff options
author | nfnty <git@nfnty.se> | 2015-10-13 20:38:27 +0200 |
---|---|---|
committer | nfnty <git@nfnty.se> | 2016-02-08 04:43:04 +0100 |
commit | 92ed0b31b775dfa25bf417520c830273142a185e (patch) | |
tree | cfd202de07276a797d6f0074d88f49f86ee5d446 | |
parent | 6d24d8cdcd6ef1b004f7d6d92374b9297819b503 (diff) | |
download | ranger-92ed0b31b775dfa25bf417520c830273142a185e.tar.gz |
VCS: Separate VCS Thread
-rw-r--r-- | ranger/container/directory.py | 16 | ||||
-rw-r--r-- | ranger/ext/vcs/__init__.py | 2 | ||||
-rw-r--r-- | ranger/ext/vcs/vcs.py | 149 | ||||
-rw-r--r-- | ranger/gui/ui.py | 11 | ||||
-rw-r--r-- | ranger/gui/widgets/browsercolumn.py | 6 | ||||
-rw-r--r-- | ranger/gui/widgets/browserview.py | 3 | ||||
-rw-r--r-- | ranger/gui/widgets/statusbar.py | 18 |
7 files changed, 160 insertions, 45 deletions
diff --git a/ranger/container/directory.py b/ranger/container/directory.py index 95dbd730..9725ae45 100644 --- a/ranger/container/directory.py +++ b/ranger/container/directory.py @@ -306,10 +306,9 @@ class Directory(FileSystemObject, Accumulator, Loadable): files = [] disk_usage = 0 - if self.vcs: - self.vcs.check() - if self.vcs.track: - self.vcs.update() + if self.vcs and self.vcs.track and not self.vcs.is_root: + self.vcspathstatus = self.vcs.get_status_subpath( + self.path, is_directory=True) for name in filenames: try: @@ -339,7 +338,8 @@ class Directory(FileSystemObject, Accumulator, Loadable): if item.vcs.is_root: self.has_vcschild = True else: - item.vcspathstatus = self.vcs.get_status_subpath(item.path) + item.vcspathstatus = self.vcs.get_status_subpath( + item.path, is_directory=True) else: item = File(name, preload=stats, path_is_abs=True, basename_is_rel_to=basename_is_rel_to) @@ -384,6 +384,8 @@ class Directory(FileSystemObject, Accumulator, Loadable): finally: self.loading = False self.fm.signal_emit("finished_loading_dir", directory=self) + if self.vcs: + self.fm.ui.vcsthread.wakeup() def unload(self): self.loading = False @@ -422,7 +424,6 @@ class Directory(FileSystemObject, Accumulator, Loadable): pass self.load_generator = None - def sort(self): """Sort the contained files""" if self.files_all is None: @@ -588,7 +589,8 @@ class Directory(FileSystemObject, Accumulator, Loadable): def load_content_if_outdated(self, *a, **k): """Load the contents of the directory if outdated""" - if self.load_content_once(*a, **k): return True + if self.load_content_once(*a, **k): + return True if self.files_all is None or self.content_outdated: self.load_content(*a, **k) diff --git a/ranger/ext/vcs/__init__.py b/ranger/ext/vcs/__init__.py index 8adc11c6..c6c691b8 100644 --- a/ranger/ext/vcs/__init__.py +++ b/ranger/ext/vcs/__init__.py @@ -1,3 +1,3 @@ """VCS Extension Package""" -from .vcs import Vcs, VcsError +from .vcs import Vcs, VcsError, VcsThread diff --git a/ranger/ext/vcs/vcs.py b/ranger/ext/vcs/vcs.py index 1bd101ed..dd74ff82 100644 --- a/ranger/ext/vcs/vcs.py +++ b/ranger/ext/vcs/vcs.py @@ -8,6 +8,7 @@ import os import subprocess +import threading class VcsError(Exception): """Vcs exception""" @@ -82,26 +83,35 @@ class Vcs(object): if self.is_root: self.__class__ = getattr(getattr(ranger.ext.vcs, self.repotype), self.REPOTYPES[self.repotype]['class']) + self.rootvcs = self self.status_subpaths = {} - self.track = True - self.initiated = False - self.head = self.get_info(self.HEAD) - self.branch = self.get_branch() - self.remotestatus = self.get_status_remote() - self.obj.vcspathstatus = self.get_status_root_cheap() - - if not os.access(self.repodir, os.R_OK): + self.in_repodir = False + try: + self.head = self.get_info(self.HEAD) + self.branch = self.get_branch() + self.remotestatus = self.get_status_remote() + self.obj.vcspathstatus = self.get_status_root_cheap() + except VcsError: + return + + if os.access(self.repodir, os.R_OK): + self.track = True + else: self.track = False directoryobject.vcspathstatus = 'unknown' self.remotestatus = 'unknown' else: + self.rootvcs = directoryobject.fm.get_directory(self.root).vcs # Do not track self.repodir or its subpaths if self.path == self.repodir or self.path.startswith(self.repodir + '/'): self.track = False + self.in_repodir = True else: - self.track = directoryobject.fm.get_directory(self.root).vcs.track + self.track = self.rootvcs.track + self.in_repodir = False else: self.track = False + self.in_repodir = False # Auxiliar #--------------------------- @@ -144,22 +154,65 @@ class Vcs(object): def check(self): """Check repository health""" - if (self.track and not os.path.exists(self.repodir)) \ - or not self.track: + if not self.in_repodir \ + and (not self.track or (not self.is_root and self.get_repotype(self.path)[0])): self.__init__(self.obj) + return True + elif self.track and not os.path.exists(self.repodir): + self.update_tree(purge=True) + return False - def update(self): + def update_root(self): """Update repository""" - root = self.obj.fm.get_directory(self.root).vcs - root.head = root.get_info(self.HEAD) - root.branch = root.get_branch() - root.status_subpaths = root.get_status_subpaths() - root.remotestatus = root.get_status_remote() - root.obj.vcspathstatus = root.get_status_root() - - if not self.is_root: - self.obj.vcspathstatus = root.get_status_subpath( - self.path, is_directory=True) + try: + self.rootvcs.head = self.rootvcs.get_info(self.HEAD) + self.rootvcs.branch = self.rootvcs.get_branch() + self.rootvcs.status_subpaths = self.rootvcs.get_status_subpaths() + self.rootvcs.remotestatus = self.rootvcs.get_status_remote() + self.rootvcs.obj.vcspathstatus = self.rootvcs.get_status_root() + except VcsError: + self.update_tree(purge=True) + + def update_tree(self, purge=False): + """Update repository tree""" + for wroot, wdirs, _ in os.walk(self.rootvcs.path): + # Only update loaded directories + try: + wroot_obj = self.obj.fm.directories[wroot] + except KeyError: + wdirs[:] = [] + continue + if wroot_obj.content_loaded: + for fileobj in wroot_obj.files_all: + if purge: + if fileobj.is_directory: + fileobj.vcspathstatus = None + fileobj.vcs.__init__(fileobj) + else: + fileobj.vcspathstatus = None + continue + + if fileobj.is_directory: + fileobj.vcs.check() + if not fileobj.vcs.track: + continue + if not fileobj.vcs.is_root: + fileobj.vcspathstatus = self.rootvcs.get_status_subpath( + fileobj.path, is_directory=True) + else: + fileobj.vcspathstatus = self.rootvcs.get_status_subpath(fileobj.path) + + # Remove dead directories + for wdir in wdirs.copy(): + try: + wdir_obj = self.obj.fm.directories[os.path.join(wroot, wdir)] + except KeyError: + wdirs.remove(wdir) + continue + if wdir_obj.vcs.is_root or not wdir_obj.vcs.track: + wdirs.remove(wdir) + if purge: + self.rootvcs.__init__(self.rootvcs.obj) # Repo creation #--------------------------- @@ -248,19 +301,18 @@ class Vcs(object): def get_status_subpath(self, path, is_directory=False): """Returns the status of path""" - root = self.obj.fm.get_directory(self.root).vcs relpath = os.path.relpath(path, self.root) # check if relpath or its parents has a status tmppath = relpath while tmppath: - if tmppath in root.status_subpaths: - return root.status_subpaths[tmppath] + if tmppath in self.rootvcs.status_subpaths: + return self.rootvcs.status_subpaths[tmppath] tmppath = os.path.dirname(tmppath) # check if path contains some file in status if is_directory: - statuses = set(status for subpath, status in root.status_subpaths.items() + statuses = set(status for subpath, status in self.rootvcs.status_subpaths.items() if subpath.startswith(relpath + '/')) for status in self.DIR_STATUS: if status in statuses: @@ -308,6 +360,51 @@ class Vcs(object): """Gets a list of files in revision rev""" raise NotImplementedError +class VcsThread(threading.Thread): + """Vcs thread""" + def __init__(self, ui, idle_delay): + super(VcsThread, self).__init__(daemon=True) + self.ui = ui + self.delay = idle_delay / 1000 + self.wake = threading.Event() + + def run(self): + # Set for already updated roots + roots = set() + redraw = False + while True: + for column in self.ui.browser.columns: + target = column.target + if target and target.is_directory and target.vcs: + # Redraw if tree is purged + if not target.vcs.check(): + redraw = True + if target.vcs.track and not target.vcs.root in roots: + roots.add(target.vcs.root) + # Do not update repo when repodir is displayed (causes strobing) + if tuple(clmn for clmn in self.ui.browser.columns + if clmn.target + and (clmn.target.path == target.vcs.repodir or + clmn.target.path.startswith(target.vcs.repodir + '/'))): + continue + target.vcs.update_root() + target.vcs.update_tree() + redraw = True + if redraw: + redraw = False + for column in self.ui.browser.columns: + column.need_redraw = True + self.ui.status.need_redraw = True + self.ui.redraw() + roots.clear() + + self.wake.clear() + self.wake.wait(timeout=self.delay) + + def wakeup(self): + """Wakeup thread""" + self.wake.set() + import ranger.ext.vcs.git import ranger.ext.vcs.hg import ranger.ext.vcs.bzr diff --git a/ranger/gui/ui.py b/ranger/gui/ui.py index 2eacbc4d..f1e1ebbb 100644 --- a/ranger/gui/ui.py +++ b/ranger/gui/ui.py @@ -5,6 +5,7 @@ import os import sys import curses import _curses +import threading from .displayable import DisplayableContainer from .mouse_event import MouseEvent @@ -40,6 +41,7 @@ class UI(DisplayableContainer): def __init__(self, env=None, fm=None): self.keybuffer = KeyBuffer() self.keymaps = KeyMaps(self.keybuffer) + self.redrawlock = threading.Event() if fm is not None: self.fm = fm @@ -218,6 +220,7 @@ class UI(DisplayableContainer): from ranger.gui.widgets.statusbar import StatusBar from ranger.gui.widgets.taskview import TaskView from ranger.gui.widgets.pager import Pager + from ranger.ext.vcs import VcsThread # Create a title bar self.titlebar = TitleBar(self.win) @@ -248,8 +251,15 @@ class UI(DisplayableContainer): self.pager.visible = False self.add_child(self.pager) + # Create VCS thread + self.vcsthread = VcsThread(self, self.settings.idle_delay) + self.vcsthread.start() + def redraw(self): """Redraw all widgets""" + if self.redrawlock.is_set(): + return + self.redrawlock.set() self.poke() # determine which widgets are shown @@ -264,6 +274,7 @@ class UI(DisplayableContainer): self.draw() self.finalize() + self.redrawlock.clear() def redraw_window(self): """Redraw the window. This only calls self.win.redrawwin().""" diff --git a/ranger/gui/widgets/browsercolumn.py b/ranger/gui/widgets/browsercolumn.py index 40f804ba..8961f48f 100644 --- a/ranger/gui/widgets/browsercolumn.py +++ b/ranger/gui/widgets/browsercolumn.py @@ -150,8 +150,8 @@ class BrowserColumn(Pager): self.old_thisfile = self.target.pointed_obj if self.target.load_content_if_outdated() \ - or self.target.sort_if_outdated() \ - or self.last_redraw_time < self.target.last_update_time: + or self.target.sort_if_outdated() \ + or self.last_redraw_time < self.target.last_update_time: self.need_redraw = True if self.need_redraw: @@ -258,7 +258,7 @@ class BrowserColumn(Pager): key = (self.wid, selected_i == i, drawn.marked, self.main_column, drawn.path in copied, tagged_marker, drawn.infostring, drawn.vcspathstatus, - drawn.vcs.remotestatus if drawn.is_directory and drawn.vcs and drawn.vcs.is_root else None, + drawn.vcs.remotestatus if drawn.is_directory and drawn.vcs and drawn.vcs.track and drawn.vcs.is_root else None, self.fm.do_cut, current_linemode.name, metakey) diff --git a/ranger/gui/widgets/browserview.py b/ranger/gui/widgets/browserview.py index e9640208..56b9eba7 100644 --- a/ranger/gui/widgets/browserview.py +++ b/ranger/gui/widgets/browserview.py @@ -21,6 +21,7 @@ class BrowserView(Widget, DisplayableContainer): old_collapse = False draw_hints = False draw_info = False + vcsthread = None def __init__(self, win, ratios, preview = True): DisplayableContainer.__init__(self, win) @@ -91,6 +92,8 @@ class BrowserView(Widget, DisplayableContainer): self.need_redraw = True self.need_clear = False for tab in self.fm.tabs.values(): + if tab == self.fm.thistab: + continue directory = tab.thisdir if directory: directory.load_content_if_outdated() diff --git a/ranger/gui/widgets/statusbar.py b/ranger/gui/widgets/statusbar.py index b5e3b178..538757fd 100644 --- a/ranger/gui/widgets/statusbar.py +++ b/ranger/gui/widgets/statusbar.py @@ -93,6 +93,9 @@ class StatusBar(Widget): self.old_ctime = ctime self.need_redraw = True + if self.fm.thisdir.vcs and self.fm.thisdir.vcs.track: + self.need_redraw = True + if self.need_redraw: self.need_redraw = False @@ -184,24 +187,23 @@ class StatusBar(Widget): directory = target if target.is_directory else \ target.fm.get_directory(os.path.dirname(target.path)) if directory.vcs and directory.vcs.track: - vcsroot = directory.fm.get_directory(directory.vcs.root).vcs - if vcsroot.branch: - vcsinfo = '({0:s}: {1:s})'.format(vcsroot.repotype, vcsroot.branch) + if directory.vcs.rootvcs.branch: + vcsinfo = '({0:s}: {1:s})'.format(directory.vcs.rootvcs.repotype, directory.vcs.rootvcs.branch) else: - vcsinfo = '({0:s})'.format(vcsroot.repotype) + vcsinfo = '({0:s})'.format(directory.vcs.rootvcs.repotype) left.add_space() left.add(vcsinfo, 'vcsinfo') left.add_space() - if vcsroot.remotestatus: - vcsstr, vcscol = self.vcsremotestatus_symb[vcsroot.remotestatus] + if directory.vcs.rootvcs.remotestatus: + vcsstr, vcscol = self.vcsremotestatus_symb[directory.vcs.rootvcs.remotestatus] left.add(vcsstr.strip(), 'vcsremote', *vcscol) if target.vcspathstatus: vcsstr, vcscol = self.vcspathstatus_symb[target.vcspathstatus] left.add(vcsstr.strip(), 'vcsfile', *vcscol) - if vcsroot.head: + if directory.vcs.rootvcs.head: left.add_space() - left.add('{0:s}'.format(vcsroot.head['summary']), 'vcscommit') + left.add('{0:s}'.format(directory.vcs.rootvcs.head['summary']), 'vcscommit') def _get_owner(self, target): uid = target.stat.st_uid |