diff -r 2b345fa0a9a3 0.11/searchattachments/searchattachments.py
--- a/0.11/searchattachments/searchattachments.py	Wed Dec 23 14:19:46 2009 +0100
+++ b/0.11/searchattachments/searchattachments.py	Thu Aug 02 15:48:39 2012 +0200
@@ -9,335 +9,365 @@
 # import random, md5
 
 from trac.core import *
-from trac.config import Option, Section , Configuration
+from trac.config import (PathOption, IntOption, BoolOption, Section,
+                         Configuration)
 from trac.search import ISearchSource
 from trac.attachment import IAttachmentChangeListener
 from trac.util.datefmt import localtz
+from trac.util.text import to_unicode
 from datetime import datetime
 
-class AttachmentSearchPlugin(Component): 
-        """Implements a search function within uploaded attachments
-           integrated into the normal search function
+class AttachmentSearchPlugin(Component):
+    """Implements a search function within uploaded attachments
+       integrated into the normal search function
+    """
+
+    implements(ISearchSource, IAttachmentChangeListener)
+
+    def _absolute_index_dir(self):
+        return os.path.join(self.env.path, 'attachments', 'index')
+
+    absolute_index_dir = property(_absolute_index_dir)
+
+    swish = PathOption('searchattachments', 'swish',
+                       doc="Absolute path to swish binary.")
+
+    seat = PathOption('searchattachments', 'seat',
+                      doc="Absolute path to trac-seat utility")
+
+    max_excerpt_search = IntOption(
+        'searchattachments', 'max_excerpt_search', default=500000,
+        doc="max. number of bytes read from files containing the keywords to"
+        " extract an excerpt")
+    excerpt_before = IntOption(
+        'searchattachments', 'excerpt_before', default=100,
+        doc="start search excerpt before found keyword (no. of characters)")
+    excerpt_after = IntOption(
+        'searchattachments', 'excerpt_after', default=400,
+        doc="end search excerpt after found keyword (no. of characters)")
+
+    #############################
+    # IAttachmentChangeListener #
+    #############################
+
+    def attachment_added(self, attachment):
+
+        if self._create_metafile(attachment):
+            self._build_index()
+            return True
+        else:
+            self.env.log.error('Error while creating meta:%s' % attachment.filename)
+            return False
+
+    def attachment_deleted(self, attachment):
+
+        meta_path = attachment.path + '.meta'
+
+        # Remove meta file
+        try:
+            os.remove(meta_path)
+        except:
+            self.env.log.warn('Cannot delete %s.meta' % attachment.filename)
+
+        # Rebuild index
+        # running this in the background will result in unindexed attachment
+        # content for 'overwrite existing attachment with the same name"
+        # deletion & immediate re-addition, so force foreground
+        self._build_index(background=False)
+        return
+
+    ###########################
+    # ISearchSource listeners #
+    ###########################
+
+    def get_search_filters(self, req):
+        if 'WIKI_VIEW' in req.perm('wiki'):
+            yield('attachments', 'Attachments')
+
+    def get_search_results(self, req, keywords, filters):
+        if not 'attachments' in filters:
+            return
+
+        # Prepare keywords
+        query = ' '.join(keywords)
+        #for word in keywords :
+        #    query = query + word + ' '
+        query.rstrip(' ')
+        self.env.log.debug('Search query: %s' % query)
+
+        # Run external command to get raw search result
+        index_file = self._get_index_file()
+        if not index_file:
+            return
+
+        cmd = '%s -f %s -w %s' % (self.swish, index_file, query)
+        self.env.log.debug('command % s =' %  cmd)
+        error, output = commands.getstatusoutput(cmd.encode("iso8859-15"))
+        if error :
+            # TODO:Just return or raise exception here?
+            self.env.log.error(output)
+            raise Exception(output)
+            #return
+
+        # Parse output of the command
+        for line in output.split('\n'):
+            line = line.strip(' ')
+            if line and line[0] != '#' :
+                self.env.log.debug('line = %s ' % line)
+                # This is not a comment... let's parse the line
+                pattern = re.compile('^(\d*) (.*)/(.*)\.meta "(.*)" (\d*)$')
+                hit = pattern.match(line)
+                if hit:
+                    sw_rank = hit.group(1)
+                    # safeguard against how the swish index files have been
+                    # created
+                    sw_abs_dir = os.path.normpath(hit.group(2))
+                    sw_filename = hit.group(3)
+                    sw_title = hit.group(4)
+                    sw_end = hit.group(5)
+
+                    regexp = '^' + self.env.path + '/attachments/(.*)$'
+                    p = re.compile(regexp)
+                    m = p.match(sw_abs_dir)
+                    if m :
+                        sw_dir = m.group(1)
+                        file = os.path.join(sw_abs_dir, sw_filename)
+                    else:
+                        sw_dir = sw_abs_dir
+                        file = os.path.join(self.env.path, 'attachments', sw_dir, sw_filename)
+
+                    # Build variables that we'll return for this hit
+                    relative_url = 'attachment/%s/%s' % (sw_dir, sw_filename)
+                    title = 'Attachment::%s/%s' % (sw_dir, urllib.url2pathname(sw_filename))
+                    absolute_url = '/'.join([req.href(), relative_url])
+                    if os.path.exists(file):
+                        date = datetime.fromtimestamp(os.path.getmtime(file), localtz)
+                    else:
+                        return
+                    if self.max_excerpt_search > 0:
+                        excerpt = self._make_excerpt(file + '.meta', keywords)
+                    else:
+                        excerpt = ''
+                    # Return the hits
+                    yield(absolute_url, to_unicode(title), date, 
+                          unicode('SearchAttachments'), excerpt)
+
+
+    ####################
+    # Private methods  #
+    ####################
+
+    def _make_excerpt(self, metafile, keywords):
+        """Create an exceprt, starting from the first line before first matching keyword"""
+
+        handle = codecs.open(metafile, 'r', 'utf_8', 'ignore')
+        if handle:
+            content  = handle.read(self.max_excerpt_search) 
+
+            # Transform wildchar that may exist in the query
+            # to a regexp equivalent
+            wildchar = keywords[0].replace('*','[a-z0-9]*')
+            #regexp = '(\n)?.* ?(%s)' % wildchar
+            regexp = '(%s)' % wildchar
+            m = re.search(regexp, content.lower())
+            if m:
+                prefix = '... '
+                suffix = ' ...'
+                foundpos = m.start(1)
+                start = foundpos - self.excerpt_before
+                if start < 0:
+                    start = 0
+                    prefix = ''
+                end = foundpos + self.excerpt_after
+                if end > len(content):
+                    suffix = ''
+                excerpt = ''.join([prefix, content[start:end], suffix])
+            else:
+                excerpt = (
+                    '[Excerpt not available (first keyword not in meta file'
+                    ' search limit)]')
+        else:
+            excerpt = '[Excerpt not available (cannot read the meta file)]'
+
+        # Cleaning up some unprintable added by catdoc
+        excerpt = multiple_replace({ '\x0a': ' ', '\x0c':' ' }, excerpt)
+
+        return excerpt
+
+    def  _get_index_file(self) :
+        """Returns the most recent index file found in the index directory"""
+
+        if not os.path.isdir(self.absolute_index_dir):
+            self.env.log.warning('index directory does not exist')
+            return None
+
+        # Read all the matching index.* files into a dictionary
+        all = {}
+        for item in dircache.listdir(self.absolute_index_dir) :
+            path = os.path.join(self.absolute_index_dir, item)
+
+            prefix_pattern = re.compile('^index\.swish-e\.(.*)$')
+            prefix = prefix_pattern.match(item)
+            if prefix :
+                # Can be index.xxxx or index.xxxx.prop or index.xxxx.temp
+                key = prefix.group(1)
+
+                if re.compile('^.*\.temp$').match(key) :
+                    # Ignore files ending with *.temp
+                    break
+
+                if not re.compile('^.*\.prop$').match(key):
+                    # This is an index file ...
+                    # ... add last modification time
+                    all[path] = os.path.getmtime(path)
+
+
+        # Do we have indexes in the 'all' dictionary?
+        if not all:
+            self.env.log.warning(
+                '%s: %s', os.path.basename(self.env.path),
+                'attachments/index does not contain any index file')
+            return None
+
+        # Sort the indexes dictionary by increasing value
+        sorted_dict = list(all.iteritems())
+        sorted_dict.sort(lambda i1, i2: cmp(i1[1], i2[1]))
+
+        # Get last tuple
+        last_index = sorted_dict.pop()
+        return os.path.join(self.absolute_index_dir, last_index[0])
+
+    def _build_index(self, conf_file='swish.config', background=True):
+        """Build a new index by lauching the build_index script.
+        Runs as a background process if 'background' is True(the default).
         """
 
-	implements( ISearchSource, IAttachmentChangeListener )
+        self.env.log.info('Starting index update')
+        cmd = '%s "%s" index -s "%s" -c' % (self.seat, self.env.path, self.swish)
+        self.env.log.debug(cmd)
+        if background == True:
+            # Script is launched as a background process
+            # ... so we'll return immediately
+            os.system(cmd + ' &')
+        else:
+            os.system(cmd)
+        return
 
-        def _absolute_index_dir( self ):
-                return os.path.join( self.env.path , 'attachments' , 'index' )
 
-        absolute_index_dir = property(_absolute_index_dir)
+    def _create_metafile(self, attachment) :
+        """Create a metafile(ie. a text version) for the attachement."""
+        """Only extensions with a filter.* command defined in trac.ini will be processed."""
 
+        meta_file = attachment.path + '.meta'
+        extension = self._get_extension(attachment)
 
-	swish = Option('attachment', 'swish',
-		"""Absolute path to swish binary.""")
+        if not extension:
+            msg = '.meta not created for %s (cannot determine file type)' % attachment.filename
+            self.env.log.error(msg)
+            return False
 
-	seat = Option('attachment', 'seat',
-		"""Absolute path to trac-seat utility""")
+        self.env.log.debug('Uploaded file with extension = %s' % extension)
 
-        #############################
-	# IAttachmentChangeListener #
-        #############################
-	
-	def attachment_added( self , attachment ):
+        # Iterate over filter.* in the [attachment] section of the config file
+        # to build a dictionary of extension:command
+        filters = { 'txt' : 'cp -rf "%s" "%s"' }
+        for entry in self.config['searchattachments'] :
+            p = re.compile('filter.([a-zA-Z0-9]*)')
+            m = p.match(entry)
+            if m:
+                format = m.group(1).lower()
+                filters[format] = self.config['searchattachments'].get(entry)
 
-		if self._create_metafile (attachment):
-			self._build_index()
-			return True
-		else:
-			self.env.log.error('Error while creating meta:%s' % attachment.filename )
-			return False
+        # Is there a known filter for this extension?
+        if extension in filters :
 
-	def attachment_deleted( self , attachment ):
+            # Converting to text with the command defined for this extension
+            if os.path.exists(meta_file):
+                os.remove(meta_file)
 
-		meta_path = attachment.path + '.meta'
+            cmd = filters[extension] % (attachment.path, meta_file)
+            self.env.log.debug("Converting to text: %s" % cmd)
+            error, output = commands.getstatusoutput(cmd)
 
-		# Remove meta file
-		try:
-			 os.remove( meta_path )
-		except:
-			self.env.log.warn ('Cannot delete %s.meta' % attachment.filename )
+            # Command sucessful?
+            if error:
+                self.env.log.error('Error when converting .%s to ext :: %s' % (extension, output))
+                return False
+            else:
+                self.env.log.info('File %s succesfully converted ot text' % attachment.filename)
+                return True
+        # Unknown extension
+        else:
+            self.env.log.warn('No filter defined for this extension. Add a filter.%s entry in trac.ini' % extension)
+            return False
 
-		# Rebuild index
-		self._build_index()
-		return
+        return False
 
-	###########################
-	# ISearchSource listeners #
-        ###########################
+    def _get_extension(self, attachment):
 
-	def get_search_filters(self, req):
-		if 'WIKI_VIEW' in req.perm('wiki'):
-			yield('attachments', 'Attachments')
+        pattern  = re.compile('^.*\.([a-zA-Z0-9]*)$')
+        suffix = pattern.match(attachment.filename.strip())
 
-	def get_search_results(self, req, keywords, filters):
-		if not 'attachments' in filters:
-			return
+        if suffix:
+            ext = suffix.group(1).lower()
+            # Synonyms for txt
+            if ext in [ 'txt', 'text', 'xml' ]:
+                return 'txt'
+            else:
+                return ext
 
-		# Prepare keywords
-		query = ''
-		for word in keywords :
-			query = query + word + ' '
-		query.rstrip(' ')
-		self.env.log.debug ('Search query: %s' % query )
+        # No suffix...may be a text file
+        elif is_text_file(attachment.path):
+            return 'txt'
 
-		# Run external command to get raw search result
-		index_file = self._get_index_file()
-		if not index_file:
-			return
-		index_dir  = self.absolute_index_dir
- 
-		cmd = 'cd %s && %s -f %s -w %s' % ( index_dir , self.swish , index_file , query )
-		self.env.log.debug ( 'command % s =' %  cmd)
-		error , output = commands.getstatusoutput( cmd.encode("iso8859-15") )
-		if error :
-			# TODO:Just return or raise exception here?
-			self.env.log.error ( output )
-			raise Exception( output )
-			return
-
-		# Parse output of the command
-		for line in output.split('\n'): 
-			line = line.strip(' ')
-			if line and line[0] != '#' :
-				#sel.env.log.debug ( 'line = %s ' % line )
-				# This is not a comment... let's parse the line
-				pattern  = re.compile ('^(\d*) (.*)/(.*)\.meta "(.*)" (\d*)$')
-				hit      = pattern.match ( line )
-				if hit:
-					sw_rank      = hit.group(1)
-					sw_abs_dir   = hit.group(2)
-                                        sw_filename  = hit.group(3)
-                                        sw_title     = hit.group(4)
-                                        sw_end       = hit.group(5)
-
-					regexp = '^' + self.env.path + '/attachments/(.*)$'
-					p = re.compile(regexp)
-					m = p.match ( sw_abs_dir )
-					if m :
-						sw_dir = m.group(1)
-	                                        file = os.path.join( sw_abs_dir , sw_filename )
-
-					else:
-						sw_dir = sw_abs_dir
-	                                        file = os.path.join( self.env.path , 'attachments' , sw_dir , sw_filename )
-			
-					# Build variables that we'll return for this hit
-					relative_url  = 'attachment/%s/%s'  % ( sw_dir , sw_filename )
-					title         = 'Attachment::%s/%s' % ( sw_dir , urllib.url2pathname(sw_filename) )
-					if os.path.exists (file):
-						date = datetime.fromtimestamp(os.path.getmtime(file), localtz)
-					else:
-						return
-					excerpt = self._make_excerpt ( file + '.meta' , keywords )
-					# Return the hits
-					yield ( relative_url , title , date , 'SearchAttachments' , excerpt )
- 
-
-      	####################
-	# Private methods  #
-       	####################
-
-	def _make_excerpt ( self, metafile , keywords ):
-		"""Create an exceprt, starting from the first line before first matching keyword"""
-
-                handle = codecs.open( metafile , 'r' , 'utf_8' , 'ignore' )
-                if handle:
-                	content  = handle.read( 500000 )    # Read ~1Mbyte maximum
-
-			# Transform wildchar that may exist in the query
-			# to a regexp equivalent
-			wildchar = keywords[0].replace('*','[a-z0-9]*')
-                        regexp = '(\n).* ?(%s)' % wildchar
-                        m = re.compile(regexp , re.IGNORECASE ).search ( content )
-                        if m:
-                        	startline =  m.start(1)
-                               	start     =  m.start(2)
-				if start - startline > 100 : 
-					startline = start - 100
-					begin = '... '
-				else :	
-					begin = ''
-                                length  = 500                          # Number of char in the excerpt
-                        	if startline:
-                                	excerpt = begin + content[startline:start+length] + ' ...'
-                                else:
-                                       	excerpt = begin + content[0:length] + ' ...'
-                	else:
-                                excerpt = 'Excerpt not available... (cannot find first keyword)'
-		else:
-                	excerpt = 'Excerpt not available... (cannot read the meta file)'
-
-		# Cleaning up some unprintable added by catdoc
-		excerpt = multiple_replace( { '\x0a':' ' , '\x0c':' ' }  , excerpt )
- 
-		return excerpt
-
-	def  _get_index_file ( self ) :
-                """Returns the most recent index file found in the index directory"""
-
-		if not os.path.isdir( self.absolute_index_dir ):
-			self.env.log.warning ('index directory does not exist')
-			return None
-
-                # Read all the matching index.* files into a dictionary
-                all = {}
-                for item in dircache.listdir( self.absolute_index_dir ) :
-                        path = os.path.join ( self.absolute_index_dir , item )
-
-                        prefix_pattern = re.compile('^index\.swish-e\.(.*)$') 
-			prefix = prefix_pattern.match ( item )
-                        if prefix :
-                                # Can be index.xxxx or index.xxxx.prop or index.xxxx.temp
-                            	key = prefix.group(1)
-
-                        	if re.compile('^.*\.temp$').match ( key ) :
-                                	# Ignore files ending with *.temp
-                                	break
-
-                                if not re.compile('^.*\.prop$').match( key ):
-                                        # This is an index file ...
-                                        # ... add last modification time
-                                        all[path] = os.path.getmtime(path)
-
-
-		# Do we have indexes in the 'all' dictionary?
-		if not all:
-			self.env.log.warning ('attachments/index does not contain any index file')
-			return None
-
-                # Sort the indexes dictionary by increasing value
-                sorted_dict = list(all.iteritems())
-                sorted_dict.sort(lambda i1, i2: cmp(i1[1], i2[1]))
-
-		# Get last tuple
-         	last_index = sorted_dict.pop()
-		return last_index[0]
-
-	def _build_index( self , conf_file = 'swish.config' ):
-		"""Build a new index by lauching the build_index script as a background process"""
-	
-		self.env.log.info ('Starting index update')
-		cmd = '%s "%s" index -s "%s" -c' % (  self.seat , self.env.path , self.swish )
-		self.env.log.debug ( cmd  )
-		# Script is launched as a background process
-		# ... so we'll return immediately
-		os.system( cmd + ' &' )
-		return
-
-
-	def _create_metafile ( self , attachment ) :
-		"""Create a metafile (ie. a text version) for the attachement."""
-		"""Only extensions with a filter.* command defined in trac.ini will be processed."""
-
-		meta_file = attachment.path + '.meta'
-		extension = self._get_extension ( attachment )
-
-		if not extension:
-			msg = '.meta not created for %s (cannot determine file type)' % attachment.filename
-			self.env.log.error( msg )
-			return False
-
-		self.env.log.debug ('Uploaded file with extension = %s' % extension)
-		
-		# Iterate over filter.* in the [attachment] section of the config file
-		# to build a dictionary of extension:command
-	        filters = { 'txt' : 'cp -rf "%s" "%s"' }
-                for entry in self.config['attachment'] :
-                        p = re.compile('filter.([a-zA-Z0-9]*)')
-                        m = p.match ( entry )
-                        if m:
-                                format = m.group(1).lower()
-                                filters[format] = self.config['attachment'].get(entry)
-
-		# Is there a known filter for this extension?
-		if extension in filters :
-
-			# Converting to text with the command defined for this extension
-			if os.path.exists( meta_file ):
-				os.remove ( meta_file )		
-
-			cmd = filters[extension] % ( attachment.path , meta_file )
-			self.env.log.debug( "Converting to text: %s" % cmd )
-			error , output = commands.getstatusoutput ( cmd )
-
-			# Command sucessful?
-			if error:
-				self.env.log.error( 'Error when converting .%s to ext :: %s' % ( extension , output) )
-				return False
-			else:
-				self.env.log.info('File %s succesfully converted ot text' % attachment.filename )
-				return True
-		# Unknown extension
-		else:
-			self.env.log.warn ('No filter defined for this extension. Add a filter.%s entry in trac.ini' % extension )
-			return False
-
-		return False
-
-	def _get_extension ( self , attachment ):
-
-		pattern  = re.compile( '^.*\.([a-zA-Z0-9]*)$' )
-	        suffix = pattern.match( attachment.filename.strip() )
-
-	        if suffix:
-			ext = suffix.group(1).lower()
-			# Synonyms for txt
-			if ext in [ 'txt' , 'text' , 'xml' ]:
-				return 'txt'
-			else:
-				return ext
-
-		# No suffix...may be a text file
-                elif is_text_file ( attachment.path ):
-                        return 'txt'
-
-		# Cannot determine type
-	        else:
-			return False
+        # Cannot determine type
+        else:
+            return False
 
 
 ##########################
 # Some utility functions #
 ##########################
 
-def is_text( content ):
-	"""Determines if the content is text"""
+def is_text(content):
+    """Determines if the content is text"""
 
-	text_characters = "".join(map(chr, range(32, 127)) + list("\n\r\t\b"))
-	_null_trans = string.maketrans("", "")
+    text_characters = "".join(map(chr, range(32, 127)) + list("\n\r\t\b"))
+    _null_trans = string.maketrans("", "")
 
- 	if "\0" in content:
-       		return False
-    
-	if not content:  # Empty files are considered text
-        	return True
+    if "\0" in content:
+        return False
 
-	# Get the non-text "binary" characters
-	binary = content.translate( _null_trans , text_characters )
+    if not content:  # Empty files are considered text
+        return True
 
-	# If more than 30% non-text characters, then
-	# this is considered a binary file
-	if len(binary) <= len(content) * 0.3:
-		return True
+    # Get the non-text "binary" characters
+    binary = content.translate(_null_trans, text_characters)
 
-	return False
+    # If more than 30% non-text characters, then
+    # this is considered a binary file
+    if len(binary) <= len(content) * 0.3:
+        return True
 
-def is_text_file(  filepath , blocksize = 512 ):
-	"""Read the first 512 bytes of the specified file"""
-	"""Returns True if it's a text file. False otherwise"""
+    return False
 
-	if is_text(open(filepath).read(blocksize)):
-		return True
-	else:
-		return False
+def is_text_file(filepath, blocksize = 512):
+    """Read the first 512 bytes of the specified file.
+    Returns True if it's a text file, False otherwise.
+    """
 
-def multiple_replace( dict , text): 
-	""" Replace in 'text', all occurences of keys specified in the input
-  	dictionary with its corresponding value.  Returns the new tring.""" 
+    if is_text(open(filepath).read(blocksize)):
+        return True
+    else:
+        return False
 
-  	# Create a regular expression  from the dictionary keys
-  	regex = re.compile("(%s)" % "|".join(map(re.escape, dict.keys())))
+def multiple_replace(dict, text):
+    """Replace in 'text', all occurences of keys specified in the input
+    dictionary with its corresponding value.  Returns the new string.
+    """
 
-  	# For each match, look-up corresponding value in dictionary
-	return regex.sub(lambda mo: dict[mo.string[mo.start():mo.end()]], text) 
+    # Create a regular expression  from the dictionary keys
+    regex = re.compile("(%s)" % "|".join(map(re.escape, dict.keys())))
 
+    # For each match, look-up corresponding value in dictionary
+    return regex.sub(lambda mo: dict[mo.string[mo.start():mo.end()]], text)
diff -r 2b345fa0a9a3 0.11/setup.py
--- a/0.11/setup.py	Wed Dec 23 14:19:46 2009 +0100
+++ b/0.11/setup.py	Thu Aug 02 15:48:39 2012 +0200
@@ -18,7 +18,8 @@
 	entry_points = {
         'trac.plugins': [
             'searchattachments.searchattachments = searchattachments.searchattachments'
-        ]
-    }
+            ]
+        },
+        scripts=['trac-seat'],  
 )
 
diff -r 2b345fa0a9a3 0.11/trac-seat
--- a/0.11/trac-seat	Wed Dec 23 14:19:46 2009 +0100
+++ b/0.11/trac-seat	Thu Aug 02 15:48:39 2012 +0200
@@ -11,183 +11,183 @@
 import string
 import commands
 
-from ConfigParser import ConfigParser
+from trac.config import Configuration
 
 
 def index ( dirs , cleanold=False ):
-	"""Build index.swish-e using swish-e"""
-	"""index files are stored in /path/to/env/attachments/index"""
+    """Build index.swish-e using swish-e"""
+    """index files are stored in /path/to/env/attachments/index"""
 
-	# Check if index directory already exists or not
-	index_dir = os.path.join ( trac_env , 'attachments', 'index' )
-	if not os.path.exists( index_dir ):
-		os.mkdir ( index_dir )
+    # Check if index directory already exists or not
+    index_dir = os.path.join ( trac_env , 'attachments', 'index' )
+    if not os.path.exists( index_dir ):
+        os.mkdir ( index_dir )
 
-	os.chdir ( index_dir )
+    os.chdir ( index_dir )
 
-	# Write config
-	key = generate_key()
-	conf_file = 'swish.config.' + key
-	if os.path.exists( conf_file ):
-		os.remove ( conf_file )
+    # Write config
+    key = generate_key()
+    conf_file = 'swish.config.' + key
+    if os.path.exists( conf_file ):
+        os.remove ( conf_file )
 
-	# Crate config file
-     	f = open( conf_file , 'w' )
-	for d in dirs:
-		f.write (' IndexDir %s\n' % os.path.join ( trac_env , d ) )
-	f.write( 'IndexOnly .meta          \n')
-	f.write( 'IndexContents TXT* .meta \n')
-	f.close()
+    # Crate config file
+    f = open( conf_file , 'w' )
+    for d in dirs:
+        f.write (' IndexDir %s\n' % os.path.join ( trac_env , d ) )
+    f.write( 'IndexOnly .meta          \n')
+    f.write( 'IndexContents TXT* .meta \n')
+    f.close()
 
-	# Make sure index dir exists
-	index_file = 'index.swish-e.' + key
-	index_dir  = os.path.join ( trac_env , 'attachments' , 'index' )
+    # Make sure index dir exists
+    index_file = 'index.swish-e.' + key
+    index_dir  = os.path.join ( trac_env , 'attachments' , 'index' )
 
-	if not os.path.exists ( index_dir ):
-		os.mkdir ( index_dir )
+    if not os.path.exists ( index_dir ):
+        os.mkdir ( index_dir )
 
-	# Launch commmand
-	cmd  = '%s -c %s -v 2 -f %s' % ( options.swish , conf_file , index_file )
-	os.system( cmd + '&& echo "Writing index to index.swish-e.%s"' % key)
+    # Launch commmand
+    cmd  = '%s -c %s -v 2 -f %s' % ( options.swish , conf_file , index_file )
+    os.system( cmd + '&& echo "Writing index to index.swish-e.%s"' % key)
 
-	# Remove config file	
-        if os.path.exists( conf_file ):
-                os.remove ( conf_file )
+    # Remove config file
+    if os.path.exists( conf_file ):
+        os.remove ( conf_file )
 
-	if cleanold:	
-		clean ( key )
-	return
+    if cleanold:
+        clean ( key )
+    return
 
 def clean ( key ):
-	"""Clean all indexes not matching key"""
-	
-	for file in os.listdir( os.path.join ( trac_env , 'attachments', 'index' ) ):
+    """Clean all indexes not matching key"""
 
-		if file == 'index.swish-e':
-			robust_delete ( file )
+    for file in os.listdir( os.path.join ( trac_env , 'attachments', 'index' ) ):
 
-		pattern  = re.compile ('^index.swish-e.(.*)$')
-		matching = pattern.match ( file )
-		if matching:
-			suffix = matching.group(1)
-			regexp = '^%s.*$' % key
-			if not re.compile( regexp ).match ( suffix ):
-				# Not the current index ... delete
-				robust_delete ( file )
-	return
+        if file == 'index.swish-e':
+            robust_delete ( file )
+
+        pattern  = re.compile ('^index.swish-e.(.*)$')
+        matching = pattern.match ( file )
+        if matching:
+            suffix = matching.group(1)
+            regexp = '^%s.*$' % key
+            if not re.compile( regexp ).match ( suffix ):
+                # Not the current index ... delete
+                robust_delete ( file )
+    return
 
 
 def get_key ( filename ):
 
-	p1 = re.compile('^index\.swish-e\.(.*)$')
-        m1 = p1.match ( filename )
+    p1 = re.compile('^index\.swish-e\.(.*)$')
+    m1 = p1.match ( filename )
 
-       	if m1:
-		suffix = m1.group(1)
-                p2 = re.compile ('^(.*)\.prop$')
-                m2 = p2.match ( suffix )
-                if m2:
-                	return m2.group(1)
-                else:
-                	return suffix
-		
-	return None
+    if m1:
+        suffix = m1.group(1)
+        p2 = re.compile ('^(.*)\.prop$')
+        m2 = p2.match ( suffix )
+        if m2:
+            return m2.group(1)
+        else:
+            return suffix
+
+    return None
 
 
 def robust_delete ( file ):
 
-	try:
-		os.remove( file )
-		print 'deleting %s' % file
-	except:
-		pass
+    try:
+        os.remove( file )
+        print 'deleting %s' % file
+    except:
+        pass
 
-	return
+    return
 
 
 def get_extension ( path ):
 
-	pattern  = re.compile( '^.*\.([a-zA-Z0-9]*)$' )
-	suffix = pattern.match( os.path.basename(path) )
+    pattern  = re.compile( '^.*\.([a-zA-Z0-9]*)$' )
+    suffix = pattern.match( os.path.basename(path) )
 
-	if suffix:
-		ext = suffix.group(1).lower()
+    if suffix:
+        ext = suffix.group(1).lower()
 
-                # Synonyms for txt
-                if ext in [ 'txt' , 'text' , 'xml' ]:
-                	return 'txt'
-                else:
-                	return ext
+        # Synonyms for txt
+        if ext in [ 'txt' , 'text' , 'xml' ]:
+            return 'txt'
+        else:
+            return ext
 
-                # No suffix...may be a text file
-	elif is_text_file ( path ):
-        	return 'txt'
+        # No suffix...may be a text file
+    elif is_text_file ( path ):
+        return 'txt'
 
-        # Cannot determine type
-        else:
-        	return False
+    # Cannot determine type
+    else:
+        return False
 
 def is_text( content ):
-        """Determines if the content is text"""
+    """Determines if the content is text"""
 
-        text_characters = "".join(map(chr, range(32, 127)) + list("\n\r\t\b"))
-        _null_trans = string.maketrans("", "")
+    text_characters = "".join(map(chr, range(32, 127)) + list("\n\r\t\b"))
+    _null_trans = string.maketrans("", "")
 
-        if "\0" in content:
-                return False
-
-        if not content:  # Empty files are considered text
-                return True
-
-        # Get the non-text "binary" characters
-        binary = content.translate( _null_trans , text_characters )
-
-        # If more than 30% non-text characters, then
-        # this is considered a binary file
-        if len(binary) <= len(content) * 0.3:
-                return True
-
+    if "\0" in content:
         return False
 
+    if not content:  # Empty files are considered text
+        return True
+
+    # Get the non-text "binary" characters
+    binary = content.translate( _null_trans , text_characters )
+
+    # If more than 30% non-text characters, then
+    # this is considered a binary file
+    if len(binary) <= len(content) * 0.3:
+        return True
+
+    return False
+
 def is_text_file(  filepath , blocksize = 512 ):
-        """Read the first 512 bytes of the specified file"""
-        """Returns True if it's a text file. False otherwise"""
+    """Read the first 512 bytes of the specified file"""
+    """Returns True if it's a text file. False otherwise"""
 
-        if is_text(open(filepath).read(blocksize)):
-                return True
-        else:
-                return False
+    if is_text(open(filepath).read(blocksize)):
+        return True
+    else:
+        return False
 
 def convert ( path , ext ):
 
-	if ext == 'meta':
-		return False
+    if ext == 'meta':
+        return False
 
-	meta_file = path + '.meta'
+    meta_file = path + '.meta'
 
-	# Remove existing meta_file
-	if os.path.exists ( meta_file):
-		os.remove ( meta_file )
+    # Remove existing meta_file
+    if os.path.exists ( meta_file):
+        os.remove ( meta_file )
 
-	# Create new meta file
-	cmd  = filters[ext] % ( path , meta_file ) 
-	error , output = commands.getstatusoutput ( cmd )
-	if error:
-		return error
-	else:
-		return True
+    # Create new meta file
+    cmd  = filters[ext] % ( path , meta_file )
+    error , output = commands.getstatusoutput ( cmd )
+    if error:
+        return error
+    else:
+        return True
 
 
 def generate_key ():
-	"""Generate unique key"""
+    """Generate unique key"""
 
-	random.seed()
-	n = random.random()*1000000000000
-	data = str(n) + 'jklsvjksldvjkjkl'
-	base = md5.new( data )
-	key = base.hexdigest()
+    random.seed()
+    n = random.random()*1000000000000
+    data = str(n) + 'jklsvjksldvjkjkl'
+    base = md5.new( data )
+    key = base.hexdigest()
 
-	return key
+    return key
 
 
 
@@ -195,99 +195,99 @@
 
 def deletemeta ( dir , level=0  ):
 
-        mylist = {}
-        for item in os.listdir (dir):
-                path     = os.path.join ( dir , item )
-                if os.path.isfile ( path ) :
-                        mylist[item] = 0
-                else:
-                        mylist[item] = 1
+    mylist = {}
+    for item in os.listdir (dir):
+        path     = os.path.join ( dir , item )
+        if os.path.isfile ( path ) :
+            mylist[item] = 0
+        else:
+            mylist[item] = 1
 
-        # Now sort by value to list  directories first
-        slist = list( mylist.iteritems() )
-        slist.sort(lambda i1, i2: cmp(i1[1], i2[1]))
-	num = 0
-        for tuple in slist :
-                path     = os.path.join ( dir , tuple[0] )
-                if tuple[1]:
-                        # This is a directory
-                        subnum = deletemeta ( path , level + 1  )
-			num = num + subnum
-		else:
-                        # This is a file.Convert it.
-                        ext = get_extension ( path )
-                        if ext == 'meta' :
-				num = num + 1
-                                os.remove ( path )
+    # Now sort by value to list  directories first
+    slist = list( mylist.iteritems() )
+    slist.sort(lambda i1, i2: cmp(i1[1], i2[1]))
+    num = 0
+    for tuple in slist :
+        path     = os.path.join ( dir , tuple[0] )
+        if tuple[1]:
+            # This is a directory
+            subnum = deletemeta ( path , level + 1  )
+            num = num + subnum
+        else:
+            # This is a file.Convert it.
+            ext = get_extension ( path )
+            if ext == 'meta' :
+                num = num + 1
+                os.remove ( path )
 
-	return num
+    return num
 
 def meta ( dir , level=0  ):
 
-	print ' ' * 2 * level + ' \033[44m+ ' + dir + '\033[0m'
-	mylist = {}	
-	for item in os.listdir (dir):
-                path     = os.path.join ( dir , item )
-		if os.path.isfile ( path ) :
-			mylist[item] = 0
-		else:
-			mylist[item] = 1
+    print ' ' * 2 * level + ' \033[44m+ ' + dir + '\033[0m'
+    mylist = {}
+    for item in os.listdir (dir):
+        path     = os.path.join ( dir , item )
+        if os.path.isfile ( path ) :
+            mylist[item] = 0
+        else:
+            mylist[item] = 1
 
-	# Now sort by value to list  directories first
-	slist = list( mylist.iteritems() )
-	slist.sort(lambda i1, i2: cmp(i1[1], i2[1]))
+    # Now sort by value to list  directories first
+    slist = list( mylist.iteritems() )
+    slist.sort(lambda i1, i2: cmp(i1[1], i2[1]))
 
-	for tuple in slist :
-                path     = os.path.join ( dir , tuple[0] )
-		if tuple[1]:
-			# This is a directory
-			meta ( path , level + 1  )
-		else:
-			# This is a file.Convert it.
-			ext = get_extension ( path )
-			if ext and ( ext != 'meta' ) and ( ext in filters) :
-				result = convert ( path , ext )
-				if result:
-					status = '\033[0;35m%s\033[0m:\033[1;32mMETAOK\033[0m' % ext
-					close = '\033[0m'
-				else:
-					status = '%s:\033[1;31m ERROR' % ext
-					close = '\033[0m'
+    for tuple in slist :
+        path     = os.path.join ( dir , tuple[0] )
+        if tuple[1]:
+            # This is a directory
+            meta ( path , level + 1  )
+        else:
+            # This is a file.Convert it.
+            ext = get_extension ( path )
+            if ext and ( ext != 'meta' ) and ( ext in filters) :
+                result = convert ( path , ext )
+                if result:
+                    status = '\033[0;35m%s\033[0m:\033[1;32mMETAOK\033[0m' % ext
+                    close = '\033[0m'
+                else:
+                    status = '%s:\033[1;31m ERROR' % ext
+                    close = '\033[0m'
 
-			elif not ext in filters:
-				 status = '\033[0;35m%s\033[0m:\033[1;33mIGNORE\033[0m' % ext
-                                 close = '\033[0m'
+            elif not ext in filters:
+                status = '\033[0;35m%s\033[0m:\033[1;33mIGNORE\033[0m' % ext
+                close = '\033[0m'
 
-			else:
-				status = ' ? '
-				close = ''
-	
-			# TODO: Utiliser ERR pour erreur sur format connu et ? pour format inconnu
-			if ext != 'meta':	
-	                       	print ' ' * 2 * level + ' \033[44m|\033[0m - %s  ' % status + tuple[0] 
-	return
+            else:
+                status = ' ? '
+                close = ''
+
+            # TODO: Utiliser ERR pour erreur sur format connu et ? pour format inconnu
+            if ext != 'meta':
+                print ' ' * 2 * level + ' \033[44m|\033[0m - %s  ' % status + tuple[0]
+    return
 
 def vararg (option, opt_str, value, parser):
-	assert value is None
-	done = 0
-	value = []
-	rargs = parser.rargs
-	while rargs:
-        	arg = rargs[0]
+    assert value is None
+    done = 0
+    value = []
+    rargs = parser.rargs
+    while rargs:
+        arg = rargs[0]
 
-        	# Stop if we hit an arg like "--foo", "-a", "-fx", "--file=f",
-	        # etc.  Note that this also stops on "-3" or "-3.0", so if
-        	# your option takes numeric values, you will need to handle
-	        # this.
-        	if ((arg[:2] == "--" and len(arg) > 2) or
-	            (arg[:1] == "-" and len(arg) > 1 and arg[1] != "-")):
-        	    break
-	        else:
-        	    value.append(arg)
-	            del rargs[0]
+        # Stop if we hit an arg like "--foo", "-a", "-fx", "--file=f",
+        # etc.  Note that this also stops on "-3" or "-3.0", so if
+        # your option takes numeric values, you will need to handle
+        # this.
+        if ((arg[:2] == "--" and len(arg) > 2) or
+            (arg[:1] == "-" and len(arg) > 1 and arg[1] != "-")):
+            break
+        else:
+            value.append(arg)
+            del rargs[0]
 
-	setattr(parser.values, option.dest, value)
-	return
+    setattr(parser.values, option.dest, value)
+    return
 
 # Parsz input line
 usage = "usage: %prog </path/to/projenv> [command] [options]"
@@ -301,9 +301,9 @@
 
 parser.add_option( "-d", "--dir", dest="dir", action="callback" , callback=vararg , metavar="DIR1 DIR2 ..." ,
                   help="list of space separated directories to crawl or to index. " \
-			"Pathes are relative to the trac project env directory. [ attachments/wiki attachments/ticket ]" )
+                        "Pathes are relative to the trac project env directory. [ attachments/wiki attachments/ticket ]" )
 
-parser.add_option( "-c", "--clean", action="store_true" , dest="clean" , 
+parser.add_option( "-c", "--clean", action="store_true" , dest="clean" ,
                   help="remove old indexes after building index. ")
 
 parser.add_option( "-s", "--swish", action="store" , dest="swish" , metavar="EXEC" , default="/usr/local/bin/swish-e",
@@ -315,70 +315,60 @@
 
 # Default value for optiosn.dir
 if not options.dir:
-        options.dir = ('attachments/wiki', 'attachments/ticket')
+    options.dir = ('attachments/wiki', 'attachments/ticket')
 
 
 # main switch
-if len(sys.argv) == 1:
-	print 'You must specify the path to the trac environment ar first argument. Try %s --help' % os.path.basename(sys.argv[0])
-	sys.exit(1)
-elif len (sys.argv) == 2:
-	print 'no command specified. Try %s --help' % os.path.basename(sys.argv[0])
-	sys.exit(1)
+if len(args) == 0:
+    print 'You must specify the path to the trac environment as first argument. Try %s --help' % os.path.basename(sys.argv[0])
+    sys.exit(1)
+elif len (args) == 1:
+    print 'no command specified. Try %s --help' % os.path.basename(sys.argv[0])
+    sys.exit(1)
 
-trac_env = sys.argv[1]
-command  = sys.argv[2]
+trac_env = args[0]
+command  = args[1]
 
 if not os.path.exists( trac_env ):
-	print "The specificied Trac environment does not exist: %s" % trac_env
-	sys.exit(1)
+    print "The specificied Trac environment does not exist: %s" % trac_env
+    sys.exit(1)
 
 
 if command == 'index':
-		print 'clean %s' % options.clean
-		if options.clean:
-			index ( options.dir , True )
-		else:
-			index ( options.dir )
+    print 'clean %s' % options.clean
+    if options.clean:
+        index ( options.dir , True )
+    else:
+        index ( options.dir )
 
 elif command == 'meta' :
 
-	# Make global variable filter
-	filters = {}
+    # Make global variable filter
+    #FIXME: This is surely not Win-compatible, much the same in
+    #FIXME: searchattachments.py. Better: Expose txt filter in trac.ini?
+    filters = {'txt' : 'cp -rf "%s" "%s"'}
 
-	# Read supported filters for trac.ini from global-dir 
-	trac = ConfigParser()
-	trac.readfp(open( '/usr/share/trac/conf/trac.ini' ))
+    # Read supported filters for trac.ini from env_dir
+    trac_ini = os.path.join ( trac_env , 'conf' , 'trac.ini' )
+    trac = Configuration(trac_ini)
+    try:
+        p = re.compile ('^filter.(.*)$')
+        for entry in trac.options('searchattachments'):
+            m = p.match ( entry[0] )
+            if m:
+                ext = m.group(1)
+                filters[ext] = entry[1]
+    except Exception, e:
+        print e
+        print 'No local configuration'
 
-	for entry in trac.options('attachment'):
-		p = re.compile ('^filter.(.*)$')
-		m = p.match ( entry )
-		if m:
-		    ext = m.group(1)
-		    filters[ext] = trac.get('attachment', entry)
-
-	# Read supported filters for trac.ini from env_dir
-	trac = ConfigParser()
-	trac_ini = os.path.join ( trac_env , 'conf' , 'trac.ini' )
-	trac.readfp(open( trac_ini ))
-
-        try:
-	        for entry in trac.options('attachment'):
-	    	    ip = re.compile ('^filter.(.*)$')
-	    	    m = p.match ( entry )
-		    if m:
-		        ext = m.group(1)
-		        filters[ext] = trac.get('attachment', entry)
-        except:
-                print 'No local configuration'
-	
-	for d in options.dir:
-		meta ( os.path.join( trac_env , d ) )
+    for d in options.dir:
+        meta ( os.path.join( trac_env , d ) )
 
 elif command == 'deletemeta':
-	for d in options.dir:
-                print "in %s" % d
-		print "   %d .meta files deleted" % deletemeta ( os.path.join( trac_env , d ) )
+    for d in options.dir:
+        print "in %s" % d
+        print "   %d .meta files deleted" % deletemeta ( os.path.join( trac_env , d ) )
 
 else:
-	print 'Unkown command. Try %s --help' % os.path.basename(sys.argv[0])
+    print 'Unkown command. Try %s --help' % os.path.basename(sys.argv[0])
