#!/usr/bin/python

# ea:  add, modify or remove extended attributes across multiple files

# 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:
#    
# 20031227 rk
#   initial release
#
#   plans:
#     - add switch to skip over files which already have this attribute already set
# 
# note:  this and kw need de-kludgification.


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



def parseKeys(keystring):
    keyDict = {} 
    errorString = None

    # parse keys    
    keys = keystring.split(',')
    for k in keys:
        try: key,value = k.split('=')
	except ValueError: errorString="key string was not of the form key=value"; return (None,errorString)
	keyDict[key.strip()] = value.strip()

    return (keyDict,errorString)



def doesThisMatch(filename, options):
    keys = options.queryDict.keys()

    # pass 1:  see if file contains all the keys specified by the user
    attrs = xattr.listxattr(filename)
    if (sets.Set(keys) & sets.Set(attrs) == sets.Set(keys)):
	
	# pass 2: see if every value is the same
	for k in keys:
	    if options.queryDict[k] != xattr.getxattr(filename, k): return False
	    
    else: return False
    return True


def showFile( filename, options ):
    print '# %s' % filename    
    attrs = xattr.listxattr(filename)
    for n in attrs:
	print '   %s' % n,options.verbose*xattr.getxattr(filename, n)


def changeAttr( filename, options ):
    if options.ignoredotfiles==True and os.path.basename(filename)[0]=='.': return

    # 1a. just showing?
    if options.show:
	showFile( filename, options )
	return
    
    # 1b. adding? 
    if options.addList != None:  # bail out if user specified simulation only
	if options.nothing: return
	if options.verbose: print filename
	keysDict = options.addDict
	for k in keysDict.keys():
	    xattr.setxattr(filename, k, keysDict[k])
	return

    # 2c. querying?
    elif  options.queryList != None:
	if doesThisMatch(filename, options):
	    print filename
	return
	
    # 2d. deleting?
    elif  options.deleteListList != None:
	if options.nothing: return  # bail out if user specified simulation only
        if options.verbose: print filename
	fileKeys = sets.Set( xattr.listxattr(filename) )
	for k in list(options.deleteListList & fileKeys):
	    try: xattr.removexattr(filename, k) 
	    except IOError: pass
	return


if __name__=='__main__':
    parser = OptionParser()
    parser.add_option("-a", "--add", action="store", dest="addList",
                      help="add attributes from given list")
    parser.add_option("-q", "--query", action="store", dest="queryList",
                      help="query files for compliance with given key/val pairs");
    parser.add_option("-d", "--delete", action="store", dest="deleteList",
		      help="delete attribute (attribute list is of the form key1,key2,...,keyN)")
    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 \".\"")		   


    (options, args) = parser.parse_args()


    options.show = False
    if options.addList == options.deleteList == options.queryList == None:  options.show = True

	
    # begin script
    if len(args)==0:  args = glob.glob('*')    

    # quickly build some lists
    if options.queryList != None: # querying?
	options.queryDict,errorString = parseKeys( options.queryList )
	if errorString != None:  parser.error( errorString )
    elif options.addList != None: # adding?
	options.addDict,errorString = parseKeys( options.addList )
	if errorString != None:  options.error(errorString)
    elif options.deleteList != None: # deleting?
	options.deleteListList =  sets.Set( [i.strip() for i in options.deleteList.split(',')] )
	
    for arg in args: # go through every argument
	if os.path.isdir(arg) and options.recursive==True:
	  changeAttr(arg,options)
  	  for root, dirs, files in os.walk(arg): # get list of files at every dir level
	    for f in files+dirs: # go through list of files
		fname = os.path.join(root,f)
		changeAttr(fname,options)
	else:
		changeAttr(arg, options)

		
		
		
		
		
		
		
		
		

		
		
		
		
		
		
		


		


						      

