#!/usr/bin/env python

# fnmeta.py
# attach metadata to files by base64ing the metadata and renaming the file
#  to include it. was sick of trying to append ".maybe this works?" to
#  filenames. this is poor-man's filesystem metadata. check out leaftag for
#  a real solution.

# objective: allow me to put comments in filenames which contain ?!* etc
#  without the resulting shell pain. handy for "retrieved from http://bla"
# bonus: i can now attach arbitrary binary metadata to files in a
#  filesystem-agnostic way.
# example: echo 'retrieved from www.movable-type.co.uk/scripts/aes.html on' \
#  $(date +%m-%d-%y_%H%M) | fnmeta.py aes.html
# alternative: (proposed by hatero) append metadata to end of the file itself.
#  the metadata's length could be added to the filename. the major drawback
#  to this approach is that the file can no longer be used as-is.

# zip -c is used for one-line comments

# PUZZLE: why does this metadata cause a _huge_ base64 to be gen'd?
# http://sourceforge.net/tracker/index.php?func=detail&aid=1770556&gro
#  up_id=86916&atid=581351

# PUZZLE: could zlib help here? http://www.python.org/doc/lib/module-zlib.html
#  bz2 provides a compatible interface: http://docs.python.org/lib/node304.html
#  >>> import zlib
#  >>> import base64
#  >>> base64.urlsafe_b64encode("the cat jumped over!")
#  'dGhlIGNhdCBqdW1wZWQgb3ZlciE='
#  >>> base64.urlsafe_b64encode(zlib.compress("the cat jumped over!"))
#  'eJwryUhVSE4sUcgqzS1ITVHIL0stUgQATPkHPA=='
#  >>> base64.urlsafe_b64encode(zlib.compress("the cat jumped over!",9))
#  'eNoryUhVSE4sUcgqzS1ITVHIL0stUgQATPkHPA=='

import base64 # for base64.urlsafe_b64encode() etc
import sys # for sys.argv
import re # for re.compile(), etc
import os # for os.rename()
import shutil # for shutil.copyfile()

# since the equals sign is used for variable assignment in (at least) zsh,
#  let's substitute some other character for it. update METADATA_FORMAT_VER if
#  you change this. tried "~" (homedir), "," (cmd.exe ignores), "^" (cmd.exe
#  asks "More?")
REPL_FOR_EQ = "%"

# make sure to increment this by one whenever the metadata format is changed.
METADATA_FORMAT_VER = "1"

def does_filename_contain_metadata( filename ):
    # these metadata delimiters must each contain at least one character
    #  which is not in the base64 alphabet
    myRegex = re.compile('\.fnmeta' +
                         METADATA_FORMAT_VER +
                         '\..+\.fnmeta' +
                         METADATA_FORMAT_VER)
    myMatch = myRegex.search( filename )
    return myMatch

def extract_metadata( span, filename ):
    # print "extracting metadata from file '%s' with span '%s'" % (filename, span)
    urlSafeBase64 = filename[span[0] + 8 : span[1] - 7].replace(REPL_FOR_EQ, "=")
    print base64.urlsafe_b64decode( urlSafeBase64 )

def insert_metadata( filename ):
    # print "inserting metadata for file '%s'" % (filename)
    #confirmAns = raw_input("are you sure you want to do this [y]/n: ")
    # TODO: use a line editor cause this'll read BS, C-a, etc as raw
    newMetaData = raw_input("please enter the new metadata: ")
    urlSafeBase64 = base64.urlsafe_b64encode( newMetaData )
    # insert the new metadata at the end of the filename if the filename doesn't
    #  contain a dot
    lastDot = filename.rfind( ".")
    if( lastDot == -1 ):
        lastDot = len( filename )
    filenameWithMetadata = filename[:lastDot] + \
                           ".fnmeta" + \
                           METADATA_FORMAT_VER + \
                           "." + \
                           urlSafeBase64.replace("=", REPL_FOR_EQ) + \
                           ".fnmeta" + \
                           METADATA_FORMAT_VER + \
                           filename[lastDot:]
    print "new filename: ", filenameWithMetadata
    # make up a backup in case the rename operation kills the original
    # PUZZLE: this isn't necessary is it?
    # shutil.copyfile(filename, filename + ".fnmeta.backup" )

    # TODO: handle filenames that are too long
    #  http://en.wikipedia.org/wiki/Comparison_of_file_systems
    #  ntfs/fat32/ext2/ext3: 255
    #  Python 2.4.1 on cygwin gives "OSError: [Errno 91] File name too long"
    os.rename( filename, filenameWithMetadata )
    
def modify_metadata( filename ):
    # TODO: use me!
    print "modifying the metadata in:", filename

if __name__ == "__main__":

    # print the usage message if no filenames were specified
    if len(sys.argv) < 2:
        sys.exit( "usage: " + sys.argv[0] + " filename1 [filename2...]" )

    # iterate over each filename given as an argument
    for currentFileName in sys.argv[1:]:
        firstMatch = does_filename_contain_metadata( currentFileName )
        
        if( firstMatch ):
            # extract the metadata from the filename and print it
            extract_metadata( firstMatch.span(), currentFileName )
        else:
            # prompt the user for the new metadata and insert it into the
            #  filename
            insert_metadata( currentFileName )

# rename current file such that metadata is included before rightmost dot or at end if no dot is present
#  tyler.jpeg becomes tyler.fnmetabegin.METADATA.fnmetaend.jpeg
#  oderkirk becomes oderkirk.fnmetabegin.METADATA.fnmetaend



