Преглед на файлове

moving back to python2, so the xattr library works; dirWidget2 converted

Pat Beirne преди 9 години
родител
ревизия
d7a8120045
променени са 2 файла, в които са добавени 273 реда и са изтрити 11 реда
  1. 7 11
      dirWidget2.py
  2. 266 0
      dirnotes2

+ 7 - 11
dirWidget2.py

@@ -1,9 +1,9 @@
-#!/usr/bin/python3
+#!/usr/bin/python
 ''' a widget that shows only a dir listing
  '''
 
 import sys,os,argparse
-from tkinter import *
+from Tkinter import *
 
 class DirWidget(Listbox):
 	''' a simple widget that shows a list of directories, staring
@@ -17,11 +17,11 @@ class DirWidget(Listbox):
 	the most interesting parts of the interface are:
 	constructor - send in the directory to view
 	method - currentPath() returns text of current path
-	signal - selected calls a slot with a single arg: new path
+	new event <<DirChanged>>
 	'''
 
 	def __init__(self, parent, directory='.'):
-		super(DirWidget,self).__init__(parent)
+		Listbox.__init__(self,parent)
 		self.directory = directory
 		self.refill()
 		self.bind("<Key>",self.quickFind)
@@ -34,16 +34,14 @@ class DirWidget(Listbox):
 		self.refill()
 		self.event_generate("<<DirChanged>>")
 	def refill(self):
-		current,dirs,files =  os.walk(self.directory,followlinks=True).__next__()
+		current,dirs,files =  os.walk(self.directory,followlinks=True).next()
 		dirs = sorted(dirs, key=lambda s: s.lower())
 		if '/' not in dirs:
 			dirs = ['..'] + dirs
 		self.delete(0,END)
 		for d in dirs:
-			#li = QListWidgetItem(d,self)
-			# replace this with an insert
 			self.insert(END,d)
-		self.selection_set(0)	
+		#self.selection_set(0)	
 	def currentPath(self):
 		return self.directory
 	def quickFind(self,event):
@@ -52,9 +50,7 @@ class DirWidget(Listbox):
 		event.char = event.char.lower()
 		if event.char not in 'abcdefghijklmnopqrstuvwxyz0123456789_-.%':
 			return
-		print("quickFind: "+event.char+"  ")
 		for i in range(self.size()):
-			print("qf get: " + self.get(i))
 			if self.get(i)[0].lower()==event.char:
 				self.activate(i)
 				self.see(i)
@@ -62,7 +58,7 @@ class DirWidget(Listbox):
 		
 if __name__=="__main__":
 	def alert(some_event):
-		print("dirWidget hit" + str(some_event))
+		print("dirWidget hit dir: " + some_event.widget.currentPath())
 		os.system('notify-send -t 1500 "file: ' + 
 			some_event.widget.currentPath() + '"')
 

+ 266 - 0
dirnotes2

@@ -0,0 +1,266 @@
+#!/usr/bin/python3
+""" a simple gui or command line app
+to view and create/edit file comments
+
+comments are stored in xattr user.xdg.comment
+
+depends on python-pyxattr
+
+because files are so often over-written, save a copy
+of the comments in a database ~/.dirnotes.db
+
+these comments stick to the symlink
+
+"""
+
+import sys,os,argparse
+from dirWidget2 import DirWidget
+from tkinter import *
+import xattr, sqlite3, time
+
+VERSION = "0.2"
+COMMENT_KEY = "user.xdg.comment"
+DATABASE_NAME = "~/.dirnotes.db"
+# convert the ~/ form to a fully qualified path
+DATABASE_NAME = os.path.expanduser(DATABASE_NAME)
+
+class DataBase:
+	''' the database is flat
+		fileName: fully qualified name
+		st_mtime: a float
+		size: a long
+		comment: a string
+		comment_time: a float, the time of the comment save
+		this is effectively a log file, as well as a resource for a restore
+			(in case a file-move is done without comment)
+		the database is associated with a user, in the $HOME dir
+	'''
+	def __init__(self):
+		'''try to open the database; if not found, create it'''
+		try:
+			self.db = sqlite3.connect(DATABASE_NAME)
+		except sqlite3.OperationalError:
+			print("Database %s not found" % DATABASE_NAME)
+			raise OperationalError
+		c = self.db.cursor()
+		try:
+			c.execute("select * from dirnotes")
+		except sqlite3.OperationalError:
+			print("Table %s created" % ("dirnotes"))
+			c.execute("create table dirnotes (name TEXT, date DATETIME, size INTEGER, comment TEXT, comment_date DATETIME)")
+		
+	def getData(self, fileName):
+		c = self.db.cursor()
+		c.execute("select * from dirnotes where name=? 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):
+		''' TODO: convert filename to canonical '''
+		c = self.db.cursor()
+		s = os.stat(fileName)
+		print ("params: %s %f %d %s %f" % ((os.path.abspath(fileName), s.st_mtime, s.st_size, comment, 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()
+
+def parse():
+	parser = argparse.ArgumentParser(description='dirnotes application')
+	parser.add_argument('dirname',metavar='dirname',type=str,
+		help='directory [default=current dir]',default=".",nargs='?')
+	parser.add_argument('dirname2',help='comparison directory, shows two-dirs side-by-side',nargs='?')
+	parser.add_argument('-n','--nogui',action="store_const",const="1",
+		help='use text base interface')
+	parser.add_argument('-v','--version',action='version',version='%(prog)s '+VERSION)
+	group = parser.add_mutually_exclusive_group()
+	group.add_argument('-s','--sort-by-name',metavar='sort',action="store_const",const='n')
+	group.add_argument('-m','--sort-by-date',metavar='sort',action='store_const',const='d')
+	return parser.parse_args()
+
+
+class FileObj():
+	def __init__(self, fileName):
+		self.fileName = fileName
+		# also get the date, directory or not, etc
+		self.comment = ''
+		try:
+			self.comment = xattr.getxattr(fileName,COMMENT_KEY)
+		except Exception as e:
+			#print("comment read on %s failed, execption %s" % (self.fileName,e)) 
+			pass
+	def getName(self):
+		return self.fileName
+	def getComment(self):
+		return self.comment
+	def setComment(self,newComment):
+		self.comment = newComment
+		try:
+			xattr.setxattr(self.fileName,COMMENT_KEY,self.comment)
+			return True
+		# we need to move these cases out to a handler 
+		except Exception as e:
+			print("problem setting the comment on file %s" % (self.fileName,))
+			if os.access(self.fileName, os.W_OK)!=True:
+				print("you don't appear to have write permissions on this file")
+				# change the listbox background to yellow
+				self.displayBox.notifyUnchanged()				
+			elif "Errno 95" in str(e):
+				print("is this a VFAT or EXFAT volume? these don't allow comments")
+			return False
+
+class DirNotes(Frame):
+	''' the main window of the app
+		has 3 list boxes: dir_left, dir_right (may be invisible) and files
+		
+		'''
+	def __init__(self, filename, db, parent=None):
+		super(DirNotes,self).__init__(parent)
+		self.db = db
+
+		win = QWidget()
+		self.setCentralWidget(win)
+
+		lb = QTableWidget()
+		self.lb = lb
+		lb.setColumnCount(2)
+		lb.horizontalHeader().setResizeMode( 1, QHeaderView.Stretch );
+		lb.verticalHeader().setDefaultSectionSize(20);	# thinner rows
+		lb.verticalHeader().setVisible(False)
+		
+		# resize the comments column
+		# and resize the parent window to match the directory size
+
+		# allow multiple entries on the line at this point
+		#d = os.listdir(p.filename[0])
+		#d.sort()
+		current, dirs, files = os.walk(filename,followlinks=True).next()
+		dirs = map(lambda x:x+'/', dirs)
+		dirs.sort()
+		files.sort()
+		
+		d = dirs + files
+		lb.setRowCount(len(d))
+
+		self.files = []
+		for i in range(len(d)):
+			this_file = FileObj(current+'/'+d[i])
+			self.files = self.files + [this_file]
+			item = QTableWidgetItem(this_file.getName())
+			item.setFlags(QtCore.Qt.ItemIsEnabled)
+			lb.setItem(i,0,item)
+			#lb.itemAt(i,0).setFlags(Qt.ItemIsEnabled) #NoItemFlags)
+			comment = this_file.getComment()
+			lb.setItem(i,1,QTableWidgetItem(comment))
+		lb.setHorizontalHeaderItem(0,QTableWidgetItem("file"))
+		lb.setHorizontalHeaderItem(1,QTableWidgetItem("comment"))
+		lb.resizeColumnsToContents()
+
+		e = QLabel("View and edit file comments stored in extended attributes user.xdg.comment",win)
+
+		b1 = QPushButton("restore from database",win)
+		dirLeft = DirWidget(current,win)
+		dirLeft.setMaximumHeight(140)
+		dirLeft.setMaximumWidth(200)
+		dirRight = DirWidget(current,win)
+		dirRight.setMaximumHeight(140)
+		dirRight.setMaximumWidth(200)
+		dirRight.setEnabled(False)
+		
+		layout = QVBoxLayout()
+		upperLayout = QHBoxLayout()
+		layout.addWidget(e)
+		upperLayout.addWidget(dirLeft)
+		upperLayout.addWidget(b1)
+		upperLayout.addWidget(dirRight)
+		layout.addLayout(upperLayout)
+		layout.addWidget(lb)
+		win.setLayout(layout)
+		
+		lb.itemChanged.connect(self.change)
+		b1.pressed.connect(self.restore_from_database)
+
+		QShortcut(QKeySequence("Ctrl+Q"), self, self.close)	
+		self.setWindowTitle("test")
+		self.setMinimumSize(600,400)
+	def closeEvent(self,e):
+		print("closing")
+		
+	def change(self,x):
+		print("debugging " + x.text() + " r:" + str(x.row()) + " c:" + str(x.column()))
+		the_file = dn.files[x.row()]
+		r = the_file.setComment(str(x.text())) 
+		if r:
+			self.db.log(the_file.getName(),x.text())
+	def restore_from_database(self):
+		print("restore from database")
+		fileName = str(self.lb.item(self.lb.currentRow(),0).text())
+		fo_row = self.db.getData(fileName)
+		if len(fo_row)>1:
+			comment = fo_row[3]
+			print(fileName,fo_row[0],comment)
+		
+if __name__=="__main__":
+	p = parse()
+	if p.dirname[-1]=='/':
+		p.dirname = p.dirname[:-1]
+	print(p.dirname)
+	
+	db = DataBase()
+
+	tk_basis = Tk()
+	dn = DirNotes(tk_basis,p.dirname,db)
+	dn.pack()
+	
+	mainloop()
+	
+	#xattr.setxattr(filename,COMMENT_KEY,commentText)
+
+
+''' files from directories
+use os.isfile()
+os.isdir()
+current, dirs, files = os.walk("path").next()
+possible set folllowLinks=True'''
+
+''' notes from the wdrm project
+table showed 
+filename, size, date size, date, desc
+
+at start, fills the list of all the files
+skip the . entry
+'''
+
+''' should we also do user.xdg.tags="TagA,TagB" ?
+user.charset
+user.creator=application_name or user.xdg.creator
+user.xdg.origin.url
+user.xdg.language=[RFC3066/ISO639]
+user.xdg.publisher
+'''
+
+''' to allow column-sorting, you use the sortByColumn and set the Horiz-header to clickable
+'''
+
+''' TODO: also need a way to display-&-restore comments from the database '''
+
+''' QFileDialog
+	-make my own?
+	-existing one has
+		-history
+		-back button
+		-up button
+		-but we don't need
+			-directory date
+			-icon option
+			-url browser (unless we go network file system)
+			-new folder button
+			-file type chooser
+			-text entry box
+			-choose & cancel buttons
+	
+	'''
+