#!/usr/bin/env python from PyQt4 import QtCore, QtGui import sys import math import urllib import os import rpm # -------------------------------------------------------------------------- icon_dir = "/abc/scan/icons"; icon_cache = { } def icon (icon_name) : if icon_name == "" : return None else : if not icon_name in icon_cache : icon_cache [icon_name] = QtGui.QIcon (icon_dir + "/" + icon_name + ".xpm") return icon_cache [icon_name] # -------------------------------------------------------------------------- def print_object (obj) : print obj.__dict__ # for s in obj.__dict__.keys () : # print s, ":", repr (getattr (obj, s)) # -------------------------------------------------------------------------- def decode_str (s) : return urllib.unquote (s) def decode_num (s) : return s def decode_time (s) : return s # -------------------------------------------------------------------------- Normal = 0 Old = 1 New = 2 Modified = 3 Equal = 4 class FileInfo : "File Data" def __init__ (self) : self.kind = Normal self.name = "" self.size = "" self.mtime = "" self.md5 = "" self.file_node = None class DirInfo : "Directory Data" def __init__ (self) : self.kind = Normal self.name = "" self.subdirs = { } self.files = {} self.tree_node = None self.file_node = None # -------------------------------------------------------------------------- def readLine (line) : "Read one line from scan-file, return FileInfo" result = FileInfo () items = line.split () inx = 1 for item in items : if item.startswith ("md5=") : result.md5 = item[4:] elif item.startswith ("name=") : result.name = decode_str (item[5:]) elif item.startswith ("size=") : result.size = decode_num (item[5:]) elif item.startswith ("mode=") : result.mode = item[5:] elif item.startswith ("uid=") : result.uid = decode_num (item[4:]) elif item.startswith ("gid=") : result.gid = decode_num (item[4:]) elif item.startswith ("atime=") : result.atime = decode_time (item[6:]) elif item.startswith ("ctime=") : result.ctime = decode_time (item[6:]) elif item.startswith ("mtime=") : result.mtime = decode_time (item[6:]) elif inx == 1 : result.md5 = item inx = inx + 1 elif inx == 2 : result.name = decode_str (item) inx = inx + 1 elif inx == 3 : result.size = decode_num (item) inx = inx + 1 elif inx == 4 : result.mtime = decode_time (item) inx = inx + 1 return result def addFile (top, data) : current = top if data.name.find ('/') != -1 : path = data.name.split ('/') # slash else : path = data.name.split ('\\') # backslash count = len (path) inx = 0 while inx < count-1 : item = path [inx] if item != "" : if not current.subdirs.has_key (item) : current.subdirs [item] = DirInfo () # create new subitem current = current.subdirs [item] inx = inx + 1 item = path [count-1] current.files[item] = data # store file data def readFile (fileName) : "Read scan-file, return DirInfo" result = DirInfo () file = open (fileName, "r") for line in file : data = readLine (line) addFile (result, data) file.close () return result # -------------------------------------------------------------------------- def directoryIconName (kind) : icon_name = "" if kind == Normal : icon_name = "normal_folder" elif kind == Old : icon_name = "red_folder" elif kind == New : icon_name = "green_folder" elif kind == Modified : icon_name = "yellow_folder" elif kind == Equal : icon_name = "grey_folder" return icon_name class TreeItem (QtGui.QTreeWidgetItem): def __init__ (self, parent, text, icon_image=None): QtGui.QTreeWidgetItem.__init__ (self, parent) self.dir_data = None # self.setFlags (self.flags () | QtCore.Qt.ItemIsEditable) self.setText (0, text) if icon_image : self.setIcon (0, icon_image) class TreeView (QtGui.QTreeWidget): "Directory tree - on the left side of main window" def __init__ (self, parent=None): QtGui.QTreeWidget.__init__ (self, parent) self.setColumnCount (1) self.header ().hide () self.setRootIsDecorated (True) self.setSelectionMode (QtGui.QAbstractItemView.ExtendedSelection) self.setEditTriggers (QtGui.QAbstractItemView.SelectedClicked) self.setIndentation (8); def showDir (top_node, top_data) : top_node.dir_data = top_data top_data.tree_node = top_node for key in top_data.subdirs.keys () : data = top_data.subdirs [key] icon_name = directoryIconName (data.kind) node = TreeItem (top_node, key, icon (icon_name)) showDir (node, data) def addBranch (tree_text, icon_image, data) : "Add DirInfo object into TreeView" node = TreeItem (win.treeView, tree_text, icon_image) showDir (node, data) def refreshDir (top_data) : all_equal = True common_kind = None start = True for key in top_data.subdirs.keys () : d = top_data.subdirs [key] refreshDir (d) if start : common_kind = d.kind start = False else : if d.kind != common_kind : all_equal = False if not all_equal : common_kind = Modified #icon_name = directoryIconName (common_kind) #icon_image = icon (icon_name) #top_node = top_data.node_data #if top_node : #if icon_image : #top_node.setIcon (0, icon_image) # -------------------------------------------------------------------------- def fileIconName (kind) : icon_name = "" if kind == Normal : icon_name = "normal_page" elif kind == Old : icon_name = "red_page" elif kind == New : icon_name = "green_page" elif kind == Modified : icon_name = "yellow_page" elif kind == Equal : icon_name = "grey_page" return icon_name class FileItem (QtGui.QTreeWidgetItem): def __init__ (self, parent): QtGui.QTreeWidgetItem.__init__ (self, parent) self.dir_data = None self.file_data = None class FileView (QtGui.QTreeWidget): "File List - on the right side" def __init__ (self, parent=None): QtGui.QTreeWidget.__init__ (self, parent) self.setColumnCount (4) self.setHeaderLabels (("name", "size", "date", "check sum")) self.setSortingEnabled (True) self.setRootIsDecorated (False) def showFile (self, data) : icon_name = fileIconName (data.kind) node = FileItem (self) node.file_data = data node.setIcon (0, icon (icon_name)) node.setText (0, data.name) node.setText (1, data.size) node.setText (2, data.mtime) node.setText (3, data.md5) def showSubdir (self, data) : icon_name = directoryIconName (data.kind) node = FileItem (self) node.dir_data = data node.setIcon (0, icon (icon_name)) node.setText (0, data.name) # node.setText (1, data.size) # node.setText (2, data.mtime) # node.setText (3, data.md5) def showFiles (self, data) : self.clear () # remove all items if data : for key in data.subdirs.keys (): self.showSubdir (data.subdirs [key]) for key in data.files.keys () : self.showFile (data.files [key]) def refreshFile (f) : icon_name = fileIconName (f.kind) # node.setIcon (0, icon (icon_name)) # -------------------------------------------------------------------------- class DetailItem (QtGui.QTreeWidgetItem): def __init__ (self, parent, text, icon_image=None): QtGui.QTreeWidgetItem.__init__ (self, parent) self.setText (0, text) if icon_image : self.setIcon (0, icon_image) class DetailView (QtGui.QTreeWidget): "Detail file list - below the File list" def __init__ (self): QtGui.QTextBrowser.__init__ (self) self.header().hide() # -------------------------------------------------------------------------- class InfoView (QtGui.QTextEdit): "Information - on next tab" def __init__ (self): QtGui.QWidget.__init__ (self) # -------------------------------------------------------------------------- class Data : def __init__ (self, name) : self.name = name self.value = 0 self.subitems = { } def __repr__ (self) : return str (self.value) + "," + str (self.subitems) class DiagramView (QtGui.QGraphicsView): "Pie diagram" def __init__(self, parent = None): QtGui.QGraphicsView.__init__(self, parent) self.scene = QtGui.QGraphicsScene (); # self.scene.setSceneRect(0, 0, 400, 400); self.setScene (self.scene) item = QtGui.QGraphicsRectItem () item.setRect (QtCore.QRectF (80, 20, 100, 100)) item.setPen (QtCore.Qt.blue) item.setBrush (QtCore.Qt.yellow) item.setFlags(QtGui.QGraphicsItem.ItemIsSelectable | QtGui.QGraphicsItem.ItemIsMovable) item.setToolTip ("tooltip") self.scene.addItem (item) self.drawRing (200, 200, 40, 100, (40, 50, 90, 45, 45)) self.drawRing (200, 200, 100, 160, (25, 15, 30, 20, 40, 25, 25, 25, 15, 5, 25, 20)) def point (self, x, y, r, fi) : fi = 2 * math.pi * fi / 360 return (x + r * math.cos (fi), y + r * math.sin (fi)) def drawSector (self, x0, y0, r1, r2, start, stop) : path = QtGui.QPainterPath () (x, y) = self.point (x0, y0, r1, start) path.moveTo (x, y) (x, y) = self.point (x0, y0, r2, start) path.lineTo (x, y) # (x, y) = self.point (x0, y0, r2, stop) path.arcTo (x0-r2, y0-r2, 2*r2, 2*r2, -start, -(stop-start)) # path.lineTo (x, y) (x, y) = self.point (x0, y0, r1, stop) path.lineTo (x, y) path.arcTo (x0-r1, y0-r1, 2*r1, 2*r1, -stop, stop-start) path.closeSubpath () item = QtGui.QGraphicsPathItem () item.setPath (path) item.setPen (QtCore.Qt.blue) item.setBrush (QtGui.QColor.fromHsvF (start*1.0/360, 1, 1)) item.setFlags(QtGui.QGraphicsItem.ItemIsSelectable | QtGui.QGraphicsItem.ItemIsMovable) self.scene.addItem (item) def drawRing (self, x0, y0, r1, r2, items) : start = 0 for item in items : stop = start + item self.drawSector (x0, y0, r1, r2, start, stop) start = stop def add (self, name, value) : items = name.split ('/') # start with 'top' dictionary store = self.top for item in items : if not store.has_key (item) : store [item] = Data (item) actual = store [item] actual.value += value # continue with 'subitems' dictionary store = actual.subitems def setup (self) : self.top = { } self.add ("bin", 100) self.add ("etc", 100) self.add ("dev", 100) self.add ("usr/bin", 100) self.add ("usr/include", 200) self.add ("usr/lib", 300) self.add ("usr/share", 300) self.add ("var/cache", 100) self.add ("var/log", 100) def sumDir (self, a) : a.sum = 0 for name in a.subdirs.keys () : f = a.subdirs [name] self.sumDir (f) a.sum += f.sum for name in a.files.keys () : f = a.files [name] a.sum += int (f.size) def setupLevel (self, a, level) : result = { } for name in a.subdirs.keys () : f = a.subdirs [name] item = Data (name) item.value = f.sum result [item.name] = item if level > 1 : item.subitems = self.setupLevel (f, level-1) return result def setupDir (self, a) : self.sumDir (a) self.top = self.setupLevel (a, 2) # -------------------------------------------------------------------------- class MainWin (QtGui.QMainWindow): "Main window" def __init__ (self, parent=None): QtGui.QMainWindow.__init__ (self, parent) self.marked_dir = None self.actions = {} self.setupUI () self.setupMenus () self.setupToolbars () self.setupConnections () def setupUI (self) : self.setStatusBar (QtGui.QStatusBar ()) self.resize (640, 480); self.split = QtGui.QSplitter () self.setCentralWidget (self.split) self.treeView = TreeView () self.split.addWidget (self.treeView) self.rightTabs = QtGui.QTabWidget () self.rightTabs.tabBar().setFocusPolicy (QtCore.Qt.NoFocus) self.rightTabs.setTabPosition (QtGui.QTabWidget.South) self.split.addWidget (self.rightTabs) self.dataSplit = QtGui.QSplitter (QtCore.Qt.Vertical) self.rightTabs.addTab (self.dataSplit, "Files") self.fileView = FileView (self) self.dataSplit.addWidget (self.fileView) self.detailView = DetailView () self.dataSplit.addWidget (self.detailView) self.diagramView = DiagramView () self.rightTabs.addTab (self.diagramView, "Diagram") self.infoView = InfoView () self.rightTabs.addTab (self.infoView, "Info") self.split.setSizes ((100, 200)) self.dataSplit.setSizes ((300, 100)) self.statusLabel = QtGui.QLabel () self.statusLabel.setFrameStyle(QtGui.QFrame.StyledPanel|QtGui.QFrame.Sunken) self.statusBar().addPermanentWidget (self.statusLabel) def action (self, ident, name, icon_image = None ) : title = name.title () act = QtGui.QAction ("&" + title, self) if icon_image : act.setIcon (icon_image) self.actions [ident] = act return act def viewAction (self, ident, name, icon_image) : act = self.action (ident, name, icon_image) act.setCheckable (1) act.setChecked (1) act.setToolTip (name) act.setStatusTip ("Show " + name + " files") return act def checkAction (self, ident, name, icon_image) : act = self.action (ident, name, icon_image) act.setCheckable (1) act.setChecked (1) act.setToolTip (name) if name == "partial" : act.setStatusTip ("Show directories with both checked and unchecked files") else : act.setStatusTip ("Show " + name + " files") return act def setupMenus (self): "Main menu" click = QtCore.SIGNAL ("triggered()") fileMenu = self.menuBar().addMenu ("&File") act = self.action ("FileOpen", "Open...") self.connect (act, click, openFile_click) fileMenu.addAction (act) act = self.action ("Test", "Test") self.connect (act, click, test_click) fileMenu.addAction (act) fileMenu.addSeparator () act = self.action ("FileQuit", "Quit") self.connect (act, click, self.close) fileMenu.addAction (act) viewMenu = self.menuBar().addMenu ("&View") act = self.viewAction ("ViewOld", "old", icon ("red_page")) viewMenu.addAction (act) act = self.viewAction ("ViewNew", "new", icon ("green_page")) viewMenu.addAction (act) act = self.viewAction ("ViewModified", "modified", icon ("yellow_page")) viewMenu.addAction (act) act = self.viewAction ("ViewEqual", "equal", icon ("grey_page")) viewMenu.addAction (act) viewMenu.addSeparator () act = self.checkAction ("ViewChecked", "checked", icon ("check")) viewMenu.addAction (act) act = self.checkAction ("ViewUnchecked", "unchecked", icon ("uncheck")) viewMenu.addAction (act) act = self.checkAction ("ViewPartial", "partial", icon ("partial")) viewMenu.addAction (act) act = self.action ("LevelUp", "Go to above directory", icon ("level_up")) act = self.action ("SortByColor", "Sort by color", icon ("color")) act = self.action ("CompactList", "Compact list", icon ("compact_list")) act = self.action ("DetailList", "Detail list", icon ("detail_list")) compareMenu = self.menuBar().addMenu ("&Compare") act = self.action ("Mark", "Mark") self.connect (act, click, mark_click) compareMenu.addAction (act) act = self.action ("Compare", "Compare") self.connect (act, click, compare_click) compareMenu.addAction (act) act = self.action ("Lookup", "Lookup") self.connect (act, click, lookup_click) compareMenu.addAction (act) def setupToolbars (self): toolbar = self.addToolBar ("tools") toolbar.setMovable (False) # toolbar.setFloatable (False) toolbar.setIconSize (QtCore.QSize (16, 16)) toolbar.addAction (self.actions["FileOpen"]) toolbar.addAction (self.actions["Test"]) toolbar.addAction (self.actions["Mark"]) toolbar.addAction (self.actions["Compare"]) toolbar.addAction (self.actions["Lookup"]) toolbar.addSeparator () toolbar.addAction (self.actions["LevelUp"]) toolbar.addSeparator () toolbar.addAction (self.actions["SortByColor"]) toolbar.addSeparator () toolbar.addAction (self.actions["CompactList"]) toolbar.addAction (self.actions["DetailList"]) toolbar.addSeparator () toolbar.addAction (self.actions["ViewOld"]) toolbar.addAction (self.actions["ViewNew"]) toolbar.addAction (self.actions["ViewModified"]) toolbar.addAction (self.actions["ViewEqual"]) toolbar.addSeparator () toolbar.addAction (self.actions["ViewChecked"]) toolbar.addAction (self.actions["ViewUnchecked"]) toolbar.addAction (self.actions["ViewPartial"]) def setupConnections (self): self.connect (self.treeView, QtCore.SIGNAL("itemClicked (QTreeWidgetItem *, int)"), self.treeView_itemClicked) self.connect (self.fileView, QtCore.SIGNAL("itemClicked (QTreeWidgetItem *, int)"), self.fileView_itemClicked) def treeView_itemClicked (self, tree_node, column) : if tree_node : self.fileView.showFiles (tree_node.dir_data) else : self.fileView.showFiles (None) def fileView_itemClicked (self, file_node, column) : if file_node : subdir = file_node.dir_data if subdir : if subdir.tree_node : self.treeView.select (subdir.tree_node) # -------------------------------------------------------------------------- def exampleDir (target, name, kind) : d = DirInfo () d.name = name d.kind = kind if target != None : target.subdirs [d.name] = d return d def exampleFile (target, name) : f = FileInfo () f.name = name target.files [f.name] = f return f def example () : t = exampleDir (None, "", Normal) t1 = exampleDir (t, "abc", New) t2 = exampleDir (t, "def", Modified) t3 = exampleDir (t2, "klm", Old) exampleFile (t2, "first file") exampleFile (t2, "second file") exampleFile (t3, "third file") addBranch ("example", icon ("normal_box"), t) # -------------------------------------------------------------------------- def showInfo (text, obj=None): win.infoView.insertPlainText (text + "\n") if obj : win.infoView.insertPlainText (str (type (obj)) + "\n") win.infoView.insertPlainText (repr (obj.__dict__) + "\n") def showStatus (text): showInfo (text) win.statusBar().showMessage (text) # win.statusLabel.setText (text) print "showStatus", text QtGui.QApplication.processEvents () def openFile (fileName) : data = readFile (fileName) addBranch (fileName, icon ("normal_tree"), data) return data def openFile_click () : fileName = QtGui.QFileDialog.getOpenFileName (win, "Open File", ":scan") if fileName : openFile (fileName) def test_click () : f1 = openFile ("/abc/scan/data/duo-diskn-foto.txt") win.diagramView.setupDir (f1) return f1 = openFile ("/abc/scan/data/duo-diskn-foto.txt") showStatus ("first file opened") f2 = openFile ("/abc/scan/data/wd-foto-fotoarchiv.txt") showStatus ("second file opened") compare (f1, f2) showStatus ("compared") return packages_click () return f1 = openFile ("/abc/scan/data3/pentium4-diskf-2009-04-25.txt") showStatus ("first file opened") f2 = openFile ("/abc/scan/data3/wd-foto-2009-04-25.txt") lookup (f1, f2) showStatus ("lookup") return # -------------------------------------------------------------------------- def currentTreeItem () : result = None item = win.treeView.currentItem () if item : result = item.dir_data return result def mark_click () : win.marked_dir = currentTreeItem () def markedTreeItem () : return win.marked_dir # -------------------------------------------------------------------------- def duplFile (a) : r = FileInfo () r.name = a.name r.size = a.size r.mtime = a.mtime r.md5 = a.md5 return r def duplDir (a, k) : result = DirInfo () result.kind = k for name in a.subdirs.keys () : f = a.subdirs [name] n = duplDir (f, k) result.subdirs [name] = n for name in a.files.keys () : f = a.files [name] n = duplFile (f) n.kind = k result.files [name] = n return result def compareDir (a, b) : result = DirInfo () all_equal = True # subdirectories for name in a.subdirs.keys () : f = a.subdirs [name] if name in b.subdirs : g = b.subdirs [name] r = compareDir (f, g) result.subdirs[name] = r else : r = duplDir (f, Old) result.subdirs[name] = r all_equal = False for name in b.subdirs.keys () : if not name in a.subdirs : g = b.subdirs [name] r = duplDir (g, New) result.subdirs[name] = r all_equal = False # files from a for name in a.files.keys () : f = a.files [name] if name in b.files : g = b.files [name] r = FileInfo () r.name = f.name cnt = 0 if f.size == g.size : r.size = f.size cnt = cnt + 1 if f.mtime == g.mtime : r.mtime = f.mtime cnt = cnt + 1 if f.md5 == g.md5 : r.md5 = f.md5 cnt = cnt + 1 if cnt == 3 : r.kind = Equal else : r.kind = Modified else : # file is only in a r = duplFile (f) r.kind = Old # store one file if r.kind != Equal : all_equal = False result.files [name] = r # remaining files from b for name in b.files.keys () : if not name in b.files : g = b.files [name] r = duplFile (f) r.kind = New all_equal = False result.files [name] = r # directory compared if all_equal : result.kind = Equal else : result.kind = Modified return result def compare (first_dir, second_dir) : if first_dir and second_dir : data = compareDir (first_dir, second_dir) addBranch ("compare", icon ("normal_box"), data) else : showStatus ("Nothing to compare") def compare_click () : first_dir = markedTreeItem () second_dir = currentTreeItem () compare (first_dir, second_dir) # -------------------------------------------------------------------------- def storeDir (a, cache) : # subdirectories for name in a.subdirs.keys () : f = a.subdirs [name] storeDir (f, cache) # files for name in a.files.keys () : f = a.files [name] cache [f.md5] = f def sourceDir (a, cache) : # subdirectories for name in a.subdirs.keys () : f = a.subdirs [name] sourceDir (f, cache) # files for name in a.files.keys () : f = a.files [name] if f.md5 in cache : kind = Equal else : kind = Old f.kind = kind refreshFile (f) refreshDir (a) def targetDir (a, cache) : # subdirectories for name in a.subdirs.keys () : f = a.subdirs [name] targetDir (f, cache) # files for name in a.files.keys () : f = a.files [name] if f.md5 in cache : kind = Equal else : kind = New f.kind = kind refreshFile (f) refreshDir (a) def printDir (a, cache) : # subdirectories for name in a.subdirs.keys () : f = a.subdirs [name] printDir (f, cache) # files for name in a.files.keys () : f = a.files [name] if not (f.md5 in cache) : if not (f.name.startswith ("Recycled")) : if not (f.name.startswith ("System")) : if not (f.name.endswith (".info")) : print f.name def lookupDir (a, b) : print "point (1)" cache = { } storeDir (b, cache) printDir (a, cache) sourceDir (a, cache) # self.targetDir (b, cache) print "point (2)" addBranch ("source", icon ("normal_box"), a) # print "point (3)" addBranch ("target", icon ("normal_box"), b) # print "point (4)" def lookup (first_dir, second_dir) : if first_dir and second_dir : lookupDir (first_dir, second_dir) def lookup_click () : first_dir = markedTreeItem () second_dir = currentTreeItem () lookupDir (first_dir, second_dir) # -------------------------------------------------------------------------- def packages () : result = DirInfo () ts = rpm.TransactionSet() # mi = ts.dbMatch() mi = ts.dbMatch('name', 'gcc') # mi = ts.dbMatch() # mi.pattern('name', rpm.RPMMIRE_GLOB, "*x11*") # rpm.addMacro('_dbpath', "/mnt/disk14/var/lib/rpm") # mi = ts.dbMatch() # ts = rpm.TransactionSet("", (rpm._RPMVSF_NOSIGNATURES)) # fd = os.open ("/mnt/store/fedora8/RPMS/gtk2-2.12.1-5.fc8.i386.rpm", os.O_RDONLY) # fd = os.open ("/mnt/store/mandrake2009.1/i586/media/main/release/qt4-common-4.5.0-3mdv2009.1.i586.rpm", os.O_RDONLY) # fd = os.open ("/mnt/store/mandrake2009.0/i586/media/main/release/qt4-doc-4.4.3-1mdv2009.0.i586.rpm", os.O_RDONLY) # hdr = ts.hdrFromFdno (fd) # mi = [ hdr ] for h in mi : name = h['name']; node = DirInfo () result.subdirs[name] = node file_names = h ['filenames'] file_sizes = h ['filesizes'] file_mtimes = h ['filemtimes'] file_md5s = h ['filemd5s'] showStatus ("package " + name) # print "%s-%s-%s" % (h['name'], h['version'], h['release']) count = len (file_names) inx = 0 while inx < count : info = FileInfo () info.name = file_names [inx] info.size = str (file_sizes [inx]) info.mtime = str (file_mtimes [inx]) info.md5 = str (file_md5s [inx]) node.files[info.name] = info inx = inx + 1 return result def packages_click () : data = packages () addBranch ("packages", icon ("normal_box"), data) # -------------------------------------------------------------------------- def main (): global win app = QtGui.QApplication (sys.argv) win = MainWin () example () win.show () app.exec_ () main ()