diff --git a/apps/syndie/java/build.xml b/apps/syndie/java/build.xml
index af20fcf58a686dfe19328eb2c67e5d0e9b61bac4..59b11223f56beb366f4230db4ceffc39106a9686 100644
--- a/apps/syndie/java/build.xml
+++ b/apps/syndie/java/build.xml
@@ -19,7 +19,7 @@
     <target name="jar" depends="builddep, compile">
         <jar destfile="./build/syndie.jar" basedir="./build/obj" includes="**/*.class">
             <manifest>
-                <attribute name="Main-Class" value="net.i2p.syndie.CLI" />
+                <attribute name="Main-Class" value="net.i2p.syndie.CLIPost" />
                 <attribute name="Class-Path" value="i2p.jar" />
             </manifest>
         </jar>
diff --git a/apps/syndie/java/src/net/i2p/syndie/Archive.java b/apps/syndie/java/src/net/i2p/syndie/Archive.java
index 43bb47402bfeb3ff56085e85762618ef6c769265..0487cabe1f682bffa1c30e92e66e6a1042976b73 100644
--- a/apps/syndie/java/src/net/i2p/syndie/Archive.java
+++ b/apps/syndie/java/src/net/i2p/syndie/Archive.java
@@ -6,6 +6,7 @@ import java.text.*;
 import net.i2p.I2PAppContext;
 import net.i2p.data.*;
 import net.i2p.syndie.data.*;
+import net.i2p.util.Log;
 
 /**
  * Store blog info in the local filesystem.
@@ -25,6 +26,7 @@ import net.i2p.syndie.data.*;
  */
 public class Archive {
     private I2PAppContext _context;
+    private Log _log;
     private File _rootDir;
     private File _cacheDir;
     private Map _blogInfo;
@@ -42,6 +44,7 @@ public class Archive {
     
     public Archive(I2PAppContext ctx, String rootDir, String cacheDir) {
         _context = ctx;
+        _log = ctx.logManager().getLog(Archive.class);
         _rootDir = new File(rootDir);
         if (!_rootDir.exists())
             _rootDir.mkdirs();
@@ -69,12 +72,11 @@ public class Archive {
                         if (bi.verify(_context)) {
                             info.add(bi);
                         } else {
-                            System.err.println("Invalid blog (but we're storing it anyway): " + bi);
-                            new Exception("foo").printStackTrace();
-                            info.add(bi);
+                            _log.error("BlogInfo is invalid: " + bi);
+                            meta.delete();
                         }
                     } catch (IOException ioe) {
-                        ioe.printStackTrace();
+                        _log.error("Error loading the blog", ioe);
                     }
                 }
             }
@@ -110,8 +112,7 @@ public class Archive {
     }
     public boolean storeBlogInfo(BlogInfo info) { 
         if (!info.verify(_context)) {
-            System.err.println("Not storing the invalid blog " + info);
-            new Exception("foo!").printStackTrace();
+            _log.warn("Not storing invalid blog " + info);
             return false;
         }
         boolean isNew = true;
@@ -130,10 +131,11 @@ public class Archive {
             FileOutputStream out = new FileOutputStream(blogFile);
             info.write(out);
             out.close();
-            System.out.println("Blog info written to " + blogFile.getPath());
+            if (_log.shouldLog(Log.DEBUG))
+                _log.debug("Blog info written to " + blogFile.getPath());
             return true;
         } catch (IOException ioe) {
-            ioe.printStackTrace();
+            _log.error("Error writing out info", ioe);
             return false;
         }
     }
@@ -174,8 +176,9 @@ public class Archive {
                     entry = getCachedEntry(entryDir);
                 if ( (entry == null) || (!entryDir.exists()) ) {
                     if (!extractEntry(entries[j], entryDir, info)) {
-                        System.err.println("Entry " + entries[j].getPath() + " is not valid");
-                        new Exception("foo!!").printStackTrace();
+                        if (_log.shouldLog(Log.ERROR))
+                            _log.error("Entry " + entries[j].getPath() + " is not valid");
+                        entries[j].delete();
                         continue;
                     }
                     entry = getCachedEntry(entryDir);
@@ -183,12 +186,13 @@ public class Archive {
                 String tags[] = entry.getTags();
                 for (int t = 0; t < tags.length; t++) {
                     if (!rv.contains(tags[t])) {
-                        System.out.println("Found a new tag in cached " + entry.getURI() + ": " + tags[t]);
+                        if (_log.shouldLog(Log.DEBUG))
+                            _log.debug("Found a new tag in cached " + entry.getURI() + ": " + tags[t]);
                         rv.add(tags[t]);
                     }
                 }
             } catch (IOException ioe) {
-                ioe.printStackTrace();
+                _log.error("Error listing tags", ioe);
             }
         } // end iterating over the entries 
         
@@ -220,7 +224,7 @@ public class Archive {
                 return ce;
             return null;
         } catch (IOException ioe) {
-            ioe.printStackTrace();
+            _log.warn("Error reading cached entry... deleting cache elements");
         }
         
         File files[] = entryDir.listFiles();
@@ -262,8 +266,8 @@ public class Archive {
                         entry = getCachedEntry(entryDir);
                     if ((entry == null) || !entryDir.exists()) {
                         if (!extractEntry(entries[i], entryDir, info)) {
-                            System.err.println("Entry " + entries[i].getPath() + " is not valid");
-                            new Exception("foo!!!!").printStackTrace();
+                            _log.error("Entry " + entries[i].getPath() + " is not valid");
+                            entries[i].delete();
                             continue;
                         }
                         entry = getCachedEntry(entryDir);
@@ -274,8 +278,8 @@ public class Archive {
                     entry.load(new FileInputStream(entries[i]));
                     boolean ok = entry.verifySignature(_context, info);
                     if (!ok) {
-                        System.err.println("Keyed entry " + entries[i].getPath() + " is not valid");
-                        new Exception("foo!!!!!!").printStackTrace();
+                        _log.error("Keyed entry " + entries[i].getPath() + " is not valid");
+                        entries[i].delete();
                         continue;
                     }
 
@@ -294,16 +298,18 @@ public class Archive {
                     for (int j = 0; j < tags.length; j++) {
                         if (tags[j].equals(tag)) {
                             rv.add(entry);
-                            System.out.println("cached entry matched requested tag [" + tag + "]: " + entry.getURI());
+                            if (_log.shouldLog(Log.DEBUG))
+                                _log.debug("cached entry matched requested tag [" + tag + "]: " + entry.getURI());
                             break;
                         }
                     }
                 } else {
-                    System.out.println("cached entry is ok and no id or tag was requested: " + entry.getURI());
+                    if (_log.shouldLog(Log.DEBUG))
+                        _log.debug("cached entry is ok and no id or tag was requested: " + entry.getURI());
                     rv.add(entry);
                 }
             } catch (IOException ioe) {
-                ioe.printStackTrace();
+                _log.error("Error listing entries", ioe);
             }
         }
         return rv;
@@ -322,11 +328,11 @@ public class Archive {
 
         BlogInfo info = getBlogInfo(uri);
         if (info == null) {
-            System.out.println("no blog metadata for the uri " + uri);
+            _log.error("no blog metadata for the uri " + uri);
             return false;
         }
         if (!container.verifySignature(_context, info)) {
-            System.out.println("Not storing the invalid blog entry at " + uri);
+            _log.error("Not storing the invalid blog entry at " + uri);
             return false;
         } else {
             //System.out.println("Signature is valid: " + container.getSignature() + " for info " + info);
@@ -341,7 +347,7 @@ public class Archive {
             container.setCompleteSize(data.length);
             return true;
         } catch (IOException ioe) {
-            ioe.printStackTrace();
+            _log.error("Error storing", ioe);
             return false;
         }
     }
@@ -422,7 +428,7 @@ public class Archive {
             out.write(DataHelper.getUTF8(_index.toString()));
             out.flush();
         } catch (IOException ioe) {
-            ioe.printStackTrace();
+            _log.error("Error writing out the index");
         }
     }
 }
diff --git a/apps/syndie/java/src/net/i2p/syndie/ArchiveIndexer.java b/apps/syndie/java/src/net/i2p/syndie/ArchiveIndexer.java
index 3deb3d1c7bc0965174e0dc45636fc715352a2ef8..a22b31436c101e16125dcc10b2d9d1bb84c4514f 100644
--- a/apps/syndie/java/src/net/i2p/syndie/ArchiveIndexer.java
+++ b/apps/syndie/java/src/net/i2p/syndie/ArchiveIndexer.java
@@ -7,6 +7,7 @@ import net.i2p.I2PAppContext;
 import net.i2p.data.*;
 import net.i2p.syndie.data.*;
 import net.i2p.syndie.sml.*;
+import net.i2p.util.Log;
 
 /**
  * Dig through the archive to build an index
@@ -16,6 +17,7 @@ class ArchiveIndexer {
     private static final int RECENT_ENTRY_COUNT = 10;
     
     public static ArchiveIndex index(I2PAppContext ctx, Archive source) {
+        Log log = ctx.logManager().getLog(ArchiveIndexer.class);
         LocalArchiveIndex rv = new LocalArchiveIndex(ctx);
         rv.setGeneratedOn(ctx.clock().now());
         
@@ -32,7 +34,7 @@ class ArchiveIndexer {
                         rv.setHeader(tok.nextToken(), tok.nextToken());
                 }
             } catch (IOException ioe) {
-                ioe.printStackTrace();
+                log.error("Error reading header file", ioe);
             }
         }
         
@@ -66,7 +68,8 @@ class ArchiveIndexer {
             long metadate = metaFile.lastModified();
 
             List entries = source.listEntries(key, -1, null, null);
-            System.out.println("Entries under " + key + ": " + entries);
+            if (log.shouldLog(Log.DEBUG))
+                log.debug("Entries under " + key + ": " + entries);
             /** tag name --> ordered map of entryId to EntryContainer */
             Map tags = new TreeMap();
             
@@ -83,7 +86,8 @@ class ArchiveIndexer {
                     }
                     Map entriesByTag = (Map)tags.get(entryTags[t]);
                     entriesByTag.put(new Long(0-entry.getURI().getEntryId()), entry);
-                    System.out.println("Entries under tag " + entryTags[t] + ":" + entriesByTag.values());
+                    if (log.shouldLog(Log.DEBUG))
+                        log.debug("Entries under tag " + entryTags[t] + ":" + entriesByTag.values());
                 }
                     
                 if (entry.getURI().getEntryId() >= newSince) {
@@ -97,8 +101,8 @@ class ArchiveIndexer {
                     BlogURI parent = new BlogURI(reply.trim());
                     if ( (parent.getKeyHash() != null) && (parent.getEntryId() >= 0) ) 
                         rv.addReply(parent, entry.getURI());
-                    else
-                        System.err.println("Parent of " + entry.getURI() + " is not valid: [" + reply.trim() + "]");
+                    else if (log.shouldLog(Log.WARN))
+                        log.warn("Parent of " + entry.getURI() + " is not valid: [" + reply.trim() + "]");
                 }
             }
             
diff --git a/apps/syndie/java/src/net/i2p/syndie/BlogManager.java b/apps/syndie/java/src/net/i2p/syndie/BlogManager.java
index e9cba3d9e46ece37b00c7fa349307cacc4b8db00..7e4e18e28a03de5b1e920bb053c6a0d1b5e1ad59 100644
--- a/apps/syndie/java/src/net/i2p/syndie/BlogManager.java
+++ b/apps/syndie/java/src/net/i2p/syndie/BlogManager.java
@@ -10,12 +10,14 @@ import net.i2p.client.naming.PetNameDB;
 import net.i2p.data.*;
 import net.i2p.syndie.data.*;
 import net.i2p.syndie.sml.*;
+import net.i2p.util.Log;
 
 /**
  *
  */
 public class BlogManager {
     private I2PAppContext _context;
+    private Log _log;
     private static BlogManager _instance;
     private File _blogKeyDir;
     private File _privKeyDir;
@@ -28,21 +30,30 @@ public class BlogManager {
     
     static {
         TimeZone.setDefault(TimeZone.getTimeZone("GMT"));
-        String rootDir = I2PAppContext.getGlobalContext().getProperty("syndie.rootDir");
-        if (false) {
-            if (rootDir == null)
-                rootDir = System.getProperty("user.home");
-            rootDir = rootDir + File.separatorChar + ".syndie";
-        } else {
-            if (rootDir == null)
-                rootDir = "./syndie";
+    }
+    
+    public static BlogManager instance() { 
+        synchronized (BlogManager.class) {
+            if (_instance == null) {
+                String rootDir = I2PAppContext.getGlobalContext().getProperty("syndie.rootDir");
+                if (false) {
+                    if (rootDir == null)
+                        rootDir = System.getProperty("user.home");
+                    rootDir = rootDir + File.separatorChar + ".syndie";
+                } else {
+                    if (rootDir == null)
+                        rootDir = "./syndie";
+                }
+                _instance = new BlogManager(I2PAppContext.getGlobalContext(), rootDir);
+            }
+            return _instance; 
         }
-        _instance = new BlogManager(I2PAppContext.getGlobalContext(), rootDir);
     }
-    public static BlogManager instance() { return _instance; }
     
-    public BlogManager(I2PAppContext ctx, String rootDir) {
+    public BlogManager(I2PAppContext ctx, String rootDir) { this(ctx, rootDir, true); }
+    public BlogManager(I2PAppContext ctx, String rootDir, boolean regenIndex) {
         _context = ctx;
+        _log = ctx.logManager().getLog(BlogManager.class);
         _rootDir = new File(rootDir);
         _rootDir.mkdirs();
         readConfig();
@@ -63,7 +74,8 @@ public class BlogManager {
         _userDir.mkdirs();
         _tempDir.mkdirs();
         _archive = new Archive(ctx, _archiveDir.getAbsolutePath(), _cacheDir.getAbsolutePath());
-        _archive.regenerateIndex();
+        if (regenIndex)
+            _archive.regenerateIndex();
     }
     
     private File getConfigFile() { return new File(_rootDir, "syndie.config"); }
@@ -76,13 +88,16 @@ public class BlogManager {
                 for (Iterator iter = p.keySet().iterator(); iter.hasNext(); ) {
                     String key = (String)iter.next();
                     System.setProperty(key, p.getProperty(key));
-                    System.out.println("Read config prop [" + key + "] = [" + p.getProperty(key) + "]");
+                    if (_log.shouldLog(Log.DEBUG))
+                        _log.debug("Read config prop [" + key + "] = [" + p.getProperty(key) + "]");
                 }
             } catch (IOException ioe) {
-                ioe.printStackTrace();
+                if (_log.shouldLog(Log.DEBUG))
+                        _log.debug("Err reading", ioe);
             }
         } else {
-            System.out.println("Config doesn't exist: " + config.getPath());
+            if (_log.shouldLog(Log.DEBUG))
+                _log.debug("Config doesn't exist: " + config.getPath());
         }
     }
     
@@ -97,7 +112,7 @@ public class BlogManager {
                     out.write(DataHelper.getUTF8(name + '=' + _context.getProperty(name) + '\n'));
             }
         } catch (IOException ioe) {
-            ioe.printStackTrace();
+            _log.error("Error writing the config", ioe);
         } finally {
             if (out != null) try { out.close(); } catch (IOException ioe) {}
         }
@@ -116,10 +131,10 @@ public class BlogManager {
             pub.writeBytes(out);
             priv.writeBytes(out);
         } catch (DataFormatException dfe) {
-            dfe.printStackTrace();
+            _log.error("Error creating the blog", dfe);
             return null;
         } catch (IOException ioe) {
-            ioe.printStackTrace();
+            _log.error("Error creating the blog", ioe);
             return null;
         }
         
@@ -181,9 +196,9 @@ public class BlogManager {
                     if (info != null)
                         rv.add(info);
                 } catch (IOException ioe) {
-                    ioe.printStackTrace();
+                    _log.error("Error listing the blog", ioe);
                 } catch (DataFormatException dfe) {
-                    dfe.printStackTrace();
+                    _log.error("Error listing the blog", dfe);
                 }
             }
         }
@@ -201,10 +216,66 @@ public class BlogManager {
             priv.readBytes(in);
             return priv;
         } catch (IOException ioe) {
-            ioe.printStackTrace();
+            _log.error("Error reading the blog key", ioe);
             return null;
         } catch (DataFormatException dfe) {
-            dfe.printStackTrace();
+            _log.error("Error reading the blog key", dfe);
+            return null;
+        }
+    }
+    
+    public User getUser(Hash blog) {
+        File files[] = _userDir.listFiles();
+        for (int i = 0; i < files.length; i++) {
+            if (files[i].isFile() && !files[i].isHidden()) {
+                Properties userProps = loadUserProps(files[i]);
+                if (userProps == null)
+                    continue;
+                User user = new User();
+                user.load(userProps);
+                if (blog.equals(user.getBlog()))
+                    return user;
+            }
+        }
+        return null;
+    }
+    
+    /**
+     * List of User instances
+     */
+    public List listUsers() {
+        File files[] = _userDir.listFiles();
+        List rv = new ArrayList();
+        for (int i = 0; i < files.length; i++) {
+            if (files[i].isFile() && !files[i].isHidden()) {
+                Properties userProps = loadUserProps(files[i]);
+                if (userProps == null)
+                    continue;
+                User user = new User();
+                user.load(userProps);
+                rv.add(user);
+            }
+        }
+        return rv;
+    }
+    
+    private Properties loadUserProps(File userFile) {
+        try {
+            Properties props = new Properties();
+            FileInputStream fin = new FileInputStream(userFile);
+            BufferedReader in = new BufferedReader(new InputStreamReader(fin, "UTF-8"));
+            String line = null;
+            while ( (line = in.readLine()) != null) {
+                int split = line.indexOf('=');
+                if (split <= 0) continue;
+                String key = line.substring(0, split);
+                String val = line.substring(split+1);
+                props.setProperty(key.trim(), val.trim());
+            }
+            String userHash = userFile.getName();
+            props.setProperty(User.PROP_USERHASH, userHash);
+            return props;
+        } catch (IOException ioe) {
             return null;
         }
     }
@@ -214,25 +285,17 @@ public class BlogManager {
         Hash userHash = _context.sha().calculateHash(DataHelper.getUTF8(login));
         Hash passHash = _context.sha().calculateHash(DataHelper.getUTF8(pass));
         File userFile = new File(_userDir, Base64.encode(userHash.getData()));
-        System.out.println("Attempting to login to " + login + " w/ pass = " + pass 
+        if (_log.shouldLog(Log.INFO))
+            _log.info("Attempting to login to " + login + " w/ pass = " + pass 
                            + ": file = " + userFile.getAbsolutePath() + " passHash = "
                            + Base64.encode(passHash.getData()));
         if (userFile.exists()) {
             try {
-                Properties props = new Properties();
-                FileInputStream fin = new FileInputStream(userFile);
-                BufferedReader in = new BufferedReader(new InputStreamReader(fin, "UTF-8"));
-                String line = null;
-                while ( (line = in.readLine()) != null) {
-                    int split = line.indexOf('=');
-                    if (split <= 0) continue;
-                    String key = line.substring(0, split);
-                    String val = line.substring(split+1);
-                    props.setProperty(key.trim(), val.trim());
-                }
+                Properties props = loadUserProps(userFile);
+                if (props == null) throw new IOException("Error reading " + userFile);
                 return user.login(login, pass, props);
             } catch (IOException ioe) {
-                ioe.printStackTrace();
+                _log.error("Error logging in", ioe);
                 return "<span class=\"b_loginMsgErr\">Error logging in - corrupt userfile</span>";
             }
         } else {
@@ -251,7 +314,6 @@ public class BlogManager {
     public String getRemotePasswordHash() { 
         String pass = _context.getProperty("syndie.remotePassword");
         
-        System.out.println("Remote password? [" + pass + "]");
         if ( (pass == null) || (pass.trim().length() <= 0) ) return null;
         return pass;
     }
@@ -330,7 +392,7 @@ public class BlogManager {
             }
             _archive.setDefaultSelector(defaultSelector);
         } catch (IOException ioe) {
-            ioe.printStackTrace();
+            _log.error("Error writing out the config", ioe);
         } finally {
             if (out != null) try { out.close(); } catch (IOException ioe) {}
             readConfig();
@@ -353,21 +415,30 @@ public class BlogManager {
         }
     }
     
-    public void saveUser(User user) {
-        if (!user.getAuthenticated()) return;
-        String userHash = Base64.encode(_context.sha().calculateHash(DataHelper.getUTF8(user.getUsername())).getData());
+    /**
+     * Store user info, regardless of whether they're logged in.  This lets you update a
+     * different user's info!
+     */
+    void storeUser(User user) {
+        String userHash = user.getUserHash();
         File userFile = new File(_userDir, userHash);
+        if (!userFile.exists()) return;
         FileOutputStream out = null;
         try {
             out = new FileOutputStream(userFile);
             out.write(DataHelper.getUTF8(user.export()));
             user.getPetNameDB().store(user.getAddressbookLocation());
         } catch (IOException ioe) {
-            ioe.printStackTrace();
+            _log.error("Error writing out the user", ioe);
         } finally {
             if (out != null) try { out.close(); } catch (IOException ioe){}
         }
     }
+    
+    public void saveUser(User user) {
+        if (!user.getAuthenticated()) return;
+        storeUser(user);
+    }
     public String register(User user, String login, String password, String registrationPassword, String blogName, String blogDescription, String contactURL) {
         System.err.println("Register [" + login + "] pass [" + password + "] name [" + blogName + "] descr [" + blogDescription + "] contact [" + contactURL + "] regPass [" + registrationPassword + "]");
         String hashedRegistrationPassword = getRegistrationPasswordHash();
@@ -399,7 +470,7 @@ public class BlogManager {
                 bw.write("showexpanded=false\n");
                 bw.flush();
             } catch (IOException ioe) {
-                ioe.printStackTrace();
+                _log.error("Error registering the user", ioe);
                 return "<span class=\"b_regMsgErr\">Internal error registering - " + ioe.getMessage() + "</span>";
             } finally {
                 if (out != null) try { out.close(); } catch (IOException ioe) {}
@@ -425,7 +496,7 @@ public class BlogManager {
                 try {
                     routerDb.store();
                 } catch (IOException ioe) {
-                    ioe.printStackTrace();
+                    _log.error("Error exporting the hosts", ioe);
                     return "<span class=\"b_addrMsgErr\">Error exporting the hosts: " + ioe.getMessage() + "</span>";
                 }
             }
@@ -433,6 +504,21 @@ public class BlogManager {
         return "<span class=\"b_addrMsgOk\">Hosts exported</span>";
     }
     
+    /**
+     * Guess what the next entry ID should be for the given user.  Rounds down to 
+     * midnight of the current day + 1 for each post in that day.
+     */
+    public long getNextBlogEntry(User user) {
+        long entryId = -1;
+        long now = _context.clock().now();
+        long dayBegin = getDayBegin(now);
+        if (user.getMostRecentEntry() >= dayBegin)
+            entryId = user.getMostRecentEntry() + 1;
+        else
+            entryId = dayBegin;
+        return entryId;
+    }
+    
     public BlogURI createBlogEntry(User user, String subject, String tags, String entryHeaders, String sml) {
         return createBlogEntry(user, subject, tags, entryHeaders, sml, null, null, null);
     }
@@ -443,13 +529,7 @@ public class BlogManager {
         SigningPrivateKey privkey = getMyPrivateKey(info);
         if (privkey == null) return null;
         
-        long entryId = -1;
-        long now = _context.clock().now();
-        long dayBegin = getDayBegin(now);
-        if (user.getMostRecentEntry() >= dayBegin)
-            entryId = user.getMostRecentEntry() + 1;
-        else
-            entryId = dayBegin;
+        long entryId = getNextBlogEntry(user);
         
         StringTokenizer tok = new StringTokenizer(tags, " ,\n\t");
         String tagList[] = new String[tok.countTokens()];
@@ -466,12 +546,14 @@ public class BlogManager {
                 raw.append(tagList[i]).append('\t');
             raw.append('\n');
             if ( (entryHeaders != null) && (entryHeaders.trim().length() > 0) ) {
-                System.out.println("Entry headers: " + entryHeaders);
+                if (_log.shouldLog(Log.DEBUG))
+                    _log.debug("Creating entry with headers: " + entryHeaders);
                 BufferedReader userHeaders = new BufferedReader(new InputStreamReader(new ByteArrayInputStream(DataHelper.getUTF8(entryHeaders)), "UTF-8"));
                 String line = null;
                 while ( (line = userHeaders.readLine()) != null) {
                     line = line.trim();
-                    System.out.println("Line: " + line);
+                    if (_log.shouldLog(Log.DEBUG))
+                        _log.debug("header line: " + line);
                     if (line.length() <= 0) continue;
                     int split = line.indexOf('=');
                     int split2 = line.indexOf(':');
@@ -520,7 +602,7 @@ public class BlogManager {
                 return null;
             }
         } catch (IOException ioe) {
-            ioe.printStackTrace();
+            _log.error("Error creating post", ioe);
             return null;
         }
     }
@@ -536,7 +618,7 @@ public class BlogManager {
             info.load(metadataStream);
             return _archive.storeBlogInfo(info);
         } catch (IOException ioe) {
-            ioe.printStackTrace();
+            _log.error("Error importing meta", ioe);
             return false;
         }
     }
@@ -552,7 +634,7 @@ public class BlogManager {
             c.load(entryStream);
             return _archive.storeEntry(c);
         } catch (IOException ioe) {
-            ioe.printStackTrace();
+            _log.error("Error importing entry", ioe);
             return false;
         }
     }
@@ -616,7 +698,7 @@ public class BlogManager {
                 Destination d = new Destination(location);
                 return (d.getPublicKey() != null);
             } catch (DataFormatException dfe) {
-                dfe.printStackTrace();
+                _log.error("Error validating address location", dfe);
                 return false;
             }
         } else {
diff --git a/apps/syndie/java/src/net/i2p/syndie/CLI.java b/apps/syndie/java/src/net/i2p/syndie/CLI.java
index 83f9e9a23a52ca916301b7c35de1cf4a58522e0f..b51a94593c6365f5a6bc6ea25a8a2ac74b7c8f61 100644
--- a/apps/syndie/java/src/net/i2p/syndie/CLI.java
+++ b/apps/syndie/java/src/net/i2p/syndie/CLI.java
@@ -13,7 +13,7 @@ public class CLI {
     public static final String USAGE = "Usage: \n" +
         "rootDir regenerateIndex\n" +
         "rootDir createBlog name description contactURL[ archiveURL]*\n" +
-        "rootDir createEntry blogPublicKeyHash tag[,tag]* (NOW|entryId) (NONE|entryKeyBase64) smlFile[ attachmentFile]*\n" +
+        "rootDir createEntry blogPublicKeyHash tag[,tag]* (NEXT|NOW|entryId) (NONE|entryKeyBase64) smlFile[ attachmentFile attachmentName attachmentDescription mimeType]*\n" +
         "rootDir listMyBlogs\n" +
         "rootDir listTags blogPublicKeyHash\n" +
         "rootDir listEntries blogPublicKeyHash blogTag\n" +
@@ -130,50 +130,117 @@ public class CLI {
     }
 
     private static void createEntry(String args[]) {
-        // "rootDir createEntry blogPublicKey tag[,tag]* (NOW|entryId) (NONE|entryKeyBase64) smlFile[ attachmentFile]*\n" +
-
+        // "rootDir createEntry blogPublicKeyHash tag[,tag]* (NEXT|NOW|entryId) (NONE|entryKeyBase64) "
+        //  smlFile[ attachmentFile attachmentName attachmentDescription mimeType]*\n"
+        String rootDir = args[0];
+        String hashStr = args[2];
+        List tags = new ArrayList();
+        StringTokenizer tok = new StringTokenizer(args[3], ",");
+        while (tok.hasMoreTokens())
+            tags.add(tok.nextToken().trim());
+        String entryIdDef = args[4];
+        String entryKeyDef = args[5];
+        String smlFile = args[6];
+        List attachmentFilenames = new ArrayList();
+        List attachmentNames = new ArrayList();
+        List attachmentDescriptions = new ArrayList();
+        List attachmentMimeTypes = new ArrayList();
+        for (int i = 7; i + 3 < args.length; i += 4) {
+            attachmentFilenames.add(args[i].trim());
+            attachmentNames.add(args[i+1].trim());
+            attachmentDescriptions.add(args[i+2].trim());
+            attachmentMimeTypes.add(args[i+3].trim());
+        }
         I2PAppContext ctx = I2PAppContext.getGlobalContext();
-        BlogManager mgr = new BlogManager(ctx, args[0]);
+        BlogManager mgr = new BlogManager(ctx, rootDir);
+        EntryContainer entry = createEntry(ctx, mgr, hashStr, tags, entryIdDef, entryKeyDef, smlFile, true,
+                                           attachmentFilenames, attachmentNames, attachmentDescriptions, 
+                                           attachmentMimeTypes);
+        if (entry != null)
+            mgr.getArchive().regenerateIndex();
+    }
+    
+    /**
+     * Create a new entry, storing it into the blogManager's archive and incrementing the 
+     * blog's "most recent id" setting.  This does not however regenerate the manager's index.
+     *
+     * @param blogHashStr base64(SHA256(blog public key))
+     * @param tags list of tags/categories to post under (String elements
+     * @param entryIdDef NEXT (for next entry id for the blog, or midnight of the current day),
+     *                   NOW (current time), or an explicit entry id
+     * @param entryKeyDef session key under which the entry should be encrypted
+     * @param smlFilename file in which the sml entry is to be found
+     * @param storeLocal if true, should this entry be stored in the mgr.getArchive()
+     * @param attachmentFilenames list of filenames for attachments to load
+     * @param attachmentNames list of names to use for the given attachments
+     * @param attachmentDescriptions list of descriptions for the given attachments
+     * @param attachmentMimeTypes list of mime types to use for the given attachments
+     * @return blog URI posted, or null
+     */
+    public static EntryContainer createEntry(I2PAppContext ctx, BlogManager mgr, String blogHashStr, List tags, 
+                                             String entryIdDef, String entryKeyDef, String smlFilename, boolean storeLocal,
+                                             List attachmentFilenames, List attachmentNames, 
+                                             List attachmentDescriptions, List attachmentMimeTypes) {
+        Hash blogHash = new Hash(Base64.decode(blogHashStr));
+        User user = mgr.getUser(blogHash);
         long entryId = -1;
-        if ("NOW".equals(args[4])) {
+        if ("NOW".equalsIgnoreCase(entryIdDef)) {
             entryId = ctx.clock().now();
+        } else if ("NEXT".equalsIgnoreCase(entryIdDef) || (entryIdDef == null)) {
+            entryId = mgr.getNextBlogEntry(user);
         } else {
             try {
-                entryId = Long.parseLong(args[4]);
+                entryId = Long.parseLong(entryIdDef);
             } catch (NumberFormatException nfe) {
                 nfe.printStackTrace();
-                return;
+                return null;
             }
         }
-        StringTokenizer tok = new StringTokenizer(args[3], ",");
-        String tags[] = new String[tok.countTokens()];
-        for (int i = 0; i < tags.length; i++)
-            tags[i] = tok.nextToken();
-        BlogURI uri = new BlogURI(new Hash(Base64.decode(args[2])), entryId);
+        String tagVals[] = new String[(tags != null ? tags.size() : 0)];
+        if (tags != null)
+            for (int i = 0; i < tags.size(); i++)
+                tagVals[i] = ((String)tags.get(i)).trim();
+        BlogURI uri = new BlogURI(blogHash, entryId);
         BlogInfo blog = mgr.getArchive().getBlogInfo(uri);
         if (blog == null) {
             System.err.println("Blog does not exist: " + uri);
-            return;
+            return null;
         }
         SigningPrivateKey key = mgr.getMyPrivateKey(blog);
         
         try {
-            byte smlData[] = read(args[6]);
-            EntryContainer c = new EntryContainer(uri, tags, smlData);
-            for (int i = 7; i < args.length; i++) {
-                c.addAttachment(read(args[i]), new File(args[i]).getName(), 
-                                "Attached file", "application/octet-stream");
+            byte smlData[] = read(smlFilename);
+            EntryContainer c = new EntryContainer(uri, tagVals, smlData);
+            if ( (attachmentFilenames != null) && 
+                 (attachmentFilenames.size() == attachmentNames.size()) && 
+                 (attachmentFilenames.size() == attachmentDescriptions.size()) && 
+                 (attachmentFilenames.size() == attachmentMimeTypes.size()) ) {
+                for (int i = 0; i < attachmentFilenames.size(); i++) {
+                    File attachmentFile = new File((String)attachmentFilenames.get(i));
+                    String name = (String)attachmentNames.get(i);
+                    String descr = (String)attachmentDescriptions.get(i);
+                    String mimetype = (String)attachmentMimeTypes.get(i);
+                    c.addAttachment(read(attachmentFile.getAbsolutePath()), name, descr, mimetype);
+                }
             }
             SessionKey entryKey = null;
-            if (!"NONE".equals(args[5]))
-                entryKey = new SessionKey(Base64.decode(args[5]));
+            if ( (entryKeyDef != null) && (entryKeyDef.trim().length() > 0) && (!"NONE".equalsIgnoreCase(entryKeyDef)) )
+                entryKey = new SessionKey(Base64.decode(entryKeyDef));
             c.seal(ctx, key, entryKey);
-            boolean ok = mgr.getArchive().storeEntry(c);
-            System.out.println("Blog entry created: " + c+ "? " + ok);
-            if (ok)
-                mgr.getArchive().regenerateIndex();
+            if (storeLocal) {
+                boolean ok = mgr.getArchive().storeEntry(c);
+                //System.out.println("Blog entry created: " + c+ "? " + ok);
+                if (!ok) {
+                    System.err.println("Error: store failed");
+                    return null;
+                }
+            }
+            user.setMostRecentEntry(uri.getEntryId());
+            mgr.storeUser(user); // saves even if !user.getAuthenticated()
+            return c;
         } catch (IOException ioe) {
             ioe.printStackTrace();
+            return null;
         }
     }
     
diff --git a/apps/syndie/java/src/net/i2p/syndie/CLIPost.java b/apps/syndie/java/src/net/i2p/syndie/CLIPost.java
new file mode 100644
index 0000000000000000000000000000000000000000..a5f21ccec138c56816fda5bfab3e6812179014f5
--- /dev/null
+++ b/apps/syndie/java/src/net/i2p/syndie/CLIPost.java
@@ -0,0 +1,206 @@
+package net.i2p.syndie;
+
+import java.io.*;
+import java.util.*;
+import net.i2p.*;
+import net.i2p.data.*;
+import net.i2p.syndie.data.*;
+import net.i2p.util.EepPost;
+
+/**
+ * Simple CLI to post an entry.
+ * 
+ */
+public class CLIPost {
+    public static final String USAGE = "Usage: \"" + CLIPost.class.getName() + " [args]\", where args are:"
+        + "\n  --syndieDir $syndieRootDir  // syndie root dir, under which syndie.config exists"
+        + "\n  --blog $blogHash            // base64 of the blog's key"
+        + "\n  --sml $smlFile              // file with the SML entry"
+        + "\n  [--importurl ($url|none)]   // defaults to http://localhost:7657/syndie/import.jsp"
+        + "\n  [--proxyhost $hostname]     // HTTP proxy host for sending the data to the import URL"
+        + "\n  [--proxyport $portnum]      // HTTP proxy port for sending the data to the import URL"
+        + "\n  [--storelocal (true|false)] // should it be stored directly with the file system"
+        + "\n                              // (false by default, since its stored locally via importurl)"
+        + "\n  [--entryId ($num|next|now)] // entryId to use: explicit, the blog's next (default), or timestamp"
+        + "\n  [--attachment$N $file $name $desc $type]"
+        + "\n                              // Nth file / suggested name / description / mime type";
+    
+    public static void main(String args[]) {
+        String rootDir = getArg(args, "syndieDir");
+        String hashStr = getArg(args, "blog");
+        String smlFile = getArg(args, "sml");
+        if ( (rootDir == null) || (hashStr == null) || (smlFile == null) ) {
+            System.err.println(USAGE);
+            return;
+        }
+        
+        String url = getArg(args, "importurl");
+        String entryIdDef = getArg(args, "entryId");
+        
+        List attachmentFilenames = new ArrayList();
+        List attachmentNames = new ArrayList();
+        List attachmentDescriptions = new ArrayList();
+        List attachmentMimeTypes = new ArrayList();
+        while (true) {
+            // --attachment$N $file $name $desc $type]
+            String file = getAttachmentParam(args, attachmentFilenames.size(), 0);
+            String name = getAttachmentParam(args, attachmentFilenames.size(), 1);
+            String desc = getAttachmentParam(args, attachmentFilenames.size(), 2);
+            String type = getAttachmentParam(args, attachmentFilenames.size(), 3);
+            if ( (file != null) && (name != null) && (desc != null) && (type != null) ) {
+                attachmentFilenames.add(file);
+                attachmentNames.add(name);
+                attachmentDescriptions.add(desc);
+                attachmentMimeTypes.add(type);
+            } else {
+                break;
+            }
+        }
+        
+        List tags = readTags(smlFile);
+        
+        // don't support the entry key stuff yet...
+        String entryKeyDef = null; //args[5];
+        
+        String loc = getArg(args, "storelocal");
+        boolean storeLocal = false;
+        if (loc != null)
+            storeLocal = Boolean.valueOf(loc).booleanValue();
+        
+        if (!storeLocal && "none".equalsIgnoreCase(url)) {
+            System.err.println("You need to post it somewhere, so either specify \"--storelocal true\"");
+            System.err.println("or don't specify \"--importurl none\"");
+            return;
+        }
+        
+        I2PAppContext ctx = I2PAppContext.getGlobalContext();
+        BlogManager mgr = new BlogManager(ctx, rootDir, false);
+        EntryContainer entry = CLI.createEntry(ctx, mgr, hashStr, tags, entryIdDef, entryKeyDef, smlFile, storeLocal, 
+                                               attachmentFilenames, attachmentNames, attachmentDescriptions, 
+                                               attachmentMimeTypes);
+        if (entry != null) {
+            if (storeLocal)
+                mgr.getArchive().regenerateIndex();
+            if (!("none".equalsIgnoreCase(url))) {
+                if ( (url == null) || (url.trim().length() <= 0) )
+                    url = "http://localhost:7657/syndie/import.jsp";
+
+                // now send it to the import URL
+                BlogInfo info = mgr.getArchive().getBlogInfo(entry.getURI().getKeyHash());
+                File fMeta = null;
+                File fData = null;
+
+                try {
+                    fMeta = File.createTempFile("cli", ".snm", mgr.getTempDir());
+                    fData = File.createTempFile("cli", ".snd", mgr.getTempDir());
+                    FileOutputStream out = new FileOutputStream(fMeta);
+                    info.write(out);
+                    out.close();
+                    out = new FileOutputStream(fData);
+                    entry.write(out, true);
+                    out.close();
+                } catch (IOException ioe) {
+                    System.err.println("Error writing temp files: " + ioe.getMessage());
+                    return;
+                }
+
+                Map uploads = new HashMap(2);
+                uploads.put("blogmeta0", fMeta);
+                uploads.put("blogpost0", fData);
+
+                String proxyHost = getArg(args, "proxyhost");
+                String proxyPortStr = getArg(args, "proxyport");
+                int proxyPort = -1;
+                if (proxyPortStr != null) 
+                    try { proxyPort = Integer.parseInt(proxyPortStr); } catch (NumberFormatException nfe) { }
+
+                OnCompletion job = new OnCompletion();
+                EepPost post = new EepPost();
+                post.postFiles(url, (proxyPort > 0 ? proxyHost : null), proxyPort, uploads, job);
+                boolean posted = job.waitForCompletion(30*1000);
+                if (posted)
+                    System.out.println("Posted successfully: " + entry.getURI().toString());
+                else
+                    System.out.println("Posting failed");
+            } else if (storeLocal) {
+                System.out.println("Store local successfully: " + entry.getURI().toString());
+            } else {
+                // foo
+            }
+        } else {
+            System.err.println("Error creating the blog entry");
+        }
+    }
+    
+    private static class OnCompletion implements Runnable {
+        private boolean _complete;
+        public OnCompletion() { _complete = false; }
+        public void run() { 
+            _complete = true; 
+            synchronized (OnCompletion.this) { 
+                OnCompletion.this.notifyAll();
+            }
+        }
+        public boolean waitForCompletion(long max) {
+            long end = max + System.currentTimeMillis();
+            while (!_complete) {
+                long now = System.currentTimeMillis();
+                if (now >= end)
+                    return false;
+                try {
+                    synchronized (OnCompletion.this) {
+                        OnCompletion.this.wait(end-now);
+                    }
+                } catch (InterruptedException ie) {}
+            }
+            return true;
+        }
+    }
+
+    private static String getArg(String args[], String param) {
+        if (args != null) 
+            for (int i = 0; i + 1< args.length; i++)
+                if (args[i].equalsIgnoreCase("--"+param))
+                    return args[i+1];
+        return null;
+    }
+    private static String getAttachmentParam(String args[], int attachmentNum, int paramNum) {
+        if (args != null) 
+            for (int i = 0; i + 4 < args.length; i++)
+                if (args[i].equalsIgnoreCase("--attachment"+attachmentNum))
+                    return args[i+1+paramNum];
+        return null;
+    }
+    
+    private static List readTags(String smlFile) {
+        BufferedReader in = null;
+        try {
+            in = new BufferedReader(new InputStreamReader(new FileInputStream(smlFile), "UTF-8"));
+            String line = null;
+            while ( (line = in.readLine()) != null) {
+                if (line.length() <= 0)
+                    return new ArrayList();
+                else if (line.startsWith("Tags:"))
+                    return parseTags(line.substring("Tags:".length()));
+            }
+            return null;
+        } catch (IOException ioe) {
+            return new ArrayList();
+        } finally {
+            if (in != null) try { in.close(); } catch (IOException ioe) {}
+        }
+    }
+    
+    private static List parseTags(String tags) {
+        if (tags == null) 
+            return new ArrayList();
+        StringTokenizer tok = new StringTokenizer(tags, " ,\t\n");
+        List rv = new ArrayList();
+        while (tok.hasMoreTokens()) {
+            String cur = tok.nextToken().trim();
+            if (cur.length() > 0)
+                rv.add(cur);
+        }
+        return rv;
+    }
+}
diff --git a/apps/syndie/java/src/net/i2p/syndie/User.java b/apps/syndie/java/src/net/i2p/syndie/User.java
index d11856c62cdaccfbe13d132f655c960c7c5d1e49..3d25413e89151737ece0019770e24303f061d911 100644
--- a/apps/syndie/java/src/net/i2p/syndie/User.java
+++ b/apps/syndie/java/src/net/i2p/syndie/User.java
@@ -16,6 +16,7 @@ public class User {
     private String _username;
     private String _hashedPassword;
     private Hash _blog;
+    private String _userHash;
     private long _mostRecentEntry;
     /** Group name to List of blog selectors, where the selectors are of the form
      * blog://$key, entry://$key/$entryId, blogtag://$key/$tag, tag://$tag
@@ -39,6 +40,8 @@ public class User {
     private String _torProxyHost;
     private int _torProxyPort;
     private PetNameDB _petnames;
+
+    static final String PROP_USERHASH = "__userHash";
     
     public User() {
         _context = I2PAppContext.getGlobalContext();
@@ -47,6 +50,7 @@ public class User {
     private void init() {
         _authenticated = false;
         _username = null;
+        _userHash = null;
         _hashedPassword = null;
         _blog = null;
         _mostRecentEntry = -1;
@@ -70,6 +74,7 @@ public class User {
     
     public boolean getAuthenticated() { return _authenticated; }
     public String getUsername() { return _username; }
+    public String getUserHash() { return _userHash; }
     public Hash getBlog() { return _blog; }
     public String getBlogStr() { return Base64.encode(_blog.getData()); }
     public long getMostRecentEntry() { return _mostRecentEntry; }
@@ -105,15 +110,22 @@ public class User {
     }
     
     public String login(String login, String pass, Properties props) {
-        String expectedPass = props.getProperty("password");
+        _username = login;
+        load(props);
         String hpass = Base64.encode(_context.sha().calculateHash(DataHelper.getUTF8(pass)).getData());
-        if (!hpass.equals(expectedPass)) {
-            _authenticated = false;
+        if (!hpass.equals(_hashedPassword)) {
             return "<span class=\"b_loginMsgErr\">Incorrect password</span>";
         }
-        
-        _username = login;
-        _hashedPassword = expectedPass;
+        _lastLogin = _context.clock().now();
+        _authenticated = true;
+        return LOGIN_OK;
+    }
+    
+    
+    public void load(Properties props) {
+        _authenticated = false;
+        _hashedPassword = props.getProperty("password");
+        _userHash = props.getProperty(PROP_USERHASH);
         
         // blog=luS9d3uaf....HwAE=
         String b = props.getProperty("blog");
@@ -185,9 +197,6 @@ public class User {
         _eepProxyHost = props.getProperty("eepproxyhost");
         _webProxyHost = props.getProperty("webproxyhost");
         _torProxyHost = props.getProperty("torproxyhost");
-        _lastLogin = _context.clock().now();
-        _authenticated = true;
-        return LOGIN_OK;
     }
     
     private int getInt(String val) {
diff --git a/history.txt b/history.txt
index 32f3995ae35f40ade89ca0be458f1b0fc36b2cdb..c0147bff6abda59a4f2f346825f4e8f330dd6355 100644
--- a/history.txt
+++ b/history.txt
@@ -1,4 +1,10 @@
-$Id: history.txt,v 1.286 2005/10/08 17:05:49 jrandom Exp $
+$Id: history.txt,v 1.287 2005/10/09 00:46:57 jrandom Exp $
+
+2005-10-09  jrandom
+    * Syndie CLI cleanup for simpler CLI posting.  Usage shown with
+      java -jar lib/syndie.jar
+    * Beginnings of the Syndie logging cleanup
+    * Delete corrupt Syndie posts
 
 2005-10-09  jrandom
     * Now that the streaming lib works reasonably, set the default inactivity 
diff --git a/router/java/src/net/i2p/router/RouterVersion.java b/router/java/src/net/i2p/router/RouterVersion.java
index a5408f020cc9cc7ed7485d556d44ec5cbc3e7c1b..4dc63df947567b3323ee0ff249039d2ebb400f36 100644
--- a/router/java/src/net/i2p/router/RouterVersion.java
+++ b/router/java/src/net/i2p/router/RouterVersion.java
@@ -15,9 +15,9 @@ import net.i2p.CoreVersion;
  *
  */
 public class RouterVersion {
-    public final static String ID = "$Revision: 1.261 $ $Date: 2005/10/08 17:05:48 $";
+    public final static String ID = "$Revision: 1.262 $ $Date: 2005/10/09 00:46:57 $";
     public final static String VERSION = "0.6.1.2";
-    public final static long BUILD = 2;
+    public final static long BUILD = 3;
     public static void main(String args[]) {
         System.out.println("I2P Router version: " + VERSION + "-" + BUILD);
         System.out.println("Router ID: " + RouterVersion.ID);