summary refs log tree commit diff stats
path: root/ranger
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 /ranger
parent6d24d8cdcd6ef1b004f7d6d92374b9297819b503 (diff)
downloadranger-92ed0b31b775dfa25bf417520c830273142a185e.tar.gz
VCS: Separate VCS Thread
Diffstat (limited to 'ranger')
-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
/a>
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132