Author: aum
Date: 2006-06-08 09:01:04 +0000 (Thu, 08 Jun 2006)
New Revision: 9082

Modified:
   trunk/apps/pyFreenet/code.leo
   trunk/apps/pyFreenet/fcp/node.py
   trunk/apps/pyFreenet/fcp/sitemgr.py
   trunk/apps/pyFreenet/freesitemgr
   trunk/apps/pyFreenet/freesitemgr.py
   trunk/apps/pyFreenet/manpages/freesitemgr.1
   trunk/apps/pyFreenet/manpages/freesitemgr.1.html
Log:
Further changes to freesitemgr:
1) Only new/changed files get inserted, unless -i is set
2) site manifests get stored in ~/.freesites


Modified: trunk/apps/pyFreenet/code.leo
===================================================================
--- trunk/apps/pyFreenet/code.leo       2006-06-08 04:42:51 UTC (rev 9081)
+++ trunk/apps/pyFreenet/code.leo       2006-06-08 09:01:04 UTC (rev 9082)
@@ -1,8 +1,8 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <leo_file>
 <leo_header file_format="2" tnodes="0" max_tnode_index="64" clone_windows="0"/>
-<globals body_outline_ratio="0.256694367498">
-       <global_window_position top="70" left="78" height="748" width="1083"/>
+<globals body_outline_ratio="0.2594644506">
+       <global_window_position top="38" left="140" height="748" width="1083"/>
        <global_log_window_position top="0" left="0" height="0" width="0"/>
 </globals>
 <preferences/>
@@ -29,7 +29,7 @@
 </v>
 <v t="aum.20060513073239" a="E"><vh>Package 'fcp'</vh>
 <v t="aum.20060516141235" tnodeList="aum.20060516141235"><vh>@nosent 
__init__.py</vh></v>
-<v t="aum.20060506215707" a="E" 
tnodeList="aum.20060506215707,aum.20060506215707.1,aum.20060506220237,aum.20060506215707.2,aum.20060506215707.3,aum.20060607085345,aum.20060506220237.1,aum.20060506220237.2,aum.20060514223716,aum.20060506231352.1,aum.20060506231352,aum.20060507003931,aum.20060511001853,aum.20060521180804,aum.20060506224238,aum.20060514224855,aum.20060514224919,aum.20060514225725,aum.20060514223936,aum.20060514223822,aum.20060514223845,aum.20060514224020,aum.20060514124642,aum.20060514191601,aum.20060511205201,aum.20060506232639,aum.20060506232639.1,aum.20060511222538,aum.20060512101715,aum.20060511205201.1,aum.20060511205201.2,aum.20060506223545,aum.20060506224238.1,aum.20060506231352.2,aum.20060506220856,aum.20060506222005,aum.20060507124316,aum.20060511103841,aum.20060511103841.1,aum.20060511103952,aum.20060511103952.1,aum.20060604204143,aum.20060514134235,aum.20060512181209,aum.20060514162944,aum.20060514124934,aum.20060512102840,aum.20060514164052,aum.20060509184020.1,aum.20060509184020.2,aum.20060509224119,aum.20060509224221,aum.20060603170554,aum.20060603231840,aum.20060603231840.1,aum.20060603231840.2"><vh>@nosent
 node.py</vh>
+<v t="aum.20060506215707" a="E" 
tnodeList="aum.20060506215707,aum.20060506215707.1,aum.20060506220237,aum.20060506215707.2,aum.20060506215707.3,aum.20060607085345,aum.20060506220237.1,aum.20060506220237.2,aum.20060514223716,aum.20060506231352.1,aum.20060506231352,aum.20060507003931,aum.20060511001853,aum.20060521180804,aum.20060506224238,aum.20060514224855,aum.20060514224919,aum.20060514225725,aum.20060514223936,aum.20060514223822,aum.20060514223845,aum.20060514224020,aum.20060514124642,aum.20060514191601,aum.20060511205201,aum.20060506232639,aum.20060506232639.1,aum.20060511222538,aum.20060512101715,aum.20060511205201.1,aum.20060511205201.2,aum.20060506223545,aum.20060506224238.1,aum.20060506231352.2,aum.20060506220856,aum.20060506222005,aum.20060507124316,aum.20060511103841,aum.20060511103841.1,aum.20060511103952,aum.20060511103952.1,aum.20060604204143,aum.20060514134235,aum.20060512181209,aum.20060514162944,aum.20060514124934,aum.20060512102840,aum.20060514164052,aum.20060509184020.1,aum.20060509184020.2,aum.20060509224119,aum.20060509224221,aum.20060603170554,aum.20060603231840,aum.20060603231840.1,aum.20060603231840.2"><vh>@file
 node.py</vh>
 <v t="aum.20060506215707.1"><vh>imports</vh></v>
 <v t="aum.20060506220237"><vh>exceptions</vh></v>
 <v t="aum.20060506215707.2"><vh>globals</vh></v>
@@ -57,7 +57,7 @@
 <v t="aum.20060511205201"><vh>shutdown</vh></v>
 </v>
 <v t="aum.20060506232639" a="E"><vh>Manager Thread</vh>
-<v t="aum.20060506232639.1"><vh>_mgrThread</vh></v>
+<v t="aum.20060506232639.1" a="V"><vh>_mgrThread</vh></v>
 <v t="aum.20060511222538"><vh>_msgIncoming</vh></v>
 <v t="aum.20060512101715"><vh>_submitCmd</vh></v>
 <v t="aum.20060511205201.1"><vh>_on_rxMsg</vh></v>
@@ -71,7 +71,7 @@
 <v t="aum.20060507124316"><vh>_log</vh></v>
 </v>
 </v>
-<v t="aum.20060511103841" a="E"><vh>class JobTicket</vh>
+<v t="aum.20060511103841"><vh>class JobTicket</vh>
 <v t="aum.20060511103841.1"><vh>__init__</vh></v>
 <v t="aum.20060511103952"><vh>isComplete</vh></v>
 <v t="aum.20060511103952.1"><vh>wait</vh></v>
@@ -516,7 +516,7 @@
 </v>
 </v>
 <v t="aum.20060513073239.5" a="E"><vh>Test files</vh>
-<v t="aum.20060608131556" a="V" tnodeList="aum.20060608131556"><vh>@file 
putdirtest.py</vh></v>
+<v t="aum.20060608131556" tnodeList="aum.20060608131556"><vh>@file 
putdirtest.py</vh></v>
 <v t="aum.20060526123909" tnodeList="aum.20060526123909"><vh>@file 
fstest.c</vh></v>
 <v t="aum.20060511003500" tnodeList="aum.20060511003500"><vh>@file 
test.py</vh></v>
 <v t="aum.20060512152233" tnodeList="aum.20060512152233"><vh>@file 
genkey.py</vh></v>
@@ -1217,7 +1217,7 @@
                 log(DEBUG, "No incoming client req")
                 pass

-        self._log(INFO, "Manager thread terminated normally")
+        self._log(DETAIL, "Manager thread terminated normally")
         return

     except:
@@ -6732,6 +6732,9 @@
     # buffer ourselves, not just drop it through as a bunch of keywords,
     # since we want to control the order of keyword lines

+    chkonly = False
+    #chkonly = True
+
     # get keyword args
     dir = kw['dir']
     sitename = kw.get('name', 'freesite')
@@ -6767,11 +6770,29 @@
         uriFull = uriFull.replace("SSK@", "USK@")
         while uriFull.endswith("/"):
             uriFull = uriFull[:-1]
+
+    manifestDict = kw.get('manifest', None)
+
+    if manifestDict:
+        # work from the manifest provided by caller
+        #print "got manifest kwd"
+        #print manifestDict
+        manifest = []
+        for relpath, attrDict in manifestDict.items():
+            if attrDict['changed']:
+                attrDict['relpath'] = relpath
+                attrDict['fullpath'] = os.path.join(dir, relpath)
+                manifest.append(attrDict)
+    else:
+        # build manifest by reading the directory
+        #print "no manifest kwd"
+        manifest = readdir(kw['dir'])
+        manifestDict = {}
+        for rec in manifest:
+            manifestDict[rec['relpath']] = rec
+        #print manifestDict

-    # scan directory and add its files
-    manifest = readdir(kw['dir'])
-    
-    manifestDict = {}
+
     jobs = []
     #allAtOnce = False

@@ -6825,7 +6846,7 @@
             fullpath = filerec['fullpath']
             mimetype = filerec['mimetype']

-            manifestDict[relpath] = filerec
+            #manifestDict[relpath] = filerec

             log(INFO, "Launching insert of %s" % relpath)

@@ -6839,6 +6860,7 @@
                            mimetype=mimetype,
                            async=1,
                            verbosity=verbosity,
+                           chkonly=chkonly,
                            )
             jobs.append(job)
             filerec['job'] = job
@@ -6878,6 +6900,9 @@
         fullpath = filerec['fullpath']
         mimetype = filerec['mimetype']

+        # update the uri in the manifest dict
+        manifestDict[relpath]['uri'] = job.uri
+
         # don't add if the file failed to insert
         if filebyfile:
             if isinstance(filerec['job'].result, Exception):
@@ -6908,13 +6933,16 @@

     # --------------------------------------
     # now dispatch the job
-    finalResult = self._submitCmd(
-                    id, "ClientPutComplexDir",
-                    rawcmd=fullbuf,
-                    async=kw.get('async', False),
-                    callback=kw.get('callback', False),
-                    Persistence=kw.get('Persistence', 'connection'),
-                    )
+    if chkonly:
+        finalResult = "no_uri"
+    else:
+        finalResult = self._submitCmd(
+                        id, "ClientPutComplexDir",
+                        rawcmd=fullbuf,
+                        async=kw.get('async', False),
+                        callback=kw.get('callback', False),
+                        Persistence=kw.get('Persistence', 'connection'),
+                        )

     # finally all done, return result or job ticket
     return finalResult
@@ -7020,7 +7048,7 @@

 # fcp imports
 import node
-from node import FCPNode
+from node import FCPNode, guessMimetype
 from node import SILENT, FATAL, CRITICAL, ERROR, INFO, DETAIL, DEBUG

 </t>
@@ -7046,6 +7074,8 @@

     conf = self.config
     for sitename in conf.sections():
+        
+        print "Updating site '%s'" % sitename

         # fill in any incomplete details with site entries
         needToSave = False
@@ -7063,6 +7093,7 @@
             uri = pub.replace("SSK@", "USK@") + sitename + "/0"
             conf.set(sitename, "uri", uri)
             conf.set(sitename, "privatekey", priv)
+
         if needToSave:
             self.saveConfig()

@@ -7073,11 +7104,51 @@
         privatekey = conf.get(sitename, "privatekey")

         files = node.readdir(dir, gethashes=True)
+
+        # get old manifest - dict of path-&gt;{'uri':uri, 'hash':hash} mappings
+        siterec = self.siteManifests[sitename]
+
+        #print "readdir files:"
+        #print files
+        #print "siterec files:"
+        #print siterec
+
+        # determine 'site hash'
         h = sha.new()
         for f in files:
+            relpath = f['relpath']
+            if siterec.has_key(relpath):
+                filerec = siterec[relpath]
+                filehash = f['hash']
+                if (filerec['hash'] != f['hash']) or self.insertall:
+                    #print "File: %s" % relpath
+                    #print "  oldhash=%s" % filerec['hash']
+                    #print "  newhash=%s" % f['hash']
+                    filerec['changed'] = True
+            else:
+                # new file, create new record and mark as changed
+                #print "new file %s" % relpath
+                siterec[relpath] = {'uri':"",
+                                    'hash':f['hash'],
+                                    'changed':True,
+                                    'fullpath': os.path.join(dir, relpath),
+                                    'mimetype': guessMimetype(relpath),
+                                    }
             h.update(f['hash'])
+
+        # dump deleted files from siterec
+        filenames = [f['relpath'] for f in files]
+        for relpath in siterec.keys():
+            if relpath not in filenames:
+                del siterec[relpath]
+        
         hashNew = h.hexdigest()
-        if hashNew != hash:
+
+        # don't insert site if it hasn't changed
+        if (hashNew == hash) and not self.insertall:
+            print "Site '%s' unchanged, skipping update" % sitename
+        else:
+            # site has changed, so better insert it
             log(INFO, "Updating site %s" % sitename)
             log(INFO, "privatekey=%s" % privatekey)
             noSites = False
@@ -7091,20 +7162,33 @@
                                     verbosity=self.Verbosity,
                                     filebyfile=self.filebyfile,
                                     allatonce=self.allatonce,
-                                    maxconcurrent=self.maxconcurrent)
-                log(INFO, "site %s updated successfully" % sitename)
+                                    maxconcurrent=self.maxconcurrent,
+                                    manifest=siterec,
+                                    insertall=self.insertall)
+    
+                print "Site '%s' updated successfully" % sitename
             except:
                 traceback.print_exc()
-                log(ERROR, "site %s failed to update" % sitename)
-            conf.set(sitename, "hash", hashNew)
-        else:
-            log(INFO, "Site %s not changed, no need to update" % sitename)
+                print "site '%s' failed to update" % sitename

+        # update the site's global hash
+        conf.set(sitename, "hash", hashNew)
+
+        # and re-populate the manifest
+        i = 0
+        for relpath, attrdict in siterec.items():
+            #print attrdict
+            conf.set(sitename, "files.%d.relpath" % i, relpath)
+            conf.set(sitename, "files.%d.uri" % i, attrdict['uri'])
+            conf.set(sitename, "files.%d.hash" % i, attrdict['hash'])
+            conf.set(sitename, "files.%d.mimetype" % i, attrdict['mimetype'])
+            i += 1
+
+    # save the updated config
     self.saveConfig()

     if noSites:
         log(INFO, "No sites needed updating")
-
 </t>
 <t tx="aum.20060511114439">class SiteMgr:
     """
@@ -7135,6 +7219,9 @@
         - maxconcurrent - default 10 - if set, this also sets filebyfile
           and allatonce both to True. Value of maxconcurrent is the
           maximum number of concurrent inserts
+        - insertall - default False - if set, reinserts all files whether
+          they have changed or not. Otherwise, only inserts new or changed
+          files
     """
     # set up the logger
     logfile = kw.pop('logfile', sys.stderr)
@@ -7147,6 +7234,8 @@
     self.verbosity = kw.get('verbosity', 0)
     self.Verbosity = kw.get('Verbosity', 0)

+    #print "SiteMgr: verbosity=%s" % self.verbosity
+
     self.fcpHost = fcpHost
     self.fcpPort = fcpPort

@@ -7165,6 +7254,8 @@
     else:
         self.maxconcurrent = 10

+    self.insertall = kw.get('insertall', False)
+
     self.kw = kw

     self.node = None
@@ -7182,6 +7273,9 @@

     self.configFile = configFile

+    # a map of sitenames -&gt; filesets
+    self.siteManifests = {}
+
     if os.path.isfile(configFile):
         self.loadConfig()
     else:
@@ -7240,12 +7334,53 @@
         conf.set("DEFAULT", "fcpport", str(self.fcpPort))


+    manifests = self.siteManifests
+
     for sitename in conf.sections():

         if not conf.has_option(sitename, "dir"):
             raise Exception("Config file error: No directory specified for 
site '%s'" \
                             % sitename)

+        # get or create a site record dict
+        siterec = manifests.get(sitename, None)
+        if not siterec:
+            siterec = manifests[sitename] = {}
+
+        # grab the manifests
+        for name in conf.options(sitename):
+            #print "name=%s" % name
+            
+            if name.startswith("files."):
+                # get or create a file record
+                _ignore, n, attr = name.split(".")
+                n = int(n)
+                val = conf.get(sitename, name)
+
+                filerec = siterec.get(n, None)
+                if not filerec:
+                    filerec = siterec[n] = {}
+                filerec[attr] = val
+                
+                # now remove from section
+                conf.remove_option(sitename, name)
+
+        # change the siterec to a dict of filename-&gt;uri 
+        newrec = {}
+        siteDir = conf.get(sitename, "dir")
+        for rec in siterec.values():
+            newrec[rec['relpath']] = {
+                'fullpath': os.path.join(siteDir, rec['relpath']),
+                'uri': rec['uri'],
+                'hash': rec['hash'],
+                'mimetype' : guessMimetype(rec['relpath']),
+                'changed' : False,
+                }
+        manifests[sitename] = newrec
+           
+    #print manifests
+
+
 </t>
 <t tx="aum.20060511114604.1">def saveConfig(self):
     """
@@ -7320,6 +7455,8 @@
 <t tx="aum.20060511130507">def help():

     print "%s: A console-based, cron-able freesite inserter" % sys.argv[0]
+    print "That manages and inserts your freesites as USKs"
+    print
     print "Usage: %s" % sys.argv[0]

     print "This utility inserts/updates freesites, and is"
@@ -8582,7 +8719,7 @@
     """
     dump help info and exit
     """
-    print "%s: a console-based freesite insertion utility" % progname
+    print "%s: a console-based USK freesite insertion utility" % progname

     print "Usage: %s [options] &lt;command&gt; &lt;args&gt;" % progname
     print "Options:"
@@ -8591,7 +8728,7 @@
     print "  -f, --file=filename"
     print "          - use a different config file (default is %s)" % confFile
     print "  -v, --verbose"
-    print "          - run verbosely"
+    print "          - run verbosely, set this twice for even more noise"
     print "  -q, --quiet"
     print "          - run quietly"
     print "  -l, --logfile=filename"
@@ -8612,6 +8749,10 @@
     print "            limits the number of simultaneous file inserts,"
     print "            to avoid unduly thrashing the node"
     print "            setting this option also sets -s and -a"
+    print "  -i, --insert-all"
+    print "          - if set, force insertion of all files, even ones that"
+    print "            aren't new or haven't changed. Otherwise, only insert"
+    print "            new/changed files."
     print
     print "Available Commands:"
     print "  setup          - create/edit freesite config file interactively"
@@ -8628,20 +8769,23 @@
     # default job options
     opts = {
             "configfile" : confFile,
-            "verbosity" : fcp.node.INFO,
+            "verbosity" : fcp.node.ERROR,
+            "Verbosity" :1023,
             "logfile" : logFile,
             "filebyfile" : False,
             "allatonce" : False,
             "maxconcurrent" : 10,
+            "insertall" : False,
             }

     # process command line switches
     try:
         cmdopts, args = getopt.getopt(
             sys.argv[1:],
-            "?hvf:l:sam:",
+            "?hvf:l:sam:i",
             ["help", "verbose", "file=", "logfile=",
              "single-files", "all-at-once", "max-concurrent=",
+             "insert-all",
              ]
             )
     except getopt.GetoptError:
@@ -8658,8 +8802,7 @@
             sys.exit(0)

         if o in ("-v", "--verbosity"):
-            opts['verbosity'] = fcp.node.DETAIL
-            opts['Verbosity'] = 1023
+            opts['verbosity'] += 1

         if o in ("-q", "--quiet"):
             opts['verbosity'] = fcp.node.SILENT
@@ -8679,6 +8822,9 @@
         if o in ("-m", "--max-concurrent"):
             opts['maxconcurrent'] = int(a)

+        if o in ("-i", "--insert-all"):
+            opts['insertall'] = True
+
     # process command
     if len(args) &lt; 1:
         usage(msg="No command given")
@@ -8872,7 +9018,7 @@
         print "No such freesite '%s'" % sitename
         return

-    if getyesno("Are you sure you wish to delete freesite '%s'", False):
+    if getyesno("Are you sure you wish to delete freesite '%s'" % sitename, 
False):
         sitemgr.removeSite(sitename)
         print "Removed freesite '%s'" % sitename
     else:
@@ -13519,9 +13665,9 @@
 Generates n random files into a temporary directory
 """

-nfiles = 2
-maxConcurrent = 10
-tmpDir = "/tmp/freesitetest"
+nfiles = 1
+maxConcurrent = 2
+tmpDir = "/tmp/putdirtest"

 import sys, os, time
 import fcp

Modified: trunk/apps/pyFreenet/fcp/node.py
===================================================================
--- trunk/apps/pyFreenet/fcp/node.py    2006-06-08 04:42:51 UTC (rev 9081)
+++ trunk/apps/pyFreenet/fcp/node.py    2006-06-08 09:01:04 UTC (rev 9082)
@@ -1,4 +1,7 @@
 #!/usr/bin/env python
+#@+leo-ver=4
+#@+node:@file node.py
+#@@first
 """
 An implementation of a freenet client library for
 FCP v2, offering considerable flexibility.
@@ -13,10 +16,14 @@

 """

+#@+others
+#@+node:imports
 import sys, os, socket, time, thread
 import threading, mimetypes, sha, Queue
 import select, traceback, base64

+#@-node:imports
+#@+node:exceptions
 class ConnectionRefused(Exception):
     """
     cannot connect to given host/port
@@ -49,6 +56,8 @@
 class FCPProtocolError(FCPException):
     pass

+#@-node:exceptions
+#@+node:globals
 # where we can find the freenet node FCP port
 defaultFCPHost = "127.0.0.1"
 defaultFCPPort = 9481
@@ -83,6 +92,8 @@

 defaultVerbosity = ERROR

+#@-node:globals
+#@+node:class FCPNodeConnection
 class FCPNode:
     """
     Represents an interface to a freenet node via its FCP port,
@@ -127,8 +138,12 @@
           calling modes.

     """
+    #@    @+others
+    #@+node:attribs
     noCloseSocket = True

+    #@-node:attribs
+    #@+node:__init__
     def __init__(self, **kw):
         """
         Create a connection object
@@ -198,6 +213,8 @@
         self.running = True
         thread.start_new_thread(self._mgrThread, ())

+    #@-node:__init__
+    #@+node:__del__
     def __del__(self):
         """
         object is getting cleaned up, so disconnect
@@ -209,8 +226,12 @@
             traceback.print_exc()
             pass

+    #@-node:__del__
+    #@+node:FCP Primitives
     # basic FCP primitives

+    #@+others
+    #@+node:genkey
     def genkey(self, **kw):
         """
         Generates and returns an SSK keypair
@@ -246,6 +267,8 @@

         return pub, priv

+    #@-node:genkey
+    #@+node:get
     def get(self, uri, **kw):
         """
         Does a direct get of a key
@@ -339,6 +362,8 @@
         # now enqueue the request
         return self._submitCmd(id, "ClientGet", **opts)

+    #@-node:get
+    #@+node:put
     def put(self, uri="CHK@", **kw):
         """
         Inserts a key
@@ -469,6 +494,8 @@
         # now dispatch the job
         return self._submitCmd(id, "ClientPut", **opts)

+    #@-node:put
+    #@+node:putdir
     def putdir(self, uri, **kw):
         """
         Inserts a freesite
@@ -521,6 +548,9 @@
         # buffer ourselves, not just drop it through as a bunch of keywords,
         # since we want to control the order of keyword lines

+        chkonly = False
+        #chkonly = True
+    
         # get keyword args
         dir = kw['dir']
         sitename = kw.get('name', 'freesite')
@@ -556,11 +586,29 @@
             uriFull = uriFull.replace("SSK@", "USK@")
             while uriFull.endswith("/"):
                 uriFull = uriFull[:-1]
+    
+        manifestDict = kw.get('manifest', None)
+    
+        if manifestDict:
+            # work from the manifest provided by caller
+            #print "got manifest kwd"
+            #print manifestDict
+            manifest = []
+            for relpath, attrDict in manifestDict.items():
+                if attrDict['changed']:
+                    attrDict['relpath'] = relpath
+                    attrDict['fullpath'] = os.path.join(dir, relpath)
+                    manifest.append(attrDict)
+        else:
+            # build manifest by reading the directory
+            #print "no manifest kwd"
+            manifest = readdir(kw['dir'])
+            manifestDict = {}
+            for rec in manifest:
+                manifestDict[rec['relpath']] = rec
+            #print manifestDict

-        # scan directory and add its files
-        manifest = readdir(kw['dir'])
-        
-        manifestDict = {}
+    
         jobs = []
         #allAtOnce = False

@@ -614,7 +662,7 @@
                 fullpath = filerec['fullpath']
                 mimetype = filerec['mimetype']

-                manifestDict[relpath] = filerec
+                #manifestDict[relpath] = filerec

                 log(INFO, "Launching insert of %s" % relpath)

@@ -628,6 +676,7 @@
                                mimetype=mimetype,
                                async=1,
                                verbosity=verbosity,
+                               chkonly=chkonly,
                                )
                 jobs.append(job)
                 filerec['job'] = job
@@ -667,6 +716,9 @@
             fullpath = filerec['fullpath']
             mimetype = filerec['mimetype']

+            # update the uri in the manifest dict
+            manifestDict[relpath]['uri'] = job.uri
+    
             # don't add if the file failed to insert
             if filebyfile:
                 if isinstance(filerec['job'].result, Exception):
@@ -697,17 +749,22 @@

         # --------------------------------------
         # now dispatch the job
-        finalResult = self._submitCmd(
-                        id, "ClientPutComplexDir",
-                        rawcmd=fullbuf,
-                        async=kw.get('async', False),
-                        callback=kw.get('callback', False),
-                        Persistence=kw.get('Persistence', 'connection'),
-                        )
+        if chkonly:
+            finalResult = "no_uri"
+        else:
+            finalResult = self._submitCmd(
+                            id, "ClientPutComplexDir",
+                            rawcmd=fullbuf,
+                            async=kw.get('async', False),
+                            callback=kw.get('callback', False),
+                            Persistence=kw.get('Persistence', 'connection'),
+                            )

         # finally all done, return result or job ticket
         return finalResult

+    #@-node:putdir
+    #@+node:invertprivate
     def invertprivate(self, privatekey):
         """
         Converts an SSK or USK private key to a public equivalent
@@ -722,21 +779,31 @@

         return uri

+    #@-node:invertprivate
+    #@-others

+    #@-node:FCP Primitives
+    #@+node:Other High Level Methods
     # high level client methods

+    #@+others
+    #@+node:listenGlobal
     def listenGlobal(self, **kw):
         """
         Enable listening on global queue
         """
         self._submitCmd(None, "WatchGlobal", Enabled="true", **kw)

+    #@-node:listenGlobal
+    #@+node:ignoreGlobal
     def ignoreGlobal(self, **kw):
         """
         Stop listening on global queue
         """
         self._submitCmd(None, "WatchGlobal", Enabled="false", **kw)

+    #@-node:ignoreGlobal
+    #@+node:purgePersistentJobs
     def purgePersistentJobs(self):
         """
         Cancels all persistent jobs in one go
@@ -744,30 +811,40 @@
         for job in self.getPersistentJobs():
             job.cancel()

+    #@-node:purgePersistentJobs
+    #@+node:getAllJobs
     def getAllJobs(self):
         """
         Returns a list of persistent jobs, excluding global jobs
         """
         return self.jobs.values()

+    #@-node:getAllJobs
+    #@+node:getPersistentJobs
     def getPersistentJobs(self):
         """
         Returns a list of persistent jobs, excluding global jobs
         """
         return [j for j in self.jobs.values() if j.isPersistent and not 
j.isGlobal]

+    #@-node:getPersistentJobs
+    #@+node:getGlobalJobs
     def getGlobalJobs(self):
         """
         Returns a list of global jobs
         """
         return [j for j in self.jobs.values() if j.isGlobal]

+    #@-node:getGlobalJobs
+    #@+node:getTransientJobs
     def getTransientJobs(self):
         """
         Returns a list of non-persistent, non-global jobs
         """
         return [j for j in self.jobs.values() if not j.isPersistent]

+    #@-node:getTransientJobs
+    #@+node:refreshPersistentRequests
     def refreshPersistentRequests(self, **kw):
         """
         Sends a ListPersistentRequests to node, to ensure that
@@ -798,12 +875,16 @@
         # now enqueue the request
         return self._submitCmd(id, "ListPersistentRequests", **opts)

+    #@-node:refreshPersistentRequests
+    #@+node:setVerbosity
     def setVerbosity(self, verbosity):
         """
         Sets the verbosity for future logging calls
         """
         self.verbosity = verbosity

+    #@-node:setVerbosity
+    #@+node:shutdown
     def shutdown(self):
         """
         Terminates the manager thread
@@ -826,11 +907,17 @@
         if self.logfile not in [sys.stdout, sys.stderr]:
             self.logfile.close()

+    #@-node:shutdown
+    #@-others



+    #@-node:Other High Level Methods
+    #@+node:Manager Thread
     # methods for manager thread

+    #@+others
+    #@+node:_mgrThread
     def _mgrThread(self):
         """
         This thread is the nucleus of pyfcp, and coordinates incoming
@@ -866,19 +953,23 @@
                     log(DEBUG, "No incoming client req")
                     pass

-            self._log(INFO, "Manager thread terminated normally")
+            self._log(DETAIL, "Manager thread terminated normally")
             return

         except:
             traceback.print_exc()
             self._log(CRITICAL, "manager thread crashed")

+    #@-node:_mgrThread
+    #@+node:_msgIncoming
     def _msgIncoming(self):
         """
         Returns True if a message is coming in from the node
         """
         return len(select.select([self.socket], [], [], pollTimeout)[0]) > 0

+    #@-node:_msgIncoming
+    #@+node:_submitCmd
     def _submitCmd(self, id, cmd, **kw):
         """
         Submits a command for execution
@@ -923,6 +1014,8 @@
             self._log(DETAIL, "Waiting on job")
             return job.wait()

+    #@-node:_submitCmd
+    #@+node:_on_rxMsg
     def _on_rxMsg(self, msg):
         """
         Handles incoming messages from node
@@ -1112,6 +1205,8 @@
         job.callback('failed', msg)
         job._putResult(FCPException(msg))
         return
+    #@-node:_on_rxMsg
+    #@+node:_on_clientReq
     def _on_clientReq(self, job):
         """
         takes an incoming request job from client and transmits it to
@@ -1131,9 +1226,15 @@

         job.reqSentLock.release()

+    #@-node:_on_clientReq
+    #@-others

+    #@-node:Manager Thread
+    #@+node:Low Level Methods
     # low level noce comms methods

+    #@+others
+    #@+node:_hello
     def _hello(self):
         """
         perform the initial FCP protocol handshake
@@ -1145,12 +1246,16 @@
         resp = self._rxMsg()
         return resp

+    #@-node:_hello
+    #@+node:_getUniqueId
     def _getUniqueId(self):
         """
         Allocate a unique ID for a request
         """
         return "id" + str(int(time.time() * 1000000))

+    #@-node:_getUniqueId
+    #@+node:_txMsg
     def _txMsg(self, msgType, **kw):
         """
         low level message send
@@ -1204,6 +1309,8 @@

         self.socket.send(raw)

+    #@-node:_txMsg
+    #@+node:_rxMsg
     def _rxMsg(self):
         """
         Receives and returns a message as a dict
@@ -1287,6 +1394,8 @@
         # all done
         return items

+    #@-node:_rxMsg
+    #@+node:_log
     def _log(self, level, msg):
         """
         Logs a message. If level > verbosity, don't output it
@@ -1299,7 +1408,13 @@
         self.logfile.write(msg)
         self.logfile.flush()

+    #@-node:_log
+    #@-others
+    #@-node:Low Level Methods
+    #@-others

+#@-node:class FCPNodeConnection
+#@+node:class JobTicket
 class JobTicket:
     """
     A JobTicket is an object returned to clients making
@@ -1322,6 +1437,8 @@
         - msgs - any messages received from node in connection
           to this job
     """
+    #@    @+others
+    #@+node:__init__
     def __init__(self, node, id, cmd, kw):
         """
         You should never instantiate a JobTicket object yourself
@@ -1358,12 +1475,16 @@
         self.reqSentLock = threading.Lock()
         self.reqSentLock.acquire()

+    #@-node:__init__
+    #@+node:isComplete
     def isComplete(self):
         """
         Returns True if the job has been completed
         """
         return self.result != None

+    #@-node:isComplete
+    #@+node:wait
     def wait(self, timeout=None):
         """
         Waits forever (or for a given timeout) for a job to complete
@@ -1373,12 +1494,16 @@
         self.lock.release()

         return self.getResult()
+    #@-node:wait
+    #@+node:waitTillReqSent
     def waitTillReqSent(self):
         """
         Waits till the request has been sent to node
         """
         self.reqSentLock.acquire()

+    #@-node:waitTillReqSent
+    #@+node:getResult
     def getResult(self):
         """
         Returns result of job, or None if job still not complete
@@ -1390,6 +1515,8 @@
         else:
             return self.result

+    #@-node:getResult
+    #@+node:callback
     def callback(self, status, value):
         """
         This will be replaced in job ticket instances wherever
@@ -1397,6 +1524,8 @@
         """
         # no action needed

+    #@-node:callback
+    #@+node:cancel
     def cancel(self):
         """
         Cancels the job, if it is persistent
@@ -1422,9 +1551,13 @@
                          Global=isGlobal,
                          Identifier=self.id)

+    #@-node:cancel
+    #@+node:_appendMsg
     def _appendMsg(self, msg):
         self.msgs.append(msg)

+    #@-node:_appendMsg
+    #@+node:_putResult
     def _putResult(self, result):
         """
         Called by manager thread to indicate job is complete,
@@ -1440,6 +1573,8 @@

         self.lock.release()

+    #@-node:_putResult
+    #@+node:__repr__
     def __repr__(self):
         if self.kw.has_key("URI"):
             uri = " URI=%s" % self.kw['URI']
@@ -1447,7 +1582,13 @@
             uri = ""
         return "<FCP job %s:%s%s" % (self.id, self.cmd, uri)

+    #@-node:__repr__
+    #@-others

+#@-node:class JobTicket
+#@+node:util funcs
+#@+others
+#@+node:toBool
 def toBool(arg):
     try:
         arg = int(arg)
@@ -1467,6 +1608,8 @@
     else:
         return False

+#@-node:toBool
+#@+node:readdir
 def readdir(dirpath, prefix='', gethashes=False):
     """
     Reads a directory, returning a sequence of file dicts.
@@ -1515,6 +1658,8 @@

     return entries

+#@-node:readdir
+#@+node:guessMimetype
 def guessMimetype(filename):
     """
     Returns a guess of a mimetype based on a filename's extension
@@ -1523,6 +1668,8 @@
     if m == None:
         m = "text/plain"
     return m
+#@-node:guessMimetype
+#@+node:uriIsPrivate
 def uriIsPrivate(uri):
     """
     analyses an SSK URI, and determines if it is an SSK or USK private key
@@ -1542,7 +1689,11 @@

     return False

+#@-node:uriIsPrivate
+#@+node:base64 stuff
 # functions to encode/decode base64, freenet alphabet
+#@+others
+#@+node:base64encode
 def base64encode(raw):
     """
     Encodes a string to base64, using the Freenet alphabet
@@ -1558,6 +1709,8 @@

     return enc

+#@-node:base64encode
+#@+node:base64decode
 def base64decode(enc):
     """
     Decodes a freenet-encoded base64 string back to a binary string
@@ -1575,7 +1728,15 @@

     return raw

+#@-node:base64decode
+#@-others

+#@-node:base64 stuff
+#@-others

+#@-node:util funcs
+#@-others


+#@-node:@file node.py
+#@-leo

Modified: trunk/apps/pyFreenet/fcp/sitemgr.py
===================================================================
--- trunk/apps/pyFreenet/fcp/sitemgr.py 2006-06-08 04:42:51 UTC (rev 9081)
+++ trunk/apps/pyFreenet/fcp/sitemgr.py 2006-06-08 09:01:04 UTC (rev 9082)
@@ -8,7 +8,7 @@

 # fcp imports
 import node
-from node import FCPNode
+from node import FCPNode, guessMimetype
 from node import SILENT, FATAL, CRITICAL, ERROR, INFO, DETAIL, DEBUG

 fcpHost = node.defaultFCPHost
@@ -43,6 +43,9 @@
             - maxconcurrent - default 10 - if set, this also sets filebyfile
               and allatonce both to True. Value of maxconcurrent is the
               maximum number of concurrent inserts
+            - insertall - default False - if set, reinserts all files whether
+              they have changed or not. Otherwise, only inserts new or changed
+              files
         """
         # set up the logger
         logfile = kw.pop('logfile', sys.stderr)
@@ -55,6 +58,8 @@
         self.verbosity = kw.get('verbosity', 0)
         self.Verbosity = kw.get('Verbosity', 0)

+        #print "SiteMgr: verbosity=%s" % self.verbosity
+    
         self.fcpHost = fcpHost
         self.fcpPort = fcpPort

@@ -73,6 +78,8 @@
         else:
             self.maxconcurrent = 10

+        self.insertall = kw.get('insertall', False)
+    
         self.kw = kw

         self.node = None
@@ -90,6 +97,9 @@

         self.configFile = configFile

+        # a map of sitenames -> filesets
+        self.siteManifests = {}
+    
         if os.path.isfile(configFile):
             self.loadConfig()
         else:
@@ -156,12 +166,53 @@
             conf.set("DEFAULT", "fcpport", str(self.fcpPort))


+        manifests = self.siteManifests
+    
         for sitename in conf.sections():

             if not conf.has_option(sitename, "dir"):
                 raise Exception("Config file error: No directory specified for 
site '%s'" \
                                 % sitename)

+            # get or create a site record dict
+            siterec = manifests.get(sitename, None)
+            if not siterec:
+                siterec = manifests[sitename] = {}
+    
+            # grab the manifests
+            for name in conf.options(sitename):
+                #print "name=%s" % name
+                
+                if name.startswith("files."):
+                    # get or create a file record
+                    _ignore, n, attr = name.split(".")
+                    n = int(n)
+                    val = conf.get(sitename, name)
+    
+                    filerec = siterec.get(n, None)
+                    if not filerec:
+                        filerec = siterec[n] = {}
+                    filerec[attr] = val
+                    
+                    # now remove from section
+                    conf.remove_option(sitename, name)
+    
+            # change the siterec to a dict of filename->uri 
+            newrec = {}
+            siteDir = conf.get(sitename, "dir")
+            for rec in siterec.values():
+                newrec[rec['relpath']] = {
+                    'fullpath': os.path.join(siteDir, rec['relpath']),
+                    'uri': rec['uri'],
+                    'hash': rec['hash'],
+                    'mimetype' : guessMimetype(rec['relpath']),
+                    'changed' : False,
+                    }
+            manifests[sitename] = newrec
+               
+        #print manifests
+    
+    
     def saveConfig(self):
         """
         Saves the amended config file to disk
@@ -301,6 +352,8 @@

         conf = self.config
         for sitename in conf.sections():
+            
+            print "Updating site '%s'" % sitename

             # fill in any incomplete details with site entries
             needToSave = False
@@ -318,6 +371,7 @@
                 uri = pub.replace("SSK@", "USK@") + sitename + "/0"
                 conf.set(sitename, "uri", uri)
                 conf.set(sitename, "privatekey", priv)
+    
             if needToSave:
                 self.saveConfig()

@@ -328,11 +382,51 @@
             privatekey = conf.get(sitename, "privatekey")

             files = node.readdir(dir, gethashes=True)
+    
+            # get old manifest - dict of path->{'uri':uri, 'hash':hash} 
mappings
+            siterec = self.siteManifests[sitename]
+    
+            #print "readdir files:"
+            #print files
+            #print "siterec files:"
+            #print siterec
+    
+            # determine 'site hash'
             h = sha.new()
             for f in files:
+                relpath = f['relpath']
+                if siterec.has_key(relpath):
+                    filerec = siterec[relpath]
+                    filehash = f['hash']
+                    if (filerec['hash'] != f['hash']) or self.insertall:
+                        #print "File: %s" % relpath
+                        #print "  oldhash=%s" % filerec['hash']
+                        #print "  newhash=%s" % f['hash']
+                        filerec['changed'] = True
+                else:
+                    # new file, create new record and mark as changed
+                    #print "new file %s" % relpath
+                    siterec[relpath] = {'uri':"",
+                                        'hash':f['hash'],
+                                        'changed':True,
+                                        'fullpath': os.path.join(dir, relpath),
+                                        'mimetype': guessMimetype(relpath),
+                                        }
                 h.update(f['hash'])
+    
+            # dump deleted files from siterec
+            filenames = [f['relpath'] for f in files]
+            for relpath in siterec.keys():
+                if relpath not in filenames:
+                    del siterec[relpath]
+            
             hashNew = h.hexdigest()
-            if hashNew != hash:
+    
+            # don't insert site if it hasn't changed
+            if (hashNew == hash) and not self.insertall:
+                print "Site '%s' unchanged, skipping update" % sitename
+            else:
+                # site has changed, so better insert it
                 log(INFO, "Updating site %s" % sitename)
                 log(INFO, "privatekey=%s" % privatekey)
                 noSites = False
@@ -346,20 +440,33 @@
                                         verbosity=self.Verbosity,
                                         filebyfile=self.filebyfile,
                                         allatonce=self.allatonce,
-                                        maxconcurrent=self.maxconcurrent)
-                    log(INFO, "site %s updated successfully" % sitename)
+                                        maxconcurrent=self.maxconcurrent,
+                                        manifest=siterec,
+                                        insertall=self.insertall)
+        
+                    print "Site '%s' updated successfully" % sitename
                 except:
                     traceback.print_exc()
-                    log(ERROR, "site %s failed to update" % sitename)
-                conf.set(sitename, "hash", hashNew)
-            else:
-                log(INFO, "Site %s not changed, no need to update" % sitename)
+                    print "site '%s' failed to update" % sitename

+            # update the site's global hash
+            conf.set(sitename, "hash", hashNew)
+    
+            # and re-populate the manifest
+            i = 0
+            for relpath, attrdict in siterec.items():
+                #print attrdict
+                conf.set(sitename, "files.%d.relpath" % i, relpath)
+                conf.set(sitename, "files.%d.uri" % i, attrdict['uri'])
+                conf.set(sitename, "files.%d.hash" % i, attrdict['hash'])
+                conf.set(sitename, "files.%d.mimetype" % i, 
attrdict['mimetype'])
+                i += 1
+    
+        # save the updated config
         self.saveConfig()

         if noSites:
             log(INFO, "No sites needed updating")
-    
     def shutdown(self):
         self.node.shutdown()

@@ -379,6 +486,8 @@
 def help():

     print "%s: A console-based, cron-able freesite inserter" % sys.argv[0]
+    print "That manages and inserts your freesites as USKs"
+    print
     print "Usage: %s" % sys.argv[0]

     print "This utility inserts/updates freesites, and is"

Modified: trunk/apps/pyFreenet/freesitemgr
===================================================================
--- trunk/apps/pyFreenet/freesitemgr    2006-06-08 04:42:51 UTC (rev 9081)
+++ trunk/apps/pyFreenet/freesitemgr    2006-06-08 09:01:04 UTC (rev 9082)
@@ -118,7 +118,7 @@
         print "No such freesite '%s'" % sitename
         return

-    if getyesno("Are you sure you wish to delete freesite '%s'", False):
+    if getyesno("Are you sure you wish to delete freesite '%s'" % sitename, 
False):
         sitemgr.removeSite(sitename)
         print "Removed freesite '%s'" % sitename
     else:
@@ -145,7 +145,7 @@
     """
     dump help info and exit
     """
-    print "%s: a console-based freesite insertion utility" % progname
+    print "%s: a console-based USK freesite insertion utility" % progname

     print "Usage: %s [options] <command> <args>" % progname
     print "Options:"
@@ -154,7 +154,7 @@
     print "  -f, --file=filename"
     print "          - use a different config file (default is %s)" % confFile
     print "  -v, --verbose"
-    print "          - run verbosely"
+    print "          - run verbosely, set this twice for even more noise"
     print "  -q, --quiet"
     print "          - run quietly"
     print "  -l, --logfile=filename"
@@ -175,6 +175,10 @@
     print "            limits the number of simultaneous file inserts,"
     print "            to avoid unduly thrashing the node"
     print "            setting this option also sets -s and -a"
+    print "  -i, --insert-all"
+    print "          - if set, force insertion of all files, even ones that"
+    print "            aren't new or haven't changed. Otherwise, only insert"
+    print "            new/changed files."
     print
     print "Available Commands:"
     print "  setup          - create/edit freesite config file interactively"
@@ -198,20 +202,23 @@
     # default job options
     opts = {
             "configfile" : confFile,
-            "verbosity" : fcp.node.INFO,
+            "verbosity" : fcp.node.ERROR,
+            "Verbosity" :1023,
             "logfile" : logFile,
             "filebyfile" : False,
             "allatonce" : False,
             "maxconcurrent" : 10,
+            "insertall" : False,
             }

     # process command line switches
     try:
         cmdopts, args = getopt.getopt(
             sys.argv[1:],
-            "?hvf:l:sam:",
+            "?hvf:l:sam:i",
             ["help", "verbose", "file=", "logfile=",
              "single-files", "all-at-once", "max-concurrent=",
+             "insert-all",
              ]
             )
     except getopt.GetoptError:
@@ -228,8 +235,7 @@
             sys.exit(0)

         if o in ("-v", "--verbosity"):
-            opts['verbosity'] = fcp.node.DETAIL
-            opts['Verbosity'] = 1023
+            opts['verbosity'] += 1

         if o in ("-q", "--quiet"):
             opts['verbosity'] = fcp.node.SILENT
@@ -249,6 +255,9 @@
         if o in ("-m", "--max-concurrent"):
             opts['maxconcurrent'] = int(a)

+        if o in ("-i", "--insert-all"):
+            opts['insertall'] = True
+
     # process command
     if len(args) < 1:
         usage(msg="No command given")

Modified: trunk/apps/pyFreenet/freesitemgr.py
===================================================================
--- trunk/apps/pyFreenet/freesitemgr.py 2006-06-08 04:42:51 UTC (rev 9081)
+++ trunk/apps/pyFreenet/freesitemgr.py 2006-06-08 09:01:04 UTC (rev 9082)
@@ -118,7 +118,7 @@
         print "No such freesite '%s'" % sitename
         return

-    if getyesno("Are you sure you wish to delete freesite '%s'", False):
+    if getyesno("Are you sure you wish to delete freesite '%s'" % sitename, 
False):
         sitemgr.removeSite(sitename)
         print "Removed freesite '%s'" % sitename
     else:
@@ -145,7 +145,7 @@
     """
     dump help info and exit
     """
-    print "%s: a console-based freesite insertion utility" % progname
+    print "%s: a console-based USK freesite insertion utility" % progname

     print "Usage: %s [options] <command> <args>" % progname
     print "Options:"
@@ -154,7 +154,7 @@
     print "  -f, --file=filename"
     print "          - use a different config file (default is %s)" % confFile
     print "  -v, --verbose"
-    print "          - run verbosely"
+    print "          - run verbosely, set this twice for even more noise"
     print "  -q, --quiet"
     print "          - run quietly"
     print "  -l, --logfile=filename"
@@ -175,6 +175,10 @@
     print "            limits the number of simultaneous file inserts,"
     print "            to avoid unduly thrashing the node"
     print "            setting this option also sets -s and -a"
+    print "  -i, --insert-all"
+    print "          - if set, force insertion of all files, even ones that"
+    print "            aren't new or haven't changed. Otherwise, only insert"
+    print "            new/changed files."
     print
     print "Available Commands:"
     print "  setup          - create/edit freesite config file interactively"
@@ -198,20 +202,23 @@
     # default job options
     opts = {
             "configfile" : confFile,
-            "verbosity" : fcp.node.INFO,
+            "verbosity" : fcp.node.ERROR,
+            "Verbosity" :1023,
             "logfile" : logFile,
             "filebyfile" : False,
             "allatonce" : False,
             "maxconcurrent" : 10,
+            "insertall" : False,
             }

     # process command line switches
     try:
         cmdopts, args = getopt.getopt(
             sys.argv[1:],
-            "?hvf:l:sam:",
+            "?hvf:l:sam:i",
             ["help", "verbose", "file=", "logfile=",
              "single-files", "all-at-once", "max-concurrent=",
+             "insert-all",
              ]
             )
     except getopt.GetoptError:
@@ -228,8 +235,7 @@
             sys.exit(0)

         if o in ("-v", "--verbosity"):
-            opts['verbosity'] = fcp.node.DETAIL
-            opts['Verbosity'] = 1023
+            opts['verbosity'] += 1

         if o in ("-q", "--quiet"):
             opts['verbosity'] = fcp.node.SILENT
@@ -249,6 +255,9 @@
         if o in ("-m", "--max-concurrent"):
             opts['maxconcurrent'] = int(a)

+        if o in ("-i", "--insert-all"):
+            opts['insertall'] = True
+
     # process command
     if len(args) < 1:
         usage(msg="No command given")

Modified: trunk/apps/pyFreenet/manpages/freesitemgr.1
===================================================================
--- trunk/apps/pyFreenet/manpages/freesitemgr.1 2006-06-08 04:42:51 UTC (rev 
9081)
+++ trunk/apps/pyFreenet/manpages/freesitemgr.1 2006-06-08 09:01:04 UTC (rev 
9082)
@@ -37,13 +37,36 @@

 \fB\-s\fR, \fB\-\-single\-files\fR
 Insert each file of the freesite as single CHKs, and redirect
-to these from within the freesite manifest
+to these from within the freesite manifest. You \fBmust\fR use
+this option if inserting through an FCP port not on your local machine.
+Note that files will be inserted one at a time, with each next file
+not being inserted till the previous one is completely inserted. If you
+wish to insert more than one file concurrently, use the \fB\-a\fR
+or \fB\-m\fR options.
+.TP 

+\fB\-a\fR, \fB\-\-all\-at\-once\fR
+Enables concurrent insert of the site's files. By default, a maximum
+of 10 files will be inserting at any one time. You can increase this with
+\fB\-m\fR. Note that this \fB\-a\fR option automatically turns on
+\fB\-s\fR.
+.TP 
+
+\fB\-m\fR, \fB\-\-max\-concurrent\-inserts=\fR\fInum\fR
+Turns on \fB\-s\fR and \fB\-a\fR, and sets the maximum number
+of concurrent file inserts to \fInum\fR. You can set a very high
+value if you want, but it will be of little use because the node
+itself regulates how many concurrent inserts it performs.
+.TP 
+
+\fB\-i\fR, \fB\-\-insert\-all\fR
+Forces insert of all files in all aites. Otherwise, sites with no new
+or changed files will be skipped, and in sites that have changed,
+only the new/changed files (and a fresh manifest) will be inserted.
+
 .LP 

 .SH "COMMANDS"
-.LP 
-
 \fBsetup\fP
 Create or modify the freesite config file (\fB~/.freesites\fP)
 interactively.

Modified: trunk/apps/pyFreenet/manpages/freesitemgr.1.html
===================================================================
--- trunk/apps/pyFreenet/manpages/freesitemgr.1.html    2006-06-08 04:42:51 UTC 
(rev 9081)
+++ trunk/apps/pyFreenet/manpages/freesitemgr.1.html    2006-06-08 09:01:04 UTC 
(rev 9082)
@@ -53,7 +53,29 @@
 <DT><DD>
 <B>-s</B>, <B>--single-files</B>
 Insert each file of the freesite as single CHKs, and redirect
-to these from within the freesite manifest
+to these from within the freesite manifest. You <B>must</B> use
+this option if inserting through an FCP port not on your local machine.
+Note that files will be inserted one at a time, with each next file
+not being inserted till the previous one is completely inserted. If you
+wish to insert more than one file concurrently, use the <B>-a</B>
+or <B>-m</B> options.
+<DT><DD>
+<B>-a</B>, <B>--all-at-once</B>
+Enables concurrent insert of the site's files. By default, a maximum
+of 10 files will be inserting at any one time. You can increase this with
+<B>-m</B>. Note that this <B>-a</B> option automatically turns on
+<B>-s</B>.
+<DT><DD>
+<B>-m</B>, <B>--max-concurrent-inserts=</B><I>num</I>
+Turns on <B>-s</B> and <B>-a</B>, and sets the maximum number
+of concurrent file inserts to <I>num</I>. You can set a very high
+value if you want, but it will be of little use because the node
+itself regulates how many concurrent inserts it performs.
+<DT><DD>
+<B>-i</B>, <B>--insert-all</B>
+Forces insert of all files in all aites. Otherwise, sites with no new
+or changed files will be skipped, and in sites that have changed,
+only the new/changed files (and a fresh manifest) will be inserted.
 <P>
 </DL>
 <P>
@@ -62,9 +84,6 @@
 <A NAME="lbAF">&nbsp;</A>
 <H2>COMMANDS</H2>

-<P>
-
-<P>
 <B>setup</B>
 Create or modify the freesite config file (<B>~/.freesites</B>)
 interactively.
@@ -135,6 +154,6 @@
 This document was created by
 <A HREF="/cgi-bin/man/man2html">man2html</A>,
 using the manual pages.<BR>
-Time: 01:47:06 GMT, May 28, 2006
+Time: 08:59:41 GMT, June 08, 2006
 </BODY>
 </HTML>


Reply via email to