Browse Source

convert to python3, qt5

Pat Beirne 1 year ago
parent
commit
8bee71db38
1 changed files with 87 additions and 59 deletions
  1. 87 59
      dirnotes

+ 87 - 59
dirnotes

@@ -1,4 +1,4 @@
-#!/usr/bin/python
+#!/usr/bin/python3
 """ a simple gui or command line app
 to view and create/edit file comments
 
@@ -14,6 +14,9 @@ where possible, comments are duplicated in
 
 	these comments stick to the symlink, not the deref
 
+nav tools are enabled, so you can double-click to go into a dir, and 
+	there's an UP arrow to navigate back
+
 """
 
 
@@ -42,8 +45,9 @@ comment box gets a light yellow background.
 
 import sys,os,argparse,stat
 #~ from dirWidget import DirWidget
-from PyQt4.QtGui import *
-from PyQt4 import QtGui, QtCore
+from PyQt5.QtGui import *
+from PyQt5.QtWidgets import *
+from PyQt5.QtCore import Qt
 import xattr, sqlite3, time
 
 VERSION = "0.2"
@@ -52,7 +56,7 @@ DATABASE_NAME = "~/.dirnotes.db"
 # convert the ~/ form to a fully qualified path
 DATABASE_NAME = os.path.expanduser(DATABASE_NAME)
 
-class DataBase:
+class dnDataBase:
 	''' the database is flat
 		fileName: fully qualified name
 		st_mtime: a float
@@ -81,22 +85,22 @@ class DataBase:
 		c = self.db.cursor()
 		c.execute("select * from dirnotes where name=? and comment<>'' order by comment_date desc",(os.path.abspath(fileName),))
 		return c.fetchone()
-	def setData(self, fileName, _date, _size, comment):
-		c = self.db.cursor()
-		c.execute("insert into dirnotes values (?,?,?,?,?)",
-			(fileName, _date, _size, comment, time.time()))
-		self.db.commit()
-		return True
-	def log(self, fileName, comment):
+	def setData(self, fileName, comment):
 		''' TODO: convert filename to canonical '''
 		c = self.db.cursor()
 		s = os.lstat(fileName)
-		print "params: %s %s %d %s %s" % ((os.path.abspath(fileName), 
-				DataBase.epochToDb(s.st_mtime), s.st_size, comment, 
-				DataBase.epochToDb(time.time())))
-		c.execute("insert into dirnotes values (?,datetime(?,'unixepoch','localtime'),?,?,datetime(?,'unixepoch','localtime'))",
-			(os.path.abspath(fileName), s.st_mtime, s.st_size, str(comment), time.time()))
-		self.db.commit()
+		print ("params: %s %s %d %s %s" % ((os.path.abspath(fileName), 
+				dnDataBase.epochToDb(s.st_mtime), s.st_size, comment, 
+				dnDataBase.epochToDb(time.time()))))
+		try:
+			c.execute("insert into dirnotes values (?,datetime(?,'unixepoch','localtime'),?,?,datetime(?,'unixepoch','localtime'))",
+				(os.path.abspath(fileName), s.st_mtime, s.st_size, str(comment), time.time()))
+			self.db.commit()
+		except sqlite3.OperationalError:
+			print("database is locked or unwriteable")
+			#TODO: put up a message box for locked database
+
+
 	@staticmethod
 	def epochToDb(epoch):
 		return time.strftime("%Y-%m-%d %H:%M:%S",time.localtime(epoch))
@@ -105,7 +109,7 @@ class DataBase:
 		return time.mktime(time.strptime(dbTime,"%Y-%m-%d %H:%M:%S"))
 	@staticmethod
 	def getShortDate(longDate):
-		e = DataBase.DbToEpoch(longDate)
+		e = dnDataBase.DbToEpoch(longDate)
 		sd = time.strptime(longDate,"%Y-%m-%d %H:%M:%S")
 		# check for this year, or today
 		ty = time.mktime((time.localtime()[0],1,1,0,0,0,0,1,-1))
@@ -118,10 +122,10 @@ class DataBase:
 			return time.strftime("%X",sd)
 	#~ test code for this routine
 	#~ for i in range(int(time.time() - 370*24*3600),
-			#~ int(time.time() + 48*3600),
-			#~ 3599):
-		#~ ds = DataBase.epochToDb(i)
-		#~ print("%d\t%s\t%s" % (i,ds,DataBase.getShortDate(ds)))
+		#~ int(time.time() + 48*3600),
+		#~ 3599):
+		#~ ds = dnDataBase.epochToDb(i)
+		#~ print("%d\t%s\t%s" % (i,ds,dnDataBase.getShortDate(ds)))
 				
 
 		
@@ -146,29 +150,31 @@ class FileObj():
 	def __init__(self, fileName):
 		self.fileName = fileName
 		s = os.lstat(fileName)
-		self.date = DataBase.epochToDb(s.st_mtime)
+		self.date = dnDataBase.epochToDb(s.st_mtime)
 		if stat.S_ISDIR(s.st_mode):
 			self.size = FileObj.FILE_IS_DIR
 		elif stat.S_ISLNK(s.st_mode):
 			self.size = FileObj.FILE_IS_LINK
 		else:
 			self.size = s.st_size
-		self.comment = ''
+		self.xattrComment = ''
 		try:
-			self.comment = xattr.get(fileName,COMMENT_KEY,nofollow=True)
+			self.xattrComment = xattr.get(fileName,COMMENT_KEY,nofollow=True).decode()
 		except Exception as e:
-			print("comment read on %s failed, execption %s" % (self.fileName,e)) 
+			#print("comment read on %s failed, execption %s" % (self.fileName,e)) 
 			pass
+
 	def getName(self):
 		return self.fileName
 	def getFileName(self):
 		return os.path.split(self.fileName)[1]
-	def getComment(self):
-		return self.comment
-	def setComment(self,newComment):
-		self.comment = newComment
+
+	def getXattrComment(self):
+		return self.xattrComment
+	def setXattrComment(self,newComment):
+		self.xattrComment = newComment
 		try:
-			xattr.set(self.fileName,COMMENT_KEY,self.comment,nofollow=True)
+			xattr.set(self.fileName,COMMENT_KEY,self.xattrComment,nofollow=True)
 			return True
 		# we need to move these cases out to a handler 
 		except Exception as e:
@@ -184,6 +190,15 @@ class FileObj():
 			elif "Errno 95" in str(e):
 				print("is this a VFAT or EXFAT volume? these don't allow comments")
 			return False
+
+	def getDbComment(self,db):
+		self.dbComment = ""
+		row = db.getData(self.fileName)
+		if row and len(row)>1:
+			self.dbComment = row[3]
+	def setDbComment(self,db,comment):
+		db.setData(self.fileName,comment)
+
 	def getDate(self):
 		return self.date
 	def getSize(self):
@@ -207,10 +222,12 @@ class HelpWidget(QDialog):
 # sortable TableWidgetItem, based on idea by Aledsandar
 # http://stackoverflow.com/questions/12673598/python-numerical-sorting-in-qtablewidget
 # NOTE: the QTableWidgetItem has setData() and data() which may allow data bonding
+# in Qt5, data() binding is more awkward, so do it here
 class SortableTableWidgetItem(QTableWidgetItem):
-	def __init__(self, text, sortValue):
+	def __init__(self, text, sortValue, file_object):
 		QTableWidgetItem.__init__(self, text, QTableWidgetItem.UserType)
 		self.sortValue = sortValue
+		self.file_object = file_object
 	def __lt__(self, other):
 		return self.sortValue < other.sortValue
 		
@@ -231,7 +248,7 @@ class DirNotes(QMainWindow):
 		lb = QTableWidget()
 		self.lb = lb
 		lb.setColumnCount(4)
-		lb.horizontalHeader().setResizeMode( 3, QHeaderView.Stretch );
+		lb.horizontalHeader().setSectionResizeMode( 3, QHeaderView.Stretch );
 		lb.verticalHeader().setDefaultSectionSize(20);	# thinner rows
 		lb.verticalHeader().setVisible(False)
 		
@@ -306,7 +323,7 @@ class DirNotes(QMainWindow):
 		print("closing")
 	def sbd(self):
 		print("sort by date")
-		self.lb.sortItems(1,QtCore.Qt.DescendingOrder)
+		self.lb.sortItems(1,Qt.DescendingOrder)
 	def sbs(self):
 		print("sort by size")
 		self.lb.sortItems(2)
@@ -317,12 +334,12 @@ class DirNotes(QMainWindow):
 		HelpWidget(self)
 	def sbc(self):
 		print("sort by comment")
-		self.lb.sortItems(3,QtCore.Qt.DescendingOrder)
+		self.lb.sortItems(3,Qt.DescendingOrder)
 	def newDir(self):
 		print("change dir to "+self.dirLeft.currentPath())
 	def double(self,row,col):
 		print("double click {} {}".format(row, col))
-		fo = self.lb.item(row,0).data(32).toPyObject()
+		fo = self.lb.item(row,0).file_object
 		if col==0 and fo.getSize() == FileObj.FILE_IS_DIR:
 			print("double click on {}".format(fo.getName()))
 			self.curPath = fo.getName()
@@ -339,8 +356,8 @@ class DirNotes(QMainWindow):
 		dirIcon = QIcon.fromTheme('folder')
 		fileIcon = QIcon.fromTheme('text-x-generic')
 		linkIcon = QIcon.fromTheme('emblem-symbolic-link')
-		current, dirs, files = os.walk(self.curPath,followlinks=True).next()
-		dirs = map(lambda x:x+'/', dirs)
+		current, dirs, files = next(os.walk(self.curPath,followlinks=True))
+		dirs = list(map(lambda x:x+'/', dirs))
 		dirs.sort()
 		files.sort()
 		
@@ -353,40 +370,52 @@ class DirNotes(QMainWindow):
 		#~ print("insert {} items into cleared table {}".format(len(d),current))
 		for i in range(len(d)):
 			this_file = FileObj(current+'/'+d[i])
+			this_file.getDbComment(self.db)
+			print("FileObj created as {} and the db-comment is <{}>".format(this_file.fileName, this_file.dbComment))
 			#~ print("insert order check: {} {} {} {}".format(d[i],i,this_file.getName(),this_file.getDate()))
 			#~ self.files.update({this_file.getName(),this_file})
 			self.files = self.files + [this_file]
 			if this_file.getSize() == FileObj.FILE_IS_DIR:
-				item = SortableTableWidgetItem(d[i],' '+d[i])	# directories sort first
+				item = SortableTableWidgetItem(d[i],' '+d[i], this_file)	# directories sort first
 			else:
-				item = SortableTableWidgetItem(d[i],d[i])
-			item.setFlags(QtCore.Qt.ItemIsEnabled)
-			item.setData(32,this_file)	# keep a hidden copy of the file object
+				item = SortableTableWidgetItem(d[i],d[i], this_file)
+			item.setFlags(Qt.ItemIsEnabled)
+			#item.setData(32,this_file)	# keep a hidden copy of the file object
 			item.setToolTip(this_file.getName())
 			self.lb.setItem(i,0,item)
 			#lb.itemAt(i,0).setFlags(Qt.ItemIsEnabled) #NoItemFlags) 
-			comment = this_file.getComment()
+
+			# get the comment from database & xattrs, either can fail
+			comment = "" 
+			row = self.db.getData(this_file.getName())
+			if row and len(row)>0:
+				comment = row[3]
+			xattrComment = this_file.getXattrComment()
 			self.lb.setItem(i,3,QTableWidgetItem(comment))
+			if xattrComment != comment:
+				self.lb.item(i,3).setBackground(QBrush(Qt.yellow))
+				print("got two comments <{}> and <{}>".format(comment, xattrComment))
+
 			dt = this_file.getDate()
-			da = SortableTableWidgetItem(DataBase.getShortDate(dt),dt)
+			da = SortableTableWidgetItem(dnDataBase.getShortDate(dt),dt,this_file)
 			#da.setFont(small_font)
 			da.setToolTip(dt)
-			da.setFlags(QtCore.Qt.ItemIsEnabled)
+			da.setFlags(Qt.ItemIsEnabled)
 			self.lb.setItem(i,1,da)
 			si = this_file.getSize()
 			if si==FileObj.FILE_IS_DIR:
-				sa = SortableTableWidgetItem('',0)
+				sa = SortableTableWidgetItem('',0,this_file)
 				item.setIcon(dirIcon)
 			elif si==FileObj.FILE_IS_LINK:
-				sa = SortableTableWidgetItem('symlink',-1)
+				sa = SortableTableWidgetItem('symlink',-1,this_file)
 				item.setIcon(linkIcon)
 				dst = os.path.realpath(this_file.getName())
 				sa.setToolTip("symlink: " + dst)
 			else:
-				sa = SortableTableWidgetItem(str(si),si)
+				sa = SortableTableWidgetItem(str(si),si,this_file)
 				item.setIcon(fileIcon)
-			sa.setTextAlignment(QtCore.Qt.AlignRight)
-			sa.setFlags(QtCore.Qt.ItemIsEnabled)
+			sa.setTextAlignment(Qt.AlignRight)
+			sa.setFlags(Qt.ItemIsEnabled)
 			self.lb.setItem(i,2,sa)
 		self.refilling = False
 		self.lb.sortingEnabled = True
@@ -396,16 +425,17 @@ class DirNotes(QMainWindow):
 		if self.refilling:
 			return
 		print("debugging " + x.text() + " r:" + str(x.row()) + " c:" + str(x.column()))
-		print("      selected file: "+self.lb.item(x.row(),0).data(32).toPyObject().getName())
-		the_file = self.lb.item(x.row(),0).data(32).toPyObject()
+		print("      selected file: "+self.lb.item(x.row(),0).file_object.getName())
+		the_file = self.lb.item(x.row(),0).file_object
 		print("      and the row file is "+the_file.getName())
-		r = the_file.setComment(str(x.text())) # TODO: change priority
+		r = the_file.setXattrComment(str(x.text())) # TODO: change priority
 		if r:
-			self.db.log(the_file.getName(),x.text())
+			self.db.setData(the_file.getName(),x.text())
+
 	def restore_from_database(self):
 		print("restore from database")
 		# retrieve the full path name
-		fileName = str(self.lb.item(self.lb.currentRow(),0).data(32).toPyObject().getName())
+		fileName = str(self.lb.item(self.lb.currentRow(),0).file_object.getName())
 		print("using filename: "+fileName)
 		existing_comment = str(self.lb.item(self.lb.currentRow(),3).text())
 		print("restore....existing="+existing_comment+"=")
@@ -431,8 +461,9 @@ if __name__=="__main__":
 		p.dirname = p.dirname + '/'
 	print(p.dirname)
 	
-	db = DataBase()
+	db = dnDataBase()
 
+	# TODO: if there's a file specified, jump to it
 	a = QApplication([])
 	dn = DirNotes(p.dirname,db)
 	dn.show()
@@ -471,7 +502,4 @@ user.xdg.publisher
 ''' commandline xattr
 getfattr -h (don't follow symlink) -d (dump all properties)
 '''
-''' qt set color
-               newitem.setData(Qt.BackgroundRole,QBrush(QColor("yellow")))
-'''
 ''' if the args line contains a file, jump to it '''