summary refs log tree commit diff stats
diff options
context:
space:
mode:
authornfnty <git@nfnty.se>2015-10-13 20:38:27 +0200
committernfnty <git@nfnty.se>2016-02-08 04:43:04 +0100
commit92ed0b31b775dfa25bf417520c830273142a185e (patch)
treecfd202de07276a797d6f0074d88f49f86ee5d446
parent6d24d8cdcd6ef1b004f7d6d92374b9297819b503 (diff)
downloadranger-92ed0b31b775dfa25bf417520c830273142a185e.tar.gz
VCS: Separate VCS Thread
-rw-r--r--ranger/container/directory.py16
-rw-r--r--ranger/ext/vcs/__init__.py2
-rw-r--r--ranger/ext/vcs/vcs.py149
-rw-r--r--ranger/gui/ui.py11
-rw-r--r--ranger/gui/widgets/browsercolumn.py6
-rw-r--r--ranger/gui/widgets/browserview.py3
-rw-r--r--ranger/gui/widgets/statusbar.py18
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