summary refs log tree commit diff stats
diff options
context:
space:
mode:
-rw-r--r--ranger/core/main.py61
-rw-r--r--ranger/ext/vcs/vcs.py76
-rw-r--r--ranger/gui/ui.py2
3 files changed, 81 insertions, 58 deletions
diff --git a/ranger/core/main.py b/ranger/core/main.py
index cee698c7..281f89f5 100644
--- a/ranger/core/main.py
+++ b/ranger/core/main.py
@@ -97,7 +97,9 @@ def main(
         print("Inaccessible paths: %s" % paths)
         return 1
 
-    crash_traceback = None
+    profile = None
+    exit_msg = ''
+    exit_code = 0
     try:
         # Initialize objects
         fm = FM(paths=paths)
@@ -148,43 +150,50 @@ def main(
         if ranger.args.profile:
             import cProfile
             import pstats
-            profile = None
             ranger.__fm = fm  # pylint: disable=protected-access
-            cProfile.run('ranger.__fm.loop()', tempfile.gettempdir() + '/ranger_profile')
-            profile = pstats.Stats(tempfile.gettempdir() + '/ranger_profile', stream=sys.stderr)
+            profile_file = tempfile.gettempdir() + '/ranger_profile'
+            cProfile.run('ranger.__fm.loop()', profile_file)
+            profile = pstats.Stats(profile_file, stream=sys.stderr)
         else:
             fm.loop()
     except Exception:  # pylint: disable=broad-except
         import traceback
-        crash_traceback = traceback.format_exc()
-    except SystemExit as error:
-        return error.args[0]
+        exit_msg += '''\
+ranger version: {0}
+Python version: {1}
+Locale: {2}
+'''.format(ranger.__version__, sys.version.split()[0],
+           '.'.join(str(s) for s in locale.getlocale()))
+        try:
+            exit_msg += "Current file: '{0}'\n".format(fm.thisfile.path)
+        except AttributeError:
+            pass
+        exit_msg += '''
+{0}
+ranger crashed. Please report this traceback at:
+https://github.com/hut/ranger/issues
+'''.format(traceback.format_exc())
+        exit_code = 1
+
+    except SystemExit as ex:
+        if ex.code is not None:
+            if not isinstance(ex.code, int):
+                exit_msg = ex.code
+                exit_code = 1
+            else:
+                exit_code = ex.code
     finally:
-        if crash_traceback:
-            try:
-                filepath = fm.thisfile.path if fm.thisfile else "None"
-            except AttributeError:
-                filepath = "None"
         try:
             fm.ui.destroy()
         except (AttributeError, NameError):
             pass
+        # If profiler is enabled print the stats
         if ranger.args.profile and profile:
             profile.strip_dirs().sort_stats('cumulative').print_callees()
-        if crash_traceback:
-            print("ranger version: %s, executed with python %s" %
-                  (ranger.__version__, sys.version.split()[0]))
-            print("Locale: %s" % '.'.join(str(s) for s in locale.getlocale()))
-            try:
-                print("Current file: %s" % filepath)
-            except NameError:
-                pass
-            print(crash_traceback)
-            print("ranger crashed.  "
-                  "Please report this traceback at:")
-            print("https://github.com/hut/ranger/issues")
-            return 1  # pylint: disable=lost-exception
-        return 0  # pylint: disable=lost-exception
+        # print the exit message if any
+        if exit_msg:
+            sys.stderr.write(exit_msg)
+        return exit_code  # pylint: disable=lost-exception
 
 
 def parse_arguments():
diff --git a/ranger/ext/vcs/vcs.py b/ranger/ext/vcs/vcs.py
index 00f51152..64dec6a1 100644
--- a/ranger/ext/vcs/vcs.py
+++ b/ranger/ext/vcs/vcs.py
@@ -374,19 +374,20 @@ class VcsThread(threading.Thread):  # pylint: disable=too-many-instance-attribut
     def __init__(self, ui):
         super(VcsThread, self).__init__()
         self.daemon = True
-        self.ui = ui  # pylint: disable=invalid-name
-        self.queue = queue.Queue()
-        self.advance = threading.Event()
-        self.advance.set()
+        self._ui = ui
+        self._queue = queue.Queue()
+        self._stop = threading.Event()
+        self.stopped = threading.Event()
+        self._advance = threading.Event()
+        self._advance.set()
         self.paused = threading.Event()
-        self.awoken = threading.Event()
-        self.timestamp = time.time()
-        self.redraw = False
-        self.roots = set()
+        self._awoken = threading.Event()
+        self._redraw = False
+        self._roots = set()
 
     def _is_targeted(self, dirobj):
         """Check if dirobj is targeted"""
-        if self.ui.browser.main_column and self.ui.browser.main_column.target == dirobj:
+        if self._ui.browser.main_column and self._ui.browser.main_column.target == dirobj:
             return True
         return False
 
@@ -404,14 +405,14 @@ class VcsThread(threading.Thread):  # pylint: disable=too-many-instance-attribut
             if fsobj.vcs.is_root_pointer:
                 has_vcschild = True
                 if not rootvcs.rootinit and not self._is_targeted(rootvcs.obj):
-                    self.roots.add(rootvcs.path)
+                    self._roots.add(rootvcs.path)
                     if not rootvcs.init_root():
                         rootvcs.update_tree(purge=True)
-                    self.redraw = True
+                    self._redraw = True
                 if fsobj.is_link:
                     fsobj.vcsstatus = rootvcs.obj.vcsstatus
                     fsobj.vcsremotestatus = rootvcs.obj.vcsremotestatus
-                    self.redraw = True
+                    self._redraw = True
 
         return has_vcschild
 
@@ -419,11 +420,11 @@ class VcsThread(threading.Thread):  # pylint: disable=too-many-instance-attribut
         """Process queue"""
         dirobjs = []
         paths = set()
-        self.roots.clear()
+        self._roots.clear()
 
         while True:
             try:
-                dirobjs.append(self.queue.get(block=False))
+                dirobjs.append(self._queue.get(block=False))
             except queue.Empty:
                 break
 
@@ -435,55 +436,66 @@ class VcsThread(threading.Thread):  # pylint: disable=too-many-instance-attribut
             dirobj.vcs.reinit()
             if dirobj.vcs.track:
                 rootvcs = dirobj.vcs.rootvcs
-                if rootvcs.path not in self.roots and rootvcs.check_outdated():
-                    self.roots.add(rootvcs.path)
+                if rootvcs.path not in self._roots and rootvcs.check_outdated():
+                    self._roots.add(rootvcs.path)
                     if rootvcs.update_root():
                         rootvcs.update_tree()
                     else:
                         rootvcs.update_tree(purge=True)
-                    self.redraw = True
+                    self._redraw = True
 
             has_vcschild = self._update_subroots(dirobj.files_all)
 
             if dirobj.has_vcschild != has_vcschild:
                 dirobj.has_vcschild = has_vcschild
-                self.redraw = True
+                self._redraw = True
 
     def run(self):
         while True:
             self.paused.set()
-            self.advance.wait()
-            self.awoken.wait()
-            if not self.advance.isSet():
+            self._advance.wait()
+            self._awoken.wait()
+            if self._stop.isSet():
+                self.stopped.set()
+                return
+            if not self._advance.isSet():
                 continue
+            self._awoken.clear()
             self.paused.clear()
-            self.awoken.clear()
 
             try:
                 self._queue_process()
 
-                if self.redraw:
-                    self.redraw = False
-                    for column in self.ui.browser.columns:
+                if self._redraw:
+                    self._redraw = False
+                    for column in self._ui.browser.columns:
                         if column.target and column.target.is_directory:
                             column.need_redraw = True
-                    self.ui.status.need_redraw = True
-                    self.ui.redraw()
+                    self._ui.status.need_redraw = True
+                    self._ui.redraw()
             except Exception as ex:  # pylint: disable=broad-except
-                self.ui.fm.notify('VCS Exception: View log for more info', bad=True, exception=ex)
+                self._ui.fm.notify('VCS Exception: View log for more info', bad=True, exception=ex)
+
+    def stop(self):
+        """Stop thread synchronously"""
+        self._stop.set()
+        self.paused.wait()
+        self._advance.set()
+        self._awoken.set()
+        self.stopped.wait()
 
     def pause(self):
         """Pause thread"""
-        self.advance.clear()
+        self._advance.clear()
 
     def unpause(self):
         """Unpause thread"""
-        self.advance.set()
+        self._advance.set()
 
     def process(self, dirobj):
         """Process dirobj"""
-        self.queue.put(dirobj)
-        self.awoken.set()
+        self._queue.put(dirobj)
+        self._awoken.set()
 
 
 # Backend imports
diff --git a/ranger/gui/ui.py b/ranger/gui/ui.py
index 6a2f3c6c..c3bc8539 100644
--- a/ranger/gui/ui.py
+++ b/ranger/gui/ui.py
@@ -164,6 +164,8 @@ class UI(  # pylint: disable=too-many-instance-attributes,too-many-public-method
 
     def destroy(self):
         """Destroy all widgets and turn off curses"""
+        if 'vcsthread' in self.__dict__:
+            self.vcsthread.stop()
         DisplayableContainer.destroy(self)
 
         # Restore tmux setting `automatic-rename`