Browse Source

so many changes

Pat Beirne 1 year ago
parent
commit
67214ba444
4 changed files with 821 additions and 662 deletions
  1. 26 9
      README.md
  2. 624 483
      dirnotes
  3. 170 170
      dirnotes-cli
  4. 1 0
      dirnotes-cli.md

+ 26 - 9
README.md

@@ -40,6 +40,8 @@ _s_ for sort, _M_ to change between xattr/database priority.
 
 The **<code>dirnotes-cli</code>** has options for _-l_ list and _-c_ create a comment. 
 
+All three apps in the **dirnotes** family have the ability to copy files from the current directory. 
+ 
 ## INSTALLATION
 
 Each of the 3 apps in the family is self contained. 
@@ -55,6 +57,7 @@ This is a JSON file, with three attributes that are important:
 > * database (default: <code>~/.dirnotes.db</code>) 
 > * start_mode (_xattr_ or _db_ priority)
 
+The _config_file_ should be auto-generated the first time one of the **dirnotes** apps is run.
 [_not fully implemented_]
 
 ## LIMITATIONS 
@@ -62,12 +65,13 @@ This is a JSON file, with three attributes that are important:
 
 The file comments are located in two locations: a database, and in the 
 xattr properties of the file. Each of these storage locations has its 
-own benefits and limitations:
+own benefits and limitations. These can be summed up: **_xattr_** comments
+follow the iNode, **_database_** comments follow the file name.
 
 ### xattr
 
 Comments stored in the xattr properties can be copied/moved with the file, if you
-use the correct options for <code>**cp**</code>. The <code>**mv**</code> utility 
+use the correct options: <code>**cp -p**</code>. The <code>**mv**</code> utility 
 automatically preserves _xattr_. Other programs can also be coerced into 
 perserving _xattr_ properties:
 
@@ -136,7 +140,7 @@ The comments are stored in an Sqlite3 database, usually located at "~/.dirnotes.
 |author     |   the system name of the user who created the comment  |  patb | 
 
 
-The 'date' and 'size' fields reflect the file's modification date and size at the time of the last edit of the file comment, which is also 'comment_date'.
+The _date_ and _size_ fields reflect the file's modification date and size at the time of the last edit of the file comment, which is stored in _comment_date_.
 
 As comments are editted or appended, new records are added to the database. Older records are are not purged. This gives you a history of the comments, but it means that fetching the most recent comment involves something like
 
@@ -146,6 +150,14 @@ As comments are editted or appended, new records are added to the database. Olde
 
 and just fetch the first record.
 
+The database is created the first time one of the **dirnotes** apps is run.
+
+  * misc
+
+The <code>**dirnotes**</code> gui app has a desktop icon built into the code. There is not need for an external .icon file.
+
+There was _no_ consideration given for language translation. Email [me](mail:patb@pbeirne.com) if you want this, or can help.
+
 ### MacOS {#macos}
 
 The **MacOS** inherently supports file comments. The Finder app manages most of the user activity. It handles file comments in a similar manner to **Dirnotes**. Comments are stored in two places:
@@ -164,12 +176,17 @@ If the Finder is used to copy/move files, the comments are moved properly to bot
 
 ## DEVELOPMENT STATUS{#status}
 
-Each app is a standalone file. That means there is a lot of redundancy between the tthree apps. And there _may_ be some inconsistency.
+Each app is a standalone file. That means there is a lot of redundancy between 
+the three apps. And there _may_ be some inconsistency.
 
 2022-10-04
 : All three apps are functioning and usable.  
-  The _config_file_ is fully implemented. 
-  Themes are not implemented. 
-  Comments are intended to be _utf-8_, but are _strings_ in some places. 
-  MacOS code is not written yet. 
-  The _help_ dialogs in <code>**dirnotes-tui**</code> are meagre.
+  The _config_file_ is fully implemented.  
+  Themes are not implemented.   
+  Comments are intended to be _utf-8_, but are _strings_ in some places.  
+  MacOS code is not written yet.   
+  The _help_ dialogs in <code>**dirnotes-tui**</code> are meagre.    
+  The _qt-gui_ app is working pretty well.  
+
+
+

File diff suppressed because it is too large
+ 624 - 483
dirnotes


+ 170 - 170
dirnotes-cli

@@ -9,197 +9,197 @@ answer_json = []
 verbose = 0
 db = None
 xattr_comment = "user.xdg.comment"
-xattr_author  = "user.xdg.comment.author"
-xattr_date    = "user.xdg.comment.date"
+xattr_author    = "user.xdg.comment.author"
+xattr_date      = "user.xdg.comment.date"
 
 #======= debugging/verbose ===========
 def print_d(*a):
-  if verbose > 1:
-    print('>>',*a)
+    if verbose > 1:
+        print('>>',*a)
 def print_v(*a):
-  if verbose:
-    print('>',*a)
+    if verbose:
+        print('>',*a)
 
 #============= the functions that are called from the main.loop ===============
 
 def file_copy(f,target,target_is_dir,force):
-  print_d(f"call file_copy with args={target},{target_is_dir} and {force}")
-  dest = target if not target_is_dir else target+'/'+os.path.basename(f)
-  if os.path.exists(dest) and not force:
-    go = input("The copy target <<" + dest + ">> exists. Overwrite? (y or n) ")
-    if go != 'y' and go != 'Y':
-      return
-  print_d(f"copy from {f} to {dest}")
+    print_d(f"call file_copy with args={target},{target_is_dir} and {force}")
+    dest = target if not target_is_dir else target+'/'+os.path.basename(f)
+    if os.path.exists(dest) and not force:
+        go = input("The copy target <<" + dest + ">> exists. Overwrite? (y or n) ")
+        if go != 'y' and go != 'Y':
+            return
+    print_d(f"copy from {f} to {dest}")
 
 def file_zap(f,all_flag):
-  print_d(f"zapping the comment history of {f}")
-  if all_flag:
-    print_d("zapping the entire database")
+    print_d(f"zapping the comment history of {f}")
+    if all_flag:
+        print_d("zapping the entire database")
 
 def file_modify_comment(f, create, append, erase):
-  print_d(f"modify the comment on file {f} with extra={(create,append,erase)}")
-  if not os.path.exists(f):
-    print(f"the target file does not exist; please check the spelling of the file: {f}")
-    # sys.exit() here?
+    print_d(f"modify the comment on file {f} with extra={(create,append,erase)}")
+    if not os.path.exists(f):
+        print(f"the target file does not exist; please check the spelling of the file: {f}")
+        # sys.exit() here?
 
 def file_display(f, listall, history, json, minimal):
-  print_d(f"list file details {f}")
-  x_comment = None
-  try:
-    x_comment = xattr.getxattr(f,xattr_comment).decode()
-    x_author  = xattr.getxattr(f,xattr_author).decode()
-    x_date    = xattr.getxattr(f,xattr_date).decode()
-  except:
-    pass
-  full_f = os.path.realpath(f)
-
-  d_comment = getDbComment(full_f)
-  if d_comment:
-    d_comment, d_author, d_date = d_comment
-  print_d(f"for file {f}, database comment is <{d_comment}>, xattr comment is <{x_comment}>")
-
-  if os.path.isdir(f):
-    f = f+'/'
-  if x_comment or listall:
-    if x_comment and (d_comment != x_comment):
-      x_comment += '*'
-    if not json:
-      if minimal:
-        print(f"{x_comment}")
-      else:
-        print(f"{f}: {x_comment}")
-    else:
-      if verbose:
-        answer_json.append( {"file":f,"comment":x_comment,"author":x_author,"date":x_date } )
-      else:
-        answer_json.append( {"file":f,"comment":x_comment} )
+    print_d(f"list file details {f}")
+    x_comment = None
+    try:
+        x_comment = xattr.getxattr(f,xattr_comment).decode()
+        x_author    = xattr.getxattr(f,xattr_author).decode()
+        x_date      = xattr.getxattr(f,xattr_date).decode()
+    except:
+        pass
+    full_f = os.path.realpath(f)
+
+    d_comment = getDbComment(full_f)
+    if d_comment:
+        d_comment, d_author, d_date = d_comment
+    print_d(f"for file {f}, database comment is <{d_comment}>, xattr comment is <{x_comment}>")
+
+    if os.path.isdir(f):
+        f = f+'/'
+    if x_comment or listall:
+        if x_comment and (d_comment != x_comment):
+            x_comment += '*'
+        if not json:
+            if minimal:
+                print(f"{x_comment}")
+            else:
+                print(f"{f}: {x_comment}")
+        else:
+            if verbose:
+                answer_json.append( {"file":f,"comment":x_comment,"author":x_author,"date":x_date } )
+            else:
+                answer_json.append( {"file":f,"comment":x_comment} )
 
 def getDbComment(full_filename):
-  global db
-  print_d(f"db access for {full_filename}")
-  c = db.cursor()
-  c.execute("select comment,author,comment_date from dirnotes where name=? and comment<>'' order by comment_date desc",(full_filename,))
-  a = c.fetchone()
-  if a:
-    return a[0:3]
+    global db
+    print_d(f"db access for {full_filename}")
+    c = db.cursor()
+    c.execute("select comment,author,comment_date from dirnotes where name=? and comment<>'' order by comment_date desc",(full_filename,))
+    a = c.fetchone()
+    if a:
+        return a[0:3]
 
 def openDb():
-  global db
-  dbName = "~/.dirnotes.db"
-  dbName = os.path.expanduser(dbName)
-  db = sqlite3.connect(dbName)
-  try:
-    c = db.cursor()
-    c.execute("select * from dirnotes")
-  except sqlite3.OperationalError:
-    c.execute("create table dirnotes (name TEXT, date DATETIME, size INTEGER, comment TEXT, comment_date DATETIME, author TEXT)")
-  return db 
+    global db
+    dbName = "~/.dirnotes.db"
+    dbName = os.path.expanduser(dbName)
+    db = sqlite3.connect(dbName)
+    try:
+        c = db.cursor()
+        c.execute("select * from dirnotes")
+    except sqlite3.OperationalError:
+        c.execute("create table dirnotes (name TEXT, date DATETIME, size INTEGER, comment TEXT, comment_date DATETIME, author TEXT)")
+    return db 
 
 def main(args):
-  parser = argparse.ArgumentParser(description="Display or add comments to files",
-    epilog="Some options conflict. Use only one of: -l -c -a -H -e -z -Z and one of -d -x")
-  parser.add_argument('-V',"--version", action="version",   version=f"dncli ver:{VERSION}")
-  parser.add_argument('-v',"--verbose", action='count',     help="verbose, almost debugging; do not use in scripts",default=0)
-  parser.add_argument('-j',"--json",    action="store_true",help="output in JSON format")
-  pars_m = parser.add_mutually_exclusive_group()
-  pars_m.add_argument('-l',"--listall", action="store_true",help="list all files, including those without comments")
-  parser.add_argument('-d',"--db",      action="store_true",help="list comments from database")
-  parser.add_argument('-x',"--xattr",   action="store_true",help="list comments from xattr")
-  parser.add_argument('-n',"--minimal", action="store_true",help="output only comments; useful in scripting")
-  parser.add_argument('-H',"--history", action="store_true",help="output the history of comments for a file")
-  pars_m.add_argument('-c',"--create",  metavar="comment",  help="add a comment to a file")
-  pars_m.add_argument('-a',"--append",  metavar="comment",  help="append to a comment on a file, separator=';'")
-  pars_m.add_argument('-C',"--copy",    action="store_true",help="copy a file with its comments")
-  parser.add_argument('-y',"--cp_force",action="store_true",help="copy over existing files")
-  pars_m.add_argument('-e',"--erase",   action="store_true",help="erase the comment on a file")
-  pars_m.add_argument('-z',"--zap",     action="store_true",help="clear the comment history on a file")
-  pars_m.add_argument('-Z',"--zapall",   action="store_true",help="clear the comment history in the entire database")
-  parser.add_argument('file_list',nargs='*',help="file(s); list commands may omit this")
-  args = parser.parse_args()
-
-  # default is to display all files that have comments
-
-  # major modes are: display (<none> -l -H), add-comment (-a -c -e), clear-history(-z -Z), copy (-C)
-  # determine the major mode, then apply an appropriate function over the file_list
-  
-  args.display = not (args.create or args.append or args.copy or args.erase or args.zap or args.zapall)
-  if args.cp_force and not args.copy:
-    print("the -y/--cp_force options can only be used with the -C/--copy command")
-    sys.exit(3)   
-  if args.json and not args.display:
-    print("the -j/--json option can only be used with the display modes")
-    sys.exit(4)
-  if args.minimal and not args.display:
-    print("the -n/--minimal option only applies to the display modes")
-    sys.exit(5)
-  if args.history and not args.display:
-    print("the -H/--history option only applies to the display modes")
-    sys.exit(5)
-  if args.xattr and (args.zap or args.zapall):
-    print("the -x/--xattr option doesn't apply to the -z/--zap and -Z/--zapall commands")
-    sys.exit(7)
-
-  global verbose
-  verbose = args.verbose
-  
-  #====== 1) build the file list =============
-
-  files = args.file_list
-  # for the list commands, auto-fill the file list with the current directory
-  if not files and args.display:
-    files = os.listdir(".")
-    files.sort()
-  # other command require explicity file lists, but 'dncli -c "new comment" *' will work
-  if not files:
-    print("please specify a file or files to use")
-    sys.exit(10)
-  print_d("got the files:", files)
-
-  #======= 2) build the function
-  if args.create or args.append or args.erase:
-    print_d(f"create/append/erase: {args.create} . {args.append} . {args.erase}")
-    func = file_modify_comment
-    loop_args = (args.create, args.append, args.erase)
-  elif args.zap or args.zapall:
-    print_d(f"got a zap command {args.zap} . {args.zapall}")
-    func = file_zap
-    loop_args = (args.zapall,)
-    if args.zapall:
-      files = (1,)
-  elif args.copy:
-    print_d(f"got a copy command to {args.copy}")
-    # the last item on the file list is the target
-    n_files = len(files)
-    if n_files < 2:
-      print_d(f"the copy command requires at least two arguments, the last one is the destination")
-      sys.exit(1)
-    target = files[-1]
-    target_is_directory = os.path.isdir(target)
-    files = files[:-1]
-    print_d(f"copy from {files} to {target}")
-    if n_files > 2 and not target_is_directory:
-      print_d(f"multiple copy files must go to a target directory")
-      sys.exit(3)
-    func = file_copy
-    loop_args = (target, target_is_directory, args.cp_force)
-  else:
-    assert(args.display)
-    print_v(f"list files using {'database' if args.db else 'xattr'} priority")
-    print_d(f"display command with option: {args.listall} and {args.history} and {args.json} and {args.minimal}")
-    loop_args = (args.listall, args.history, args.json, args.minimal)
-    func = file_display
-
-  #====== 3) loop on the list, execute the function =============
-  global db
-  db = openDb()
-
-  for f in files:
-    func(f,*loop_args)
-
-  if answer_json:
-    print(json.dumps(answer_json))
+    parser = argparse.ArgumentParser(description="Display or add comments to files",
+        epilog="Some options conflict. Use only one of: -l -c -a -H -e -z -Z and one of -d -x")
+    parser.add_argument('-V',"--version", action="version",     version=f"dncli ver:{VERSION}")
+    parser.add_argument('-v',"--verbose", action='count',           help="verbose, almost debugging; do not use in scripts",default=0)
+    parser.add_argument('-j',"--json",      action="store_true",help="output in JSON format")
+    pars_m = parser.add_mutually_exclusive_group()
+    pars_m.add_argument('-l',"--listall", action="store_true",help="list all files, including those without comments")
+    parser.add_argument('-d',"--db",            action="store_true",help="list comments from database")
+    parser.add_argument('-x',"--xattr",     action="store_true",help="list comments from xattr")
+    parser.add_argument('-n',"--minimal", action="store_true",help="output only comments; useful in scripting")
+    parser.add_argument('-H',"--history", action="store_true",help="output the history of comments for a file")
+    pars_m.add_argument('-c',"--create",    metavar="comment",  help="add a comment to a file")
+    pars_m.add_argument('-a',"--append",    metavar="comment",  help="append to a comment on a file, separator=';'")
+    pars_m.add_argument('-C',"--copy",      action="store_true",help="copy a file with its comments")
+    parser.add_argument('-y',"--cp_force",action="store_true",help="copy over existing files")
+    pars_m.add_argument('-e',"--erase",     action="store_true",help="erase the comment on a file")
+    pars_m.add_argument('-z',"--zap",           action="store_true",help="clear the comment history on a file")
+    pars_m.add_argument('-Z',"--zapall",     action="store_true",help="clear the comment history in the entire database")
+    parser.add_argument('file_list',nargs='*',help="file(s); list commands may omit this")
+    args = parser.parse_args()
+
+    # default is to display all files that have comments
+
+    # major modes are: display (<none> -l -H), add-comment (-a -c -e), clear-history(-z -Z), copy (-C)
+    # determine the major mode, then apply an appropriate function over the file_list
+    
+    args.display = not (args.create or args.append or args.copy or args.erase or args.zap or args.zapall)
+    if args.cp_force and not args.copy:
+        print("the -y/--cp_force options can only be used with the -C/--copy command")
+        sys.exit(3)     
+    if args.json and not args.display:
+        print("the -j/--json option can only be used with the display modes")
+        sys.exit(4)
+    if args.minimal and not args.display:
+        print("the -n/--minimal option only applies to the display modes")
+        sys.exit(5)
+    if args.history and not args.display:
+        print("the -H/--history option only applies to the display modes")
+        sys.exit(5)
+    if args.xattr and (args.zap or args.zapall):
+        print("the -x/--xattr option doesn't apply to the -z/--zap and -Z/--zapall commands")
+        sys.exit(7)
+
+    global verbose
+    verbose = args.verbose
+    
+    #====== 1) build the file list =============
+
+    files = args.file_list
+    # for the list commands, auto-fill the file list with the current directory
+    if not files and args.display:
+        files = os.listdir(".")
+        files.sort()
+    # other command require explicity file lists, but 'dncli -c "new comment" *' will work
+    if not files:
+        print("please specify a file or files to use")
+        sys.exit(10)
+    print_d("got the files:", files)
+
+    #======= 2) build the function
+    if args.create or args.append or args.erase:
+        print_d(f"create/append/erase: {args.create} . {args.append} . {args.erase}")
+        func = file_modify_comment
+        loop_args = (args.create, args.append, args.erase)
+    elif args.zap or args.zapall:
+        print_d(f"got a zap command {args.zap} . {args.zapall}")
+        func = file_zap
+        loop_args = (args.zapall,)
+        if args.zapall:
+            files = (1,)
+    elif args.copy:
+        print_d(f"got a copy command to {args.copy}")
+        # the last item on the file list is the target
+        n_files = len(files)
+        if n_files < 2:
+            print_d(f"the copy command requires at least two arguments, the last one is the destination")
+            sys.exit(1)
+        target = files[-1]
+        target_is_directory = os.path.isdir(target)
+        files = files[:-1]
+        print_d(f"copy from {files} to {target}")
+        if n_files > 2 and not target_is_directory:
+            print_d(f"multiple copy files must go to a target directory")
+            sys.exit(3)
+        func = file_copy
+        loop_args = (target, target_is_directory, args.cp_force)
+    else:
+        assert(args.display)
+        print_v(f"list files using {'database' if args.db else 'xattr'} priority")
+        print_d(f"display command with option: {args.listall} and {args.history} and {args.json} and {args.minimal}")
+        loop_args = (args.listall, args.history, args.json, args.minimal)
+        func = file_display
+
+    #====== 3) loop on the list, execute the function =============
+    global db
+    db = openDb()
+
+    for f in files:
+        func(f,*loop_args)
+
+    if answer_json:
+        print(json.dumps(answer_json))
 
 if __name__ == "__main__":
-  main(sys.argv)
+    main(sys.argv)
 
 

+ 1 - 0
dirnotes-cli.md

@@ -184,3 +184,4 @@ three attributes described in that file that are important:
 > * start_mode
 
 
+

Some files were not shown because too many files changed in this diff