#!/usr/bin/python


# kw (c) 2003-  Roey Katz, distributed under the following license:

# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2, or (at your option)
# any later version.
	
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.

# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
		      



# revisions:
# 
# 20031229 rk
#   added -w option to specify another attribute name
# 20031227 rk
#   initial release

# surgeon generals' warning:  viewing this code may lead to dizziness,
# headache, and possibly vomit


DEFAULT_KEYWORDNAME='keywords'
PREFIX='user.'

import sys,os,xattr,sets,glob
from optparse import OptionParser

def calcMaxLength( l ):
    if len(l)==0: return 0
    return max(map(len,l))    
    
def changeKeyword( filename, options ):
    if options.ignoredotfiles==True and os.path.basename(filename)[0]=='.': return

    
    # 1. get file's keyword list or create it if it does not exist
    attrlist = []
    try:  
	attrlist = xattr.getxattr( filename, options.keywordName )
	if attrlist=='': fileKeys = sets.Set()
	else: # we have to do the above because for empty user.keywords strings we don't want Set([''])
	    fileKeys = sets.Set( [i.strip() for i in attrlist.split(',')] )
	      
    except IOError:  
	if options.deleteList != None: return 
	else: fileKeys = sets.Set()
	
    # 2a. just show?
    if hasattr( options, 'show' ):
	keys = [i.strip() for i in list(fileKeys)]
	keys.sort()
	print '%-*.*s  %s' % (options.maxlen, options.maxlen, filename, ", ".join(keys))
	return

    # 2b. is query?
    if options.queryList != None:
	keyList = options.keyList
	if fileKeys & keyList:
	    if options.verbose:
		keys = [i.strip() for i in list(fileKeys)]
		keys.sort()	       
		print '%-*.*s  %s' % (options.maxlen, options.maxlen, filename, ", ".join(keys))
	    else:  print filename
	return

    # 2c. is add keyword?
    elif options.deleteList != None:
	kw = fileKeys - options.keyList
	
    # 2d. or delete keyword?	
    elif options.addList != None: 
	kw =  fileKeys | options.keyList

    # 3. write to file's keyword list
    keys = [i.strip() for i in list(kw)] # adds an '' element for some reason
    keys.sort()
    
    if options.verbose==True: print '%-*.*s:  %s -> %s' % (options.maxlen, options.maxlen, filename, ",".join(list(fileKeys)), ",".join(keys))
    if options.nothing: return
    xattr.setxattr( filename, options.keywordName, ",".join(keys) )
    


    
if __name__=='__main__':
    parser = OptionParser()
    parser.add_option("-a", "--add", type="string",    action="store",      dest="addList",
		      help="add keyword (keyword list is of the form key1,key2,...,keyN)", )
    parser.add_option("-d", "--delete", type="string", action="store",      dest="deleteList", 
		      help="delete keyword (keyword list is of the form key1,key2,...,keyN)", )
    parser.add_option("-q", "--query", type="string",  action="store",      dest="queryList",
                      help="print files whose keywords match the given keylist")
    parser.add_option("-r", "--recursive",action="store_true", dest="recursive", default=False,
		      help="recursive operation")
    parser.add_option("-n", "--nothing", action="store_true", dest="nothing", default=False,
                      help="just simulate; perform no action")
    parser.add_option("-v", "--verbose", action="store_true", dest="verbose", default=False,
                      help="print description of the actions taken to stdout")
    parser.add_option("-t", "--ignore-dotfiles", action="store_true", dest="ignoredotfiles", default=False,
                      help="ignore files beginning with \".\"")
    parser.add_option("-w", "--keyword", type="string", action="store", dest="keywordName",
                      help="specify a different extended attribute name (default is 'user.keywords')")

    (options, args) = parser.parse_args()
    
    if options.addList == options.deleteList == options.queryList == None: 
	options.show = True
	
    if options.keywordName == None:  options.keywordName = PREFIX + DEFAULT_KEYWORDNAME
    else:  options.keywordName = PREFIX + options.keywordName
    
    # begin script
    if len(args)==0:  args = glob.glob('*')

    if options.queryList != None:
	options.keyList = sets.Set([i.strip() for i in options.queryList.split(",")])
    elif options.deleteList != None:
	options.keyList = sets.Set([i.strip() for i in options.deleteList.split(",")])
    elif options.addList != None:
	options.keyList = sets.Set([i.strip() for i in options.addList.split(",")])   

    
    options.maxlen = calcMaxLength(args)+1
    for arg in args: # go through every argument
	if os.path.isdir(arg) and options.recursive: # if we're recursing...
          changeKeyword(arg, options) # ...then first change the dir itself...
  	  for root, dirs, files in os.walk(arg): # ...and then change the files and dirs under it
	    options.maxlen = calcMaxLength(files+dirs)+len(root)+1
	    for f in files+dirs: # go through list of files
		fname = os.path.join(root,f)
		changeKeyword(fname,options)
	else:
  	    options.maxlen = calcMaxLength(args)+1
	    changeKeyword(arg, options) # otherwise just change the file or dir by itself

		
		
		
		
		
		
		
		
		

		
		
		
		
		
		
		


		


						      

