[meld] Prompt user for action when file changes externally (closes bgo#460201)



commit 7f882dafc9356d41c20d93904f1ad76bcee8c4c5
Author: Kai Willadsen <kai willadsen gmail com>
Date:   Tue Sep 27 07:11:59 2011 +1000

    Prompt user for action when file changes externally (closes bgo#460201)
    
    When we get a notification that a file has changed externally, we now
    check mtimes, maintaining both the last mtime we saw (to prevent
    duplicate notifications) and the mtime at which we loaded the file.
    
    This doesn't yet prompt the user to overwrite a changed file; they've
    already been prompted to reload, but we should still prompt for the
    overwrite.

 meld/filediff.py   |   38 ++++++++++++++++++++++++++++++
 meld/meldbuffer.py |   65 +++++++++++++++++++++++++++++++++++++++++++++++++++-
 2 files changed, 102 insertions(+), 1 deletions(-)
---
diff --git a/meld/filediff.py b/meld/filediff.py
index da2511d..159a916 100644
--- a/meld/filediff.py
+++ b/meld/filediff.py
@@ -30,6 +30,7 @@ from multiprocessing.pool import ThreadPool
 from gi.repository import GLib
 from gi.repository import Pango
 from gi.repository import GObject
+from gi.repository import Gio
 from gi.repository import Gdk
 from gi.repository import Gtk
 
@@ -1173,6 +1174,10 @@ class FileDiff(melddoc.MeldDoc, gnomeglade.Component):
             yield 1
         for b in self.textbuffer:
             self.undosequence.checkpoint(b)
+            b.data.update_mtime()
+            # FIXME: Keep signal ID and disconnect... sometime
+            b.data.connect('file-changed', self.notify_file_changed)
+
 
     def _diff_files(self, refresh=False):
         yield _("[%s] Computing differences") % self.label_text
@@ -1225,6 +1230,33 @@ class FileDiff(melddoc.MeldDoc, gnomeglade.Component):
         for i in self._diff_files():
             yield i
 
+    def notify_file_changed(self, data):
+        try:
+            pane = [b.data for b in self.textbuffer].index(data)
+        except ValueError:
+            # Notification for unknown buffer
+            return
+        gfile = Gio.File.new_for_path(data.filename)
+
+        primary = _("File %s changed on disk") % gfile.get_parse_name()
+        secondary = _("Do you want to reload the file?")
+        msgarea = self.msgarea_mgr[pane].new_from_text_and_icon(
+                        Gtk.STOCK_DIALOG_WARNING, primary, secondary)
+        msgarea.add_button(_("_Reload"), Gtk.ResponseType.ACCEPT)
+        msgarea.add_button(_("Hi_de"), Gtk.ResponseType.CLOSE)
+
+        def on_file_changed_response(msgarea, response_id, *args):
+            self.msgarea_mgr[pane].clear()
+            if response_id == Gtk.ResponseType.ACCEPT:
+                self.on_revert_activate()
+            else:
+                # TODO: Set a flag to indicate that the file has been changed
+                # but not reloaded, so that we can prompt for overwrite on save
+                pass
+
+        msgarea.connect("response", on_file_changed_response)
+        msgarea.show_all()
+
     def refresh_comparison(self):
         """Refresh the view by clearing and redoing all comparisons"""
         self._disconnect_buffer_handlers()
@@ -1589,6 +1621,11 @@ class FileDiff(melddoc.MeldDoc, gnomeglade.Component):
                 self.fileentry[pane].set_filename(bufdata.filename)
             else:
                 return False
+
+        if not bufdata.current_on_disk():
+            # TODO: The file has changed since we loaded, so require confirmation.
+            pass
+
         start, end = buf.get_bounds()
         text = text_type(buf.get_text(start, end, False), 'utf8')
         if bufdata.newlines:
@@ -1625,6 +1662,7 @@ class FileDiff(melddoc.MeldDoc, gnomeglade.Component):
         if self._save_text_to_filename(save_to, text):
             self.emit("file-changed", save_to)
             self.undosequence.checkpoint(buf)
+            bufdata.update_mtime()
             return True
         else:
             return False
diff --git a/meld/meldbuffer.py b/meld/meldbuffer.py
index 54966cd..d23d917 100644
--- a/meld/meldbuffer.py
+++ b/meld/meldbuffer.py
@@ -21,6 +21,9 @@ from __future__ import unicode_literals
 import sys
 from gettext import gettext as _
 
+from gi.repository import Gio
+from gi.repository import GLib
+from gi.repository import GObject
 from gi.repository import GtkSource
 
 from meld.util.compat import text_type
@@ -72,18 +75,29 @@ class MeldBuffer(GtkSource.Buffer):
         return it
 
 
-class MeldBufferData(object):
+class MeldBufferData(GObject.GObject):
+
+    __gsignals__ = {
+        str('file-changed'): (GObject.SignalFlags.RUN_FIRST, None, ()),
+    }
 
     def __init__(self, filename=None):
+        GObject.GObject.__init__(self)
         self.modified = False
         self.writable = True
         self.editable = True
+        self._monitor = None
+        self._mtime = None
+        self._disk_mtime = None
         self.filename = filename
         self.savefile = None
         self._label = filename
         self.encoding = None
         self.newlines = None
 
+    def __del__(self):
+        self._disconnect_monitor()
+
     def get_label(self):
         #TRANSLATORS: This is the label of a new, currently-unnamed file.
         return self._label or _("<unnamed>")
@@ -93,6 +107,55 @@ class MeldBufferData(object):
 
     label = property(get_label, set_label)
 
+    def _connect_monitor(self):
+        if self._filename:
+            monitor = Gio.File.new_for_path(self._filename).monitor_file(
+                Gio.FileMonitorFlags.NONE, None)
+            handler_id = monitor.connect('changed', self._handle_file_change)
+            self._monitor = monitor, handler_id
+
+    def _disconnect_monitor(self):
+        if self._monitor:
+            monitor, handler_id = self._monitor
+            monitor.disconnect(handler_id)
+            monitor.cancel()
+
+    def _query_mtime(self, gfile):
+        try:
+            time_query = ",".join((Gio.FILE_ATTRIBUTE_TIME_MODIFIED,
+                                   Gio.FILE_ATTRIBUTE_TIME_MODIFIED_USEC))
+            info = gfile.query_info(time_query, 0, None)
+        except GLib.GError:
+            return None
+        mtime = info.get_modification_time()
+        return (mtime.tv_sec, mtime.tv_usec)
+
+    def _handle_file_change(self, monitor, f, other_file, event_type):
+        mtime = self._query_mtime(f)
+        if self._disk_mtime and mtime > self._disk_mtime:
+            self.emit('file-changed')
+        self._disk_mtime = mtime
+
+    @property
+    def filename(self):
+        return self._filename
+
+    @filename.setter
+    def filename(self, value):
+        self._disconnect_monitor()
+        self._filename = value
+        self.update_mtime()
+        self._connect_monitor()
+
+    def update_mtime(self):
+        if self._filename:
+            gfile = Gio.File.new_for_path(self._filename)
+            self._disk_mtime = self._query_mtime(gfile)
+            self._mtime = self._disk_mtime
+
+    def current_on_disk(self):
+        return self._mtime == self._disk_mtime
+
 
 class BufferLines(object):
     """Gtk.TextBuffer shim with line-based access and optional filtering


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]