Replace all mentions of /usr/bin/python2.6 with /usr/bin/python2
[time-slider.git] / usr / share / time-slider / lib / time_slider / fileversion.py
1 #!/usr/bin/python2
2 #
3 # CDDL HEADER START
4 #
5 # The contents of this file are subject to the terms of the
6 # Common Development and Distribution License (the "License").
7 # You may not use this file except in compliance with the License.
8 #
9 # You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
10 # or http://www.opensolaris.org/os/licensing.
11 # See the License for the specific language governing permissions
12 # and limitations under the License.
13 #
14 # When distributing Covered Code, include this CDDL HEADER in each
15 # file and include the License file at usr/src/OPENSOLARIS.LICENSE.
16 # If applicable, add the following below this CDDL HEADER, with the
17 # fields enclosed by brackets "[]" replaced with your own identifying
18 # information: Portions Copyright [yyyy] [name of copyright owner]
19 #
20 # CDDL HEADER END
21 #
22
23 import time
24 import getopt
25 import os
26 import gio
27 import sys
28 import threading
29 import subprocess
30 import string
31 import gnomevfs
32 import gnome.ui
33 #import traceback
34
35 try:
36         import pygtk
37         pygtk.require("2.4")
38 except:
39         pass
40 try:
41         import gtk
42         import gtk.glade
43         gtk.gdk.threads_init()
44 except:
45         sys.exit(1)
46 try:
47         import gobject
48 except:
49         sys.exit(1)
50
51 # here we define the path constants so that other modules can use it.
52 # this allows us to get access to the shared files without having to
53 # know the actual location, we just use the location of the current
54 # file and use paths relative to that.
55 SHARED_FILES = os.path.abspath(os.path.join(os.path.dirname(__file__),
56         os.path.pardir,
57         os.path.pardir))
58 LOCALE_PATH = os.path.join('/usr', 'share', 'locale')
59 RESOURCE_PATH = os.path.join(SHARED_FILES, 'res')
60
61 # the name of the gettext domain. because we have our translation files
62 # not in a global folder this doesn't really matter, setting it to the
63 # application name is a good idea tough.
64 GETTEXT_DOMAIN = 'time-slider'
65
66 # set up the glade gettext system and locales
67 gtk.glade.bindtextdomain(GETTEXT_DOMAIN, LOCALE_PATH)
68 gtk.glade.textdomain(GETTEXT_DOMAIN)
69
70 KILOBYTES = 1024.0
71 MEGABYTES = KILOBYTES*1024
72 GIGABYTES = MEGABYTES*1024
73 TERABYTES = GIGABYTES*1024
74
75 class File:
76         displayTemplates = [
77                 (TERABYTES, '%0.1f TB'), 
78                 (GIGABYTES, '%0.1f GB'),
79                 (MEGABYTES, '%0.1f MB'),
80                 (KILOBYTES, '%0.1f KB'),
81                 (0, '%0.1f B'),]
82
83         def __init__(self, path):
84                 self.path = path
85                 self.file = gio.File (path)
86                 try:
87                         self.info = self.file.query_info ("*")
88                         self.exist = True
89                 except gio.Error:
90                         self.exist = False
91
92         def  get_icon (self):
93                 #try thumnailer first
94                 icon_factory = gnome.ui.ThumbnailFactory(gnome.ui.THUMBNAIL_SIZE_NORMAL)
95                 mtime = os.path.getmtime(self.path)
96                 uri = gnomevfs.make_uri_from_input(self.path)
97                 thumb =  icon_factory.lookup (uri, mtime)
98                 if thumb:
99                   return gtk.gdk.pixbuf_new_from_file (thumb)
100                 thumb = icon_factory.generate_thumbnail (uri, self.info.get_content_type())
101                 if thumb:
102                   icon_factory.save_thumbnail (thumb, uri, mtime)
103                   return thumb
104                  #fallback get the themed icon
105                 return gtk.icon_theme_get_default().choose_icon (self.info.get_icon().get_property ("names"), 48,  gtk.ICON_LOOKUP_USE_BUILTIN).load_icon ()
106
107         def  get_size (self):
108                 amount = self.info.get_size ()
109                 for treshold, template in self.displayTemplates:
110                         if amount > treshold:
111                                 if treshold:
112                                         amount = amount /treshold
113                                 return "%s (%d bytes)" % (template % amount, self.info.get_size ())
114                 return "%d byte" % amount
115
116         def add_if_unique (self, versions):
117                 found = False
118                 for file in versions:
119                         if int (file.info.get_modification_time ()) == int (self.info.get_modification_time ()):
120                                 found = True
121                 if not found:
122                         versions.append (self)
123                         return True
124                 return False
125
126         def get_mime_type (self):
127                 return gnomevfs.get_mime_type(gnomevfs.make_uri_from_input(self.path))
128
129
130 ( COLUMN_ICON,
131   COLUMN_NAME,
132   COLUMN_STRING_DATE,
133   COLUMN_DATE,
134   COLUMN_SIZE
135 ) = range (5)
136
137
138
139 class FileVersionWindow:
140         meld_hint_displayed = False
141
142         def __init__(self, snap_path, file):
143                 self.snap_path = snap_path
144                 self.filename = file
145                 self.xml = gtk.glade.XML("%s/../../glade/time-slider-version.glade" \
146                         % (os.path.dirname(__file__)))
147                 self.window = self.xml.get_widget("toplevel")
148                 self.progress = self.xml.get_widget("progress")
149                 self.version_label = self.xml.get_widget("num_versions_label")
150                 # signal dictionary
151                 dic = {"on_toplevel_delete_event": self.exit3 ,
152                         "on_close_clicked": self.exit ,
153                         "on_compare_button_clicked": self.on_compare_button_clicked,
154                         "on_current_file_button_clicked": self.on_current_file_button_clicked,
155                         "on_treeview_row_activated": self.on_treeview_row_activated,
156                         "on_treeview_cursor_changed": self.on_treeview_cursor_changed}
157                 self.xml.signal_autoconnect(dic)
158                         
159                 self.filename_label = self.xml.get_widget("filename_label")
160                 self.size_label = self.xml.get_widget("size_label")
161                 self.date_label = self.xml.get_widget("date_label")
162                 self.older_versions_label = self.xml.get_widget("older_versions_label")
163                 self.compare_button = self.xml.get_widget("compare_button")
164                 self.button_init = False
165
166                 self.window.show ()
167
168                 self.file = File (file)
169                 self.filename_label.set_text (self.file.info.get_name ())
170                 self.size_label.set_text (self.file.get_size ())
171                 self.date_label.set_text (time.strftime ("%d/%m/%y %Hh%Ms%S", time.localtime(self.file.info.get_modification_time ())))
172                 self.xml.get_widget("icon_image").set_from_pixbuf (self.file.get_icon ())
173
174                 self.treeview = self.xml.get_widget("treeview")
175                 self.model = gtk.ListStore(gtk.gdk.Pixbuf,
176                                            gobject.TYPE_STRING,
177                                            gobject.TYPE_STRING,
178                                            gobject.TYPE_STRING,
179                                            gobject.TYPE_STRING)
180
181                 self.treeview.set_model (self.model)
182                 self.__add_columns (self.treeview)
183
184                 self.scanner = VersionScanner (self)
185                 self.scanner.start()
186
187         def __add_columns(self, treeview):
188                 model = treeview.get_model()
189
190                 renderer = gtk.CellRendererPixbuf()
191                 column = gtk.TreeViewColumn('Icon', renderer, pixbuf=COLUMN_ICON)
192                 treeview.append_column(column)
193
194                 self.date_column = gtk.TreeViewColumn('Last Modified Date', gtk.CellRendererText(),
195                         text=COLUMN_STRING_DATE)
196                 self.date_column.set_sort_column_id(COLUMN_DATE)
197                 treeview.append_column(self.date_column)
198
199                 # column for description
200                 column = gtk.TreeViewColumn('Size', gtk.CellRendererText(),
201                         text=COLUMN_SIZE)
202                 column.set_sort_column_id(COLUMN_SIZE)
203                 treeview.append_column(column)
204
205         def add_file (self, file):
206                 iter = self.model.append ()
207                 self.model.set (iter, 
208                 COLUMN_ICON, file.get_icon (),
209                 COLUMN_NAME, file.path,
210                 COLUMN_STRING_DATE, time.strftime ("%d/%m/%y %Hh%Ms%S", time.localtime(file.info.get_modification_time ())),
211                 COLUMN_DATE, file.info.get_modification_time (),
212                 COLUMN_SIZE, file.get_size ())
213
214         def exit3 (self, blah, blih):
215                 self.exit (self)
216
217         def exit (self, blah):
218                 self.scanner.join ()
219                 gtk.main_quit ()
220                 
221         def on_current_file_button_clicked (self, widget):
222                 application = gnomevfs.mime_get_default_application (gnomevfs.get_mime_type(gnomevfs.make_uri_from_input(self.filename)))
223                 if application:
224                         subprocess.Popen (str.split (application[2]) + [self.filename])
225
226         def on_treeview_row_activated (self, treeview, path, column):
227                 (model, iter) = treeview.get_selection ().get_selected ()
228                 filename = model.get (iter, 1)[0]
229                 application = gnomevfs.mime_get_default_application (gnomevfs.get_mime_type(gnomevfs.make_uri_from_input(filename)))
230                 if application:
231                         subprocess.Popen (str.split (application[2]) + [filename])
232
233         def on_treeview_cursor_changed (self, treeview):
234                 if not self.button_init:
235                         self.button_init = True
236                         if self.file.get_mime_type ().find ("text") != -1 :
237                                 self.compare_button.set_sensitive (True)
238
239         def on_compare_button_clicked (self, widget):
240                 (model, iter) = self.treeview.get_selection ().get_selected ()
241                 filename = model.get (iter, 1)[0]
242                 if os.path.exists ("/usr/bin/meld"):
243                   subprocess.Popen (["/usr/bin/meld",self.filename, filename])
244                 else:
245                   if not self.meld_hint_displayed:
246                     dialog = gtk.MessageDialog(None, 0, gtk.MESSAGE_INFO, gtk.BUTTONS_CLOSE, _("Hint"))
247                     dialog.set_title (_("Hint"))
248                     dialog.format_secondary_text(_("Installing the optional meld package will enhance the file comparison visualization"))
249                     dialog.run ()
250                     dialog.destroy ()
251                     self.meld_hint_displayed = True
252                   p1 = subprocess.Popen(["/usr/bin/diff", "-u", self.filename, filename], stdout=subprocess.PIPE)
253                   p2 = subprocess.Popen(str.split ("/usr/bin/zenity --text-info --editable"), stdin=p1.stdout, stdout=subprocess.PIPE)
254
255
256 class VersionScanner(threading.Thread):
257
258         def __init__(self, window):
259                 self.w = window
260                 self._stopevent = threading.Event()
261                 threading.Thread.__init__(self)
262
263         def run(self):
264                 l = self.w.snap_path.split (".zfs")
265                 path_before_snap = l[0]
266                 l = self.w.filename.split (path_before_snap)
267                 path_after_snap = l[1]
268                 snap_path = "%s.zfs/snapshot/" % path_before_snap;
269                 dirs = os.listdir(snap_path)
270
271                 num_dirs = len(dirs)
272                 current_dir = 1
273
274                 gobject.idle_add (self.w.progress.set_pulse_step,  (1.0 / num_dirs))
275                 gobject.idle_add (self.w.progress.set_text,  ("Scanning for older versions (%d/%d)" % (current_dir, num_dirs)))
276
277                 versions = [File (self.w.filename)]
278
279                 for dir in dirs:
280                         if not self._stopevent.isSet ():
281                                 file = File ("%s%s/%s" % (snap_path, dir, path_after_snap))
282                                 if file.exist :
283                                         if file.add_if_unique(versions):
284                                                 gobject.idle_add (self.w.add_file, file)
285                                 fraction = self.w.progress.get_fraction ()
286                                 fraction += self.w.progress.get_pulse_step ()
287                                 if fraction > 1:
288                                         fraction = 1
289
290                                 gobject.idle_add (self.w.progress.set_fraction, fraction)
291                                 current_dir += 1
292                                 gobject.idle_add (self.w.progress.set_text, "Scanning for older versions (%d/%d)" % (current_dir, num_dirs))
293                         else:
294                                 return None
295
296                 gobject.idle_add(self.w.progress.hide)
297                 gobject.idle_add(self.w.older_versions_label.set_markup , "<b>Older Versions</b> (%d) " % (len(versions) - 1))
298                 # sort by date
299                 gobject.idle_add(self.w.date_column.emit, "clicked")
300                 gobject.idle_add(self.w.date_column.emit, "clicked")
301         
302         def join(self, timeout=None):
303                 self._stopevent.set ()
304                 threading.Thread.join(self, timeout)
305
306 def main(argv):
307         try:
308                 opts, args = getopt.getopt(sys.argv[1:], "", [])
309         except getopt.GetoptError:
310                 sys.exit(2)
311         if len(args) != 2:
312                 dialog = gtk.MessageDialog(None,
313                         0,
314                         gtk.MESSAGE_ERROR,
315                         gtk.BUTTONS_CLOSE,
316                         _("Invalid arguments count."))
317                 dialog.set_title ("Error")
318                 dialog.format_secondary_text(_("Version explorer requires"
319                         " 2 arguments :\n- The path of the "
320                         "root snapshot directory.\n"
321                         "- The filename to explore."))
322                 dialog.run()
323                 sys.exit (2)
324
325         window = FileVersionWindow(args[0], args[1])
326         gtk.gdk.threads_enter()
327         gtk.main()
328         gtk.gdk.threads_leave()