# This Python file uses the following encoding: utf-8 """ GUI, main window class and correspondig methods for pyGTKtalog app. """ #{{{ licence = \ """ GPL v2 http://www.gnu.org/licenses/gpl.txt """ #}}} __version__ = "0.5" import sys import os import mimetypes import popen2 import datetime import bz2 import pygtk import gtk import gtk.glade from pysqlite2 import dbapi2 as sqlite import mx.DateTime from config import Config import deviceHelper import filetypeHelper import dialogs from preferences import Preferences import db class PyGTKtalog: def __init__(self): """ init""" # {{{ init self.conf = Config() self.conf.load() self.opened_catalog = None self.db_tmp_filename = None self.progress = self.pygtkcat.get_widget("progressbar1") # trees self.discs = self.pygtkcat.get_widget('discs') c = gtk.TreeViewColumn('Filename') cellpb = gtk.CellRendererPixbuf() cell = gtk.CellRendererText() c.pack_start(cellpb, False) c.pack_start(cell, True) c.set_attributes(cellpb, stock_id=2) c.set_attributes(cell, text=1) self.discs.append_column(c) self.files = self.pygtkcat.get_widget('files') self.files.get_selection().set_mode(gtk.SELECTION_MULTIPLE) c = gtk.TreeViewColumn('Filename') cellpb = gtk.CellRendererPixbuf() cell = gtk.CellRendererText() c.pack_start(cellpb, False) c.pack_start(cell, True) c.set_attributes(cellpb, stock_id=6) c.set_attributes(cell, text=1) c.set_sort_column_id(1) c.set_resizable(True) self.files.append_column(c) c = gtk.TreeViewColumn('Size',gtk.CellRendererText(), text=2) c.set_sort_column_id(2) c.set_resizable(True) self.files.append_column(c) c = gtk.TreeViewColumn('Date',gtk.CellRendererText(), text=3) c.set_sort_column_id(3) c.set_resizable(True) self.files.append_column(c) c = gtk.TreeViewColumn('Category',gtk.CellRendererText(), text=5) c.set_sort_column_id(5) c.set_resizable(True) self.files.append_column(c) # signals: dic = {"on_main_destroy_event" :self.doQuit, "on_quit1_activate" :self.doQuit, "on_tb_quit_clicked" :self.doQuit, "on_new1_activate" :self.newDB, "on_tb_new_clicked" :self.newDB, "on_add_cd_activate" :self.addCD, "on_tb_addcd_clicked" :self.addCD, "on_add_directory1_activate" :self.addDirectory, "on_about1_activate" :self.about, "on_properties1_activate" :self.preferences, "on_status_bar1_activate" :self.toggle_status_bar, "on_toolbar1_activate" :self.toggle_toolbar, "on_save1_activate" :self.save, "on_tb_save_clicked" :self.save, "on_save_as1_activate" :self.save_as, "on_tb_open_clicked" :self.opendb, "on_open1_activate" :self.opendb, "on_discs_cursor_changed" :self.show_files, "on_discs_row_activated" :self.collapse_expand_branch, "on_files_cursor_changed" :self.show_details, "on_files_row_activated" :self.change_view, } # connect signals self.pygtkcat.signal_autoconnect(dic) self.window.connect("delete_event", self.deleteEvent) #}}} def collapse_expand_branch(self, treeview, path, treecolumn): """if possible, expand or collapse branch of tree""" #{{{ if treeview.row_expanded(path): treeview.collapse_row(path) else: treeview.expand_row(path,False) #}}} def show_details(self,treeview): """show details about file""" #{{{ model, paths = treeview.get_selection().get_selected_rows() try: itera = model.get_iter(paths[0]) except: if __debug__: print "[mainwin.py] insufficient iterator" return if model.get_value(itera,4) == 1: #directory, do nothin', just turn off view self.details.hide() buf = self.details.get_buffer() buf.set_text('') self.details.set_buffer(buf) if __debug__: print "[mainwin.py] directory selected" elif model.get_value(itera,4) > 1: #file, show what you got. self.details.show() txt = db.dbfile(self,self.con,self.cur).getInfo(model.get_value(itera,0)) buf = self.details.get_buffer() buf.set_text(txt) self.details.set_buffer(buf) if __debug__: print "[mainwin.py] some other thing selected" #}}} def change_view(self, treeview, path, treecolumn): """change directory deep down""" #{{{ model, paths = treeview.get_selection().get_selected_rows() itera = model.get_iter(paths[0]) current_id = model.get_value(itera,0) if model.get_value(itera,4) == 1: self.filemodel = db.dbfile(self,self.con,self.cur).getCurrentFiles(current_id) self.files.set_model(self.filemodel) pat,col = self.discs.get_cursor() if pat!=None: if not self.discs.row_expanded(pat): self.discs.expand_row(pat,False) #self.discs.unselect_all() model, paths = self.discs.get_selection().get_selected_rows() selected = None new_iter = self.discs.get_model().iter_children(model.get_iter(pat)) if new_iter: while new_iter: if model.get_value(new_iter,0) == current_id: self.discs.set_cursor(model.get_path(new_iter)) new_iter = model.iter_next(new_iter) else: #directory, do nothin', just turn off view if __debug__: print "[mainwin.py] directory selected" #}}} def sort_files_view(self, model, iter1, iter2, data): print 'aaaa' def show_files(self,treeview): """show files after click on left side disc tree""" #{{{ model = treeview.get_model() selected_item = model.get_value(model.get_iter(treeview.get_cursor()[0]),0) self.filemodel = db.dbfile(self,self.con,self.cur).getCurrentFiles(selected_item) self.files.set_model(self.filemodel) self.details.show() txt = db.dbfile(self,self.con,self.cur).getInfo(selected_item) buf = self.details.get_buffer() buf.set_text(txt) self.details.set_buffer(buf) if __debug__: print "[mainwin.py] some other thing selected" """ iterator = treeview.get_model().get_iter_first(); while iterator != None: if model.get_value(iterator,0) == selected: self.glade.get_widget(self.category_dict[model.get_value(iterator,0)]).show() self.desc.set_markup("%s" % selected) else: self.glade.get_widget(self.category_dict[model.get_value(iterator,0)]).hide() iterator = treeview.get_model().iter_next(iterator); """ #}}} def opendb(self,widget): """open dtatabase file, decompress it to temp""" #{{{ try: if self.unsaved_project: if self.conf.confd['confirmabandon']: obj = dialogs.Qst('Unsaved data - pyGTKtalog','There is not saved database\nDo you really want to abandon it?') if not obj.run(): return except AttributeError: pass #create filechooser dialog dialog = gtk.FileChooserDialog( title="Open catalog", action=gtk.FILE_CHOOSER_ACTION_OPEN, buttons=( gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL, gtk.STOCK_OPEN, gtk.RESPONSE_OK ) ) dialog.set_default_response(gtk.RESPONSE_OK) f = gtk.FileFilter() f.set_name("Catalog files") f.add_pattern("*.pgt") dialog.add_filter(f) f = gtk.FileFilter() f.set_name("All files") f.add_pattern("*.*") dialog.add_filter(f) response = dialog.run() tmp = self.opened_catalog try: self.opened_catalog = dialog.get_filename() except: self.opened_catalog = tmp pass dialog.destroy() if response == gtk.RESPONSE_OK: # delete an existing temp file try: os.unlink(self.db_tmp_filename) except: pass # initial switches self.db_tmp_filename = None self.active_project = True self.unsaved_project = False self.window.set_title("untitled - pyGTKtalog") self.db_tmp_filename = "/tmp/pygtktalog%d.db" % datetime.datetime.now().microsecond source = bz2.BZ2File(self.opened_catalog, 'rb') destination = open(self.db_tmp_filename, 'wb') while True: try: data = source.read(1024000) except: dialogs.Err("Error opening file - pyGTKtalog","Cannot open file %s." % self.opened_catalog) self.opened_catalog = None self.newDB(self.window) return if not data: break destination.write(data) destination.close() source.close() self.active_project = True self.unsaved_project = False self.con = sqlite.connect("%s" % self.db_tmp_filename, detect_types=sqlite.PARSE_DECLTYPES|sqlite.PARSE_COLNAMES) self.cur = self.con.cursor() self.window.set_title("%s - pyGTKtalog" % self.opened_catalog) for w in self.widgets: try: a = self.pygtkcat.get_widget(w) a.set_sensitive(True) except: pass # PyGTK FAQ entry 23.20 while gtk.events_pending(): gtk.main_iteration() self.__display_main_tree() else: self.opened_catalog = tmp #}}} def __create_database(self,filename): """make all necessary tables in db file""" #{{{ self.con = sqlite.connect("%s" % filename, detect_types=sqlite.PARSE_DECLTYPES|sqlite.PARSE_COLNAMES) self.cur = self.con.cursor() self.cur.execute("create table files(id INTEGER PRIMARY KEY AUTOINCREMENT, filename TEXT, date datetime, size integer, type integer);") self.cur.execute("create table files_connect(id INTEGER PRIMARY KEY AUTOINCREMENT, parent numeric, child numeric, depth numeric);") self.cur.execute("insert into files values(1, 'root', 0, 0, 0);") self.cur.execute("insert into files_connect values(1, 1, 1, 0);") #}}} def save(self,widget): """save database to file. compress it with gzip""" #{{{ if self.opened_catalog == None: self.save_as(widget) else: self.__compress_and_save(self.opened_catalog) #}}} def save_as(self,widget): """save database to another file. compress it with gzip""" #{{{ dialog = gtk.FileChooserDialog( title="Save catalog as...", action=gtk.FILE_CHOOSER_ACTION_SAVE, buttons=( gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL, gtk.STOCK_SAVE, gtk.RESPONSE_OK ) ) dialog.set_action(gtk.FILE_CHOOSER_ACTION_SAVE) dialog.set_default_response(gtk.RESPONSE_OK) dialog.set_do_overwrite_confirmation(True) if widget.get_name() == 'save1': dialog.set_title('Save catalog to file...') f = gtk.FileFilter() f.set_name("Catalog files") f.add_pattern("*.pgt") dialog.add_filter(f) f = gtk.FileFilter() f.set_name("All files") f.add_pattern("*.*") dialog.add_filter(f) response = dialog.run() if response == gtk.RESPONSE_OK: filename = dialog.get_filename() if filename[-4] == '.': if filename[-3:].lower() != 'pgt': filename = filename + '.pgt' else: filename = filename[:-3] + 'pgt' else: filename = filename + '.pgt' self.__compress_and_save(filename) self.opened_catalog = filename dialog.destroy() #}}} def __compress_and_save(self,filename): """compress and save temporary file to catalog""" #{{{ source = open(self.db_tmp_filename, 'rb') destination = bz2.BZ2File(filename, 'w') while True: data = source.read(1024000) if not data: break destination.write(data) destination.close() source.close() self.window.set_title("%s - pyGTKtalog" % filename) self.unsaved_project = False #}}} def toggle_toolbar(self,widget): """toggle visibility of toolbar bar""" #{{{ self.conf.confd['showtoolbar'] = self.menu_toolbar.get_active() if self.menu_toolbar.get_active(): self.toolbar.show() else: self.toolbar.hide() #}}} def toggle_status_bar(self,widget): """toggle visibility of statusbat and progress bar""" #{{{ self.conf.confd['showstatusbar'] = self.menu_statusbar.get_active() if self.menu_statusbar.get_active(): self.statusprogress.show() else: self.statusprogress.hide() #}}} def preferences(self,widget): """display preferences window""" #{{{ a = Preferences() #}}} def doQuit(self, widget): """quit and save window parameters to config file""" #{{{ try: if widget.title: pass except: # check if any unsaved project is on go. try: if self.unsaved_project: if self.conf.confd['confirmquit']: obj = dialogs.Qst('Quit application - pyGTKtalog','There is not saved database\nDo you really want to quit?') if not obj.run(): return except AttributeError: pass self.storeSettings() gtk.main_quit() try: self.con.commit() self.cur.close() self.con.close() except: pass try: os.unlink(self.db_tmp_filename) except: pass return False #}}} def newDB(self,widget): """create database in temporary place""" #{{{ try: if self.unsaved_project: if self.conf.confd['confirmabandon']: obj = dialogs.Qst('Unsaved data - pyGTKtalog','There is not saved database\nDo you really want to abandon it?') if not obj.run(): return except AttributeError: pass self.active_project = True self.unsaved_project = False self.window.set_title("untitled - pyGTKtalog") for w in self.widgets: try: a = self.pygtkcat.get_widget(w) a.set_sensitive(True) except: pass # PyGTK FAQ entry 23.20 while gtk.events_pending(): gtk.main_iteration() # Create new database if self.db_tmp_filename!=None: self.con.commit() self.cur.close() self.con.close() os.unlink(self.db_tmp_filename) self.db_tmp_filename = datetime.datetime.now() self.db_tmp_filename = "/tmp/pygtktalog%d.db" % self.db_tmp_filename.microsecond self.__create_database(self.db_tmp_filename) #clear treeview, if possible try: self.discs.get_model().clear() except: pass try: self.files.get_model().clear() except: pass #}}} def deleteEvent(self, widget, event, data=None): """checkout actual database changed. If so, do the necessary ask.""" #{{{ try: if self.unsaved_project: if self.conf.confd['confirmquit']: obj = dialogs.Qst('Quit application - pyGTKtalog','There is not saved database\nDo you really want to quit?') if not obj.run(): return True except AttributeError: pass self.storeSettings() try: self.cur.close() except: pass try: self.con.close() except: pass try: os.unlink(self.db_tmp_filename) except: pass return False #}}} def run(self): """show window and run app""" #{{{ self.window.show(); gtk.main() #}}} def addDirectory(self,widget): """add directory structure from given location""" #{{{ obj = dialogs.PointDirectoryToAdd() res = obj.run() if res !=(None,None): self.__scan(res[1],res[0]) #}}} def addCD(self,widget): """add directory structure from cd/dvd disc""" #{{{ mount = deviceHelper.volmount(self.conf.confd['cd']) if mount == 'ok': guessed_label = deviceHelper.volname(self.conf.confd['cd']) obj = dialogs.InputDiskLabel(guessed_label) label = obj.run() if label != None: self.__scan(self.conf.confd['cd'],label) # umount/eject cd if self.conf.confd['eject']: msg = deviceHelper.eject_cd() if msg != 'ok': dialogs.Wrn("error ejecting device - pyGTKtalog","Cannot eject device pointed to %s.\nLast eject message:\n%s" % (self.conf.confd['cd'],msg)) else: msg = deviceHelper.volumount(self.conf.confd['cd']) if msg != 'ok': dialogs.Wrn("error unmounting device - pyGTKtalog","Cannot unmount device pointed to %s.\nLast umount message:\n%s" % (self.conf.confd['cd'],msg)) else: dialogs.Wrn("error mounting device - pyGTKtalog","Cannot mount device pointed to %s.\nLast mount message:\n%s" % (self.conf.confd['cd'],mount)) #}}} def __scan(self,path,label,currentdb=None): """scan content of the given path""" #{{{ mime = mimetypes.MimeTypes() mov_ext = ('mkv','avi','ogg','mpg','wmv','mp4','mpeg') img_ext = ('jpg','jpeg','png','gif','bmp','tga','tif','tiff','ilbm','iff','pcx') # count files in directory tree count = 0 if self.StatusbarID != 0: self.status.remove(self.ContextID, self.StatusbarID) self.StatusbarID = self.status.push(self.ContextID, "Calculating number of files in directory tree...") for root,kat,plik in os.walk(path): for p in plik: count+=1 while gtk.events_pending(): gtk.main_iteration() frac = 1.0/count self.count = 0 def __recurse(path,name,wobj,date=0,frac=0,size=0,idWhere=1): """recursive scans the path path = path string name = field name wobj = obiekt katalogu date = data pliku frac - kolejne krok w statusbarze. idWhere - simple id parent, or "place" where to add node """ #{{{ _size = size walker = os.walk(path) root,dirs,files = walker.next() ftype = 1 self.cur.execute("insert into files(filename, date, size, type) values(?,?,?,?)",(name, date, 0, ftype)) self.cur.execute("select seq FROM sqlite_sequence WHERE name='files'") currentid=self.cur.fetchone()[0] self.cur.execute("insert into files_connect(parent,child,depth) values(?,?,?)",(currentid, currentid, 0)) if idWhere>0: self.cur.execute("insert into files_connect(parent, child, depth) select r1.parent, r2.child, r1.depth + r2.depth + 1 as depth FROM files_connect r1, files_connect r2 WHERE r1.child = ? AND r2.parent = ? ",(idWhere, currentid)) for i in dirs: st = os.stat(os.path.join(root,i)) _size = _size + __recurse(os.path.join(path,i),i,wobj,st.st_mtime,frac,0,currentid) for i in files: self.count = self.count + 1 st = os.stat(os.path.join(root,i)) ### scan files if i[-3:].lower() in mov_ext or \ mime.guess_type(i)!= (None,None) and \ mime.guess_type(i)[0].split("/")[0] == 'video': # video only info = filetypeHelper.guess_video(os.path.join(root,i)) elif i[-3:].lower() in img_ext or \ mime.guess_type(i)!= (None,None) and \ mime.guess_type(i)[0].split("/")[0] == 'image': pass ### end of scan # progress/status if wobj.StatusbarID != 0: wobj.status.remove(wobj.ContextID, wobj.StatusbarID) wobj.StatusbarID = wobj.status.push(wobj.ContextID, "Scannig: %s" % (os.path.join(root,i))) wobj.progress.set_fraction(frac * self.count) # PyGTK FAQ entry 23.20 while gtk.events_pending(): gtk.main_iteration() _size = _size + st.st_size self.cur.execute('insert into files(filename, date, size, type) values(?,?,?,?)',(i, st.st_mtime, st.st_size,2)) self.cur.execute("select seq FROM sqlite_sequence WHERE name='files'") currentfileid=self.cur.fetchone()[0] self.cur.execute("insert into files_connect(parent,child,depth) values(?,?,?)",(currentfileid, currentfileid, 0)) if currentid>0: self.cur.execute("insert into files_connect(parent, child, depth) select r1.parent, r2.child, r1.depth + r2.depth + 1 as depth FROM files_connect r1, files_connect r2 WHERE r1.child = ? AND r2.parent = ? ",(currentid, currentfileid)) self.con.commit() self.cur.execute("update files set size=? where id=?",(_size, currentid)) return _size #}}} self.con.commit() __recurse(path,label,self,0,frac) self.unsaved_project = True self.__display_main_tree() if self.StatusbarID != 0: self.status.remove(self.ContextID, self.StatusbarID) self.StatusbarID = self.status.push(self.ContextID, "Idle") self.progress.set_fraction(0) #}}} def __display_main_tree(self): """refresh tree with model form db""" #{{{ try: self.dirmodel = db.dbfile(self,self.con,self.cur).getDirectories() except: dialogs.Err("Error opening file - pyGTKtalog","Cannot open file %s." % self.opened_catalog) self.newDB(self.window) return #self.dirmodel.set_sort_column_id(1,gtk.SORT_ASCENDING) self.discs.set_model(self.dirmodel) #}}} def about(self,widget): """simple about dialog""" #{{{ dialogs.Abt("pyGTKtalog", __version__, "About", ["Roman 'gryf' Dobosz"], licence) #}}}