Browse Source

added config file in .json format

Pat Beirne 1 year ago
parent
commit
c0397934e2
1 changed files with 118 additions and 48 deletions
  1. 118 48
      c_dirnotes.py

+ 118 - 48
c_dirnotes.py

@@ -1,4 +1,4 @@
-#!/usr/bin/python3 
+#!/usr/bin/env python3 
 
 # TOTO: when changing directories, keep the sort order?
 # TODO: fix the 'reload' function....need 'this_dir' in Files class
@@ -21,23 +21,22 @@ import os, time, stat, sys, shutil
 
 import time, math
 import curses, sqlite3, curses.textpad
-import logging, getpass
+import logging, getpass, argparse
+import json
 
-VERSION = "1.4"
+VERSION = "1.5"
 # these may be different on MacOS
-XATTR_COMMENT = "user.xdg.comment"
-XATTR_AUTHOR = "user.xdg.comment.author"
-XATTR_DATE = "user.xdg.comment.date"
-COMMENT_OWNER = os.getlogin()
+xattr_comment = "user.xdg.comment"
+xattr_author = "user.xdg.comment.author"
+xattr_date = "user.xdg.comment.date"
+DEFAULT_CONFIG_FILE = "~/.dirnotes.conf"
 
 # convert the ~/ form to a fully qualified path
-DATABASE_NAME = "~/.dirnotes.db"
-DATABASE_NAME = os.path.expanduser(DATABASE_NAME)   # doesn't deref symlinks
+# database_name = "~/.dirnotes.db"
 
-MODE_DATABASE = 0
-MODE_XATTR = 1
-mode_names = ["<Database mode> ","<Xattr mode>"]
-mode = MODE_XATTR
+mode_names = {"db":"<Database mode> ","xattr":"<Xattr mode>"}
+modes = ("db","xattr")
+mode = "db"
 
 ### commands
 CMD_COPY   = ord('c')  # open dialog to copy-with-comment
@@ -69,6 +68,16 @@ CMD_CD     = ord('\n')
 # file comments will ALWAYS be written to both xattrs & database
 #   access failure is shown once per directory
 
+# config
+#    we could store the config in the database, in a second table
+#    or in a .json file
+DEFAULT_CONFIG = {"xattr_tag":"user.xdg.comment",
+  "database":"~/.dirnotes.db",
+  "start_mode":"xattr",
+  "options for database":("~/.dirnotes.db","/etc/dirnotes.db"),
+  "options for start_mode":("db","xattr")
+}
+
 ### colors
 
 CP_TITLE  = 1
@@ -189,7 +198,7 @@ class Pane:
 
     dbComment = f.getDbComment()
     xattrComment = f.getXattrComment()
-    comment = xattrComment if mode==MODE_XATTR else dbComment
+    comment = xattrComment if mode=="xattr" else dbComment
     if dbComment != xattrComment:
       self.some_comments_differ = True
       self.file_pad.attron(COLOR_HELP)
@@ -279,7 +288,7 @@ class Files():
 
     self.db = None
     try:
-      self.db = sqlite3.connect(DATABASE_NAME)
+      self.db = sqlite3.connect(database_name)
       c = self.db.cursor()
       c.execute("select * from dirnotes")
     except sqlite3.OperationalError:
@@ -296,8 +305,12 @@ class Files():
 
 
   def sortName(a):
+    ''' when sorting by name put the .. and other <dir> entries first '''
     if a.getFileName() == '..':
       return "\x00"
+    if a.isDir():
+      return ' ' + a.getFileName()
+    # else:
     return a.getFileName()
 
   def sortDate(a):
@@ -310,15 +323,18 @@ class Files():
       return 0
     return a.getSize()
 
-  def getCurDir(self):
-    return self.directory
-  def getMasterComment(self):
-    return self.directory.xattrComment if mode==MODE_XATTR else self.directory.dbComment
+  def sortComment(a):
+    return a.getComment()
 
   sort_mode = sortName
   def sort(self):
     self.files.sort(key = Files.sort_mode)
 
+  def getCurDir(self):
+    return self.directory
+  def getMasterComment(self):
+    return self.directory.getComment()
+
   ## accessors ##
   def __len__(self):
     return len(self.files)
@@ -361,9 +377,9 @@ class FileObj():
     self.dbDate = None
     self.commentsDiffer = False
     try:
-      self.xattrComment = os.getxattr(fileName, XATTR_COMMENT, follow_symlinks=False).decode()
-      self.xattrAuthor = os.getxattr(fileName, XATTR_AUTHOR, follow_symlinks=False).decode()
-      self.xattrDate = float(os.getxattr(fileName, XATTR_DATE, follow_symlinks=False).decode())
+      self.xattrComment = os.getxattr(fileName, xattr_comment, follow_symlinks=False).decode()
+      self.xattrAuthor = os.getxattr(fileName, xattr_author, follow_symlinks=False).decode()
+      self.xattrDate = float(os.getxattr(fileName, xattr_date, follow_symlinks=False).decode())
       self.commentsDiffer = True if self.xattrComment == self.dbComment else False
     except:  # no xattr comment
       pass
@@ -389,9 +405,9 @@ class FileObj():
     return self.dbDate
   def setDbComment(self,newComment):
     try:
-      self.db = sqlite3.connect(DATABASE_NAME)
+      self.db = sqlite3.connect(database_name)
     except sqlite3.OperationalError:
-      logging.info(f"database {DATABASE_NAME} not found")
+      logging.info(f"database {database_name} not found")
       raise OperationalError
     c = self.db.cursor()
     s = os.lstat(self.fileName)
@@ -417,9 +433,9 @@ class FileObj():
   def setXattrComment(self,newComment):
     logging.info(f"set comment {newComment} on file {self.fileName}")
     try:
-      os.setxattr(self.fileName,XATTR_COMMENT,bytes(newComment,'utf8'),follow_symlinks=False)
-      os.setxattr(self.fileName,XATTR_AUTHOR,bytes(getpass.getuser(),'utf8'),follow_symlinks=False)
-      os.setxattr(self.fileName,XATTR_DATE,bytes(str(time.time()),'utf8'),follow_symlinks=False)
+      os.setxattr(self.fileName,xattr_comment,bytes(newComment,'utf8'),follow_symlinks=False)
+      os.setxattr(self.fileName,xattr_author,bytes(getpass.getuser(),'utf8'),follow_symlinks=False)
+      os.setxattr(self.fileName,xattr_date,bytes(str(time.time()),'utf8'),follow_symlinks=False)
       self.xattrAuthor = getpass.getuser()
       self.xattrDate = time.time()      # alternatively, re-instantiate this FileObj
       self.xattrComment = newComment
@@ -440,8 +456,12 @@ class FileObj():
         errorBox("is this a VFAT or EXFAT volume? these don't allow comments")
       return False
 
+  def getComment(self):
+    return self.getDbComment() if mode == "db" else self.getXattrComment()
+  def getOtherComment(self):
+    return self.getDbComment() if mode == "xattr" else self.getXattrComment()
   def getDate(self):
-   return self.date
+    return self.date
   def getSize(self):
     return self.size
   def isDir(self):
@@ -596,17 +616,18 @@ def show_help2():
   del w
 
 #TODO: fix this to paint_dialog
+#TODO: fix to allow upper/lower case responses
 sort_string = """
 Select sort order: 
  
-  Name
-  Date
-  Size
-  Comment"""
+  name
+  date
+  size
+  comment"""
 def show_sort():
   h = paint_dialog(COLOR_HELP,sort_string)
   h.attron(COLOR_TITLE)
-  h.addstr(3,3,"N") or h.addstr(4,3,"D") or h.addstr(5,3,"S") or h.addstr(6,3,"C")
+  h.addstr(3,3,"n") or h.addstr(4,3,"d") or h.addstr(5,3,"s") or h.addstr(6,3,"c")
   h.attroff(COLOR_TITLE)
   h.refresh()
   c = h.getch()
@@ -622,7 +643,7 @@ Comments detail:
 def show_detail(f):
   global mode
   h = paint_dialog(COLOR_HELP,detail_string)
-  if mode==MODE_XATTR:
+  if mode=="xattr":
     h.addstr(1,20,"from xattrs")
     c = f.getXattrComment()
     a = f.getXattrAuthor()
@@ -652,11 +673,10 @@ def edit_fn(c):
     return 7
   return c
 
-def main(w):
+def main(w, cwd):
   global files, edit_done, mode
   global COLOR_TITLE, COLOR_BODY, COLOR_FOCUS, COLOR_ERROR, COLOR_HELP
-  logging.basicConfig(filename='/tmp/dirnotes.log', level=logging.DEBUG)
-  logging.info("starting curses dirnotes")
+
   curses.init_pair(CP_TITLE, curses.COLOR_YELLOW,curses.COLOR_BLUE)
   curses.init_pair(CP_BODY,  curses.COLOR_WHITE,curses.COLOR_BLUE)
   curses.init_pair(CP_FOCUS, curses.COLOR_BLACK,curses.COLOR_CYAN)
@@ -672,10 +692,6 @@ def main(w):
   COLOR_DIFFER = curses.color_pair(CP_DIFFER)
   logging.info(f"COLOR_DIFFER is {COLOR_DIFFER}")
 
-  if len(sys.argv) > 1:
-    cwd = sys.argv[1]
-  else:
-    cwd = os.getcwd()
   files = Files(cwd)
   logging.info(f"got files, len={len(files)}")
 
@@ -695,12 +711,14 @@ def main(w):
 
     elif c == CMD_SORT:
       c = show_sort()
-      if c == ord('s'):
+      if c == ord('s') or c == ord('S'):
         Files.sort_mode = Files.sortSize
-      elif c == ord('n'):
+      elif c == ord('n') or c == ord('N'):
         Files.sort_mode = Files.sortName
-      elif c == ord('d'):
+      elif c == ord('d') or c == ord('D'):
         Files.sort_mode = Files.sortDate
+      elif c == ord('c') or c == ord('C'):
+        Files.sort_mode = Files.sortComment
       files.sort()
       mywin.refill()
       mywin.refresh()
@@ -723,7 +741,7 @@ def main(w):
       mywin.refresh()
 
     elif c == CMD_MODE:
-      mode = MODE_DATABASE if mode==MODE_XATTR else MODE_XATTR
+      mode = "db" if mode=="xattr" else "xattr"
       mywin.refill()
       mywin.refresh()
 
@@ -778,7 +796,7 @@ def main(w):
       cp_cmnt_ask = curses.newwin(6,40,5,5)
       cp_cmnt_ask.border()
       cp_cmnt_ask.addstr(1,1,"Copy comments to ==> ")
-      cp_cmnt_ask.addstr(1,22,"database" if mode==MODE_XATTR else "xattr")
+      cp_cmnt_ask.addstr(1,22,"database" if mode=="xattr" else "xattr")
       cp_cmnt_ask.addstr(2,1,"1  just this file")
       cp_cmnt_ask.addstr(3,1,"a  all files with comments")
       cp_cmnt_ask.addstr(4,1,"esc to cancel")
@@ -794,7 +812,7 @@ def main(w):
       else:
         collection = files
       for f in collection:
-        if mode==MODE_XATTR:
+        if mode=="xattr":
           if f.getXattrComment():
             f.setDbComment(f.getXattrComment())
         else:
@@ -846,6 +864,58 @@ def main(w):
       mywin.resize()
     #mywin.refresh()
 
-curses.wrapper(main)
+def pre_main():
+  # done before we switch to curses mode
+  logging.basicConfig(filename='/tmp/dirnotes.log', level=logging.DEBUG)
+  logging.info("starting curses dirnotes")
+
+  parser = argparse.ArgumentParser(description="Add comments to files")
+  parser.add_argument('-c','--config',  dest='config_file', help="config file (json format)")
+  parser.add_argument('-v','--version', action='version', version=f"dirnotes ver:{VERSION}")
+  parser.add_argument('directory', type=str, default='.', nargs='?',  help="directory to start")
+  args = parser.parse_args()
+  logging.info(args)
+
+  if args.config_file:
+    config_file = args.config_file
+  else:
+    config_file = DEFAULT_CONFIG_FILE
+  config_file = os.path.expanduser(config_file)
+
+  config = DEFAULT_CONFIG
+  try:
+    with open(config_file,"r") as f:
+      config = json.load(f)
+  except json.JSONDecodeError:
+    print(f"problem reading the config file {config_file}")
+    printf("please check the .json syntax")
+    time.sleep(2)
+  except FileNotFoundError:
+    print(f"config file {config_file} not found, using default settings & creating a default")
+    try:
+      with open(config_file,"w") as f:
+        json.dump(config,f,indent=4)
+    except:
+      print(f"problem creating file {config_file}")
+    time.sleep(2)
+
+  # print(repr(config))
+  # print("start_mode",config["start_mode"])
+  
+  # change ~ tilde to username
+  config["database"] = os.path.expanduser(config["database"])
+  # load globals from the config
+  return args, config
+
+args, config = pre_main()
+
+mode = config["start_mode"]
+xattr_comment = config["xattr_tag"]
+xattr_author  = config["xattr_tag"] + ".author"
+xattr_date    = config["xattr_tag"] + ".date"
+database_name = os.path.expanduser(config["database"])
+cwd = args.directory
+
+curses.wrapper(main, cwd)
 
 # dirnotes database is name, date, size, comment, comment_date, author