Changeset 01b1534


Ignore:
Timestamp:
Dec 16, 2013 4:12:32 PM (6 years ago)
Author:
zzz <zzz@…>
Branches:
master
Children:
30ccf1b
Parents:
8cb503d
Message:

i2psnark:

  • Move config file and DHT persistence file to a config dir
  • Move per-torrent configuration from "zmeta" in the main config file to a per-torrent config file (ticket #1132)
  • Split timestamp and bitfield into separate configs
  • Fix misspelling of autoStart config
  • Remove two unused SnarkManager? methods
Location:
apps/i2psnark/java/src/org/klomp/snark
Files:
2 edited

Legend:

Unmodified
Added
Removed
  • apps/i2psnark/java/src/org/klomp/snark/SnarkManager.java

    r8cb503d r01b1534  
    1515import java.util.HashMap;
    1616import java.util.HashSet;
     17import java.util.Iterator;
    1718import java.util.List;
    1819import java.util.Map;
     
    3940
    4041import org.klomp.snark.dht.DHT;
     42import org.klomp.snark.dht.KRPC;
    4143
    4244/**
     
    5456    private final Set<String> _magnets;
    5557    private final Object _addSnarkLock;
    56     private /* FIXME final FIXME */ File _configFile;
     58    private File _configFile;
     59    private File _configDir;
     60    /** one lock for all config, files for simplicity */
     61    private final Object _configLock = new Object();
    5762    private Properties _config;
    5863    private final I2PAppContext _context;
     
    8085    public static final String PROP_UPBW_MAX = "i2psnark.upbw.max";
    8186    public static final String PROP_DIR = "i2psnark.dir";
    82     public static final String PROP_META_PREFIX = "i2psnark.zmeta.";
    83     public static final String PROP_META_BITFIELD_SUFFIX = ".bitfield";
    84     public static final String PROP_META_PRIORITY_SUFFIX = ".priority";
    85     public static final String PROP_META_MAGNET_PREFIX = "i2psnark.magnet.";
     87    private static final String PROP_META_PREFIX = "i2psnark.zmeta.";
     88    private static final String PROP_META_STAMP = "stamp";
     89    private static final String PROP_META_BITFIELD = "bitfield";
     90    private static final String PROP_META_PRIORITY = "priority";
     91    private static final String PROP_META_BITFIELD_SUFFIX = ".bitfield";
     92    private static final String PROP_META_PRIORITY_SUFFIX = ".priority";
     93    private static final String PROP_META_MAGNET_PREFIX = "i2psnark.magnet.";
    8694
    8795    private static final String CONFIG_FILE_SUFFIX = ".config";
     96    private static final String CONFIG_FILE = "i2psnark" + CONFIG_FILE_SUFFIX;
    8897    public static final String PROP_FILES_PUBLIC = "i2psnark.filesPublic";
    89     public static final String PROP_AUTO_START = "i2snark.autoStart";   // oops
     98    public static final String PROP_OLD_AUTO_START = "i2snark.autoStart";   // oops
     99    public static final String PROP_AUTO_START = "i2psnark.autoStart";      // convert in migration to new config file
    90100    public static final String DEFAULT_AUTO_START = "false";
    91101    //public static final String PROP_LINK_PREFIX = "i2psnark.linkPrefix";
     
    108118    public static final int DEFAULT_REFRESH_DELAY_SECS = 60;
    109119    private static final int DEFAULT_PAGE_SIZE = 50;
     120    public static final String CONFIG_DIR_SUFFIX = ".d";
     121    private static final String SUBDIR_PREFIX = "s";
     122    private static final String B64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-~";
    110123
    111124    /**
     
    168181        _util = new I2PSnarkUtil(_context, ctxName);
    169182        String cfile = ctxName + CONFIG_FILE_SUFFIX;
    170         _configFile = new File(cfile);
    171         if (!_configFile.isAbsolute())
    172             _configFile = new File(_context.getConfigDir(), cfile);
     183        File configFile = new File(cfile);
     184        if (!configFile.isAbsolute())
     185            configFile = new File(_context.getConfigDir(), cfile);
     186        _configDir = migrateConfig(configFile);
     187        _configFile = new File(_configDir, CONFIG_FILE);
    173188        _trackerMap = new ConcurrentHashMap<String, Tracker>(4);
    174189        loadConfig(null);
     
    325340    }
    326341
     342    /**
     343     *  Migrate the old flat config file to the new config dir
     344     *  containing the config file minus the per-torrent entries,
     345     *  the dht file, and 16 subdirs for per-torrent config files
     346     *  Caller must synch.
     347     *
     348     *  @return the new config directory, non-null
     349     *  @throws RuntimeException on creation fail
     350     *  @since 0.9.10
     351     */
     352    private File migrateConfig(File oldFile) {
     353        File dir = new SecureDirectory(oldFile + CONFIG_DIR_SUFFIX);
     354        if ((!dir.exists()) && (!dir.mkdirs())) {
     355            _log.error("Error creating I2PSnark config dir " + dir);
     356            throw new RuntimeException("Error creating I2PSnark config dir " + dir);
     357        }
     358        // move the DHT file as-is
     359        String oldName = oldFile.toString();
     360        if (oldName.endsWith(CONFIG_FILE_SUFFIX)) {
     361            String oldDHT = oldName.replace(CONFIG_FILE_SUFFIX, KRPC.DHT_FILE_SUFFIX);
     362            File oldDHTFile = new File(oldDHT);
     363            if (oldDHTFile.exists()) {
     364                File newDHTFile = new File(dir, "i2psnark" + KRPC.DHT_FILE_SUFFIX);
     365                FileUtil.rename(oldDHTFile, newDHTFile);
     366            }
     367        }
     368        if (!oldFile.exists())
     369            return dir;
     370        Properties oldProps = new Properties();
     371        try {
     372            DataHelper.loadProps(oldProps, oldFile);
     373            // a good time to fix this ancient typo
     374            String auto = (String) oldProps.remove(PROP_OLD_AUTO_START);
     375            if (auto != null)
     376                oldProps.setProperty(PROP_AUTO_START, auto);
     377        } catch (IOException ioe) {
     378           _log.error("Error loading I2PSnark config " + oldFile, ioe);
     379           return dir;
     380        }
     381        // Gather the props for each torrent, removing them from config
     382        // old b64 of hash as key
     383        Map<String, Properties> configs = new HashMap<String, Properties>(16);
     384        for (Iterator<Map.Entry<Object, Object>> iter = oldProps.entrySet().iterator(); iter.hasNext(); ) {
     385            Map.Entry<Object, Object> e = iter.next();
     386            String k = (String) e.getKey();
     387            if (k.startsWith(PROP_META_PREFIX)) {
     388                iter.remove();
     389                String v = (String) e.getValue();
     390                try {
     391                    k = k.substring(PROP_META_PREFIX.length());
     392                    String h = k.substring(0, 28);  // length of b64 of 160 bit infohash
     393                    k = k.substring(29); // skip '.'
     394                    Properties tprops = configs.get(h);
     395                    if (tprops == null) {
     396                        tprops = new OrderedProperties();
     397                        configs.put(h, tprops);
     398                    }
     399                    if (k.equals(PROP_META_BITFIELD)) {
     400                        // old config was timestamp,bitfield; split them
     401                        int comma = v.indexOf(',');
     402                        if (comma > 0 && v.length() > comma + 1) {
     403                            tprops.put(PROP_META_STAMP, v.substring(0, comma));
     404                            tprops.put(PROP_META_BITFIELD, v.substring(comma + 1));
     405                        } else {
     406                            // timestamp only??
     407                            tprops.put(PROP_META_STAMP, v);
     408                        }
     409                    } else {
     410                        tprops.put(k, v);
     411                    }
     412                } catch (IndexOutOfBoundsException ioobe) {
     413                    continue;
     414                }
     415            }
     416        }
     417        // Now make a config file for each torrent
     418        for (Map.Entry<String, Properties> e : configs.entrySet()) {
     419            String b64 = e.getKey();
     420            Properties props = e.getValue();
     421            if (props.isEmpty())
     422                continue;
     423            b64 = b64.replace('$', '=');
     424            byte[] ih = Base64.decode(b64);
     425            if (ih == null || ih.length != 20)
     426                continue;
     427            File cfg = configFile(dir, ih);
     428            if (!cfg.exists()) {
     429                File subdir = cfg.getParentFile();
     430                if (!subdir.exists())
     431                    subdir.mkdirs();
     432                try {
     433                    DataHelper.storeProps(props, cfg);
     434                } catch (IOException ioe) {
     435                    _log.error("Error storing I2PSnark config " + cfg, ioe);
     436                }
     437            }
     438        }
     439        // now store in new location, minus the zmeta entries
     440        File newFile = new File(dir, CONFIG_FILE);
     441        Properties newProps = new OrderedProperties();
     442        newProps.putAll(oldProps);
     443        try {
     444            DataHelper.storeProps(newProps, newFile);
     445        } catch (IOException ioe) {
     446            _log.error("Error storing I2PSnark config " + newFile, ioe);
     447            return dir;
     448        }
     449        oldFile.delete();
     450        if (_log.shouldLog(Log.WARN))
     451            _log.warn("Config migrated from " + oldFile + " to " + dir);
     452        return dir;
     453    }
     454
     455    /**
     456     *  The config for a torrent
     457     *  @return non-null, possibly empty
     458     *  @since 0.9.10
     459     */
     460    private Properties getConfig(Snark snark) {
     461        return getConfig(snark.getInfoHash());
     462    }
     463
     464    /**
     465     *  The config for a torrent
     466     *  @param ih 20-byte infohash
     467     *  @return non-null, possibly empty
     468     *  @since 0.9.10
     469     */
     470    private Properties getConfig(byte[] ih) {
     471        Properties rv = new OrderedProperties();
     472        File conf = configFile(_configDir, ih);
     473        synchronized(_configLock) {  // one lock for all
     474            try {
     475                DataHelper.loadProps(rv, conf);
     476            } catch (IOException ioe) {}
     477        }
     478        return rv;
     479    }
     480
     481    /**
     482     *  The config file for a torrent
     483     *  @param confDir the config directory
     484     *  @param ih 20-byte infohash
     485     *  @since 0.9.10
     486     */
     487    private static File configFile(File confDir, byte[] ih) {
     488        String hex = I2PSnarkUtil.toHex(ih);
     489        File subdir = new SecureDirectory(confDir, SUBDIR_PREFIX + B64.charAt((ih[0] >> 2) & 0x3f));
     490        return new File(subdir, hex + CONFIG_FILE_SUFFIX);
     491    }
     492
    327493    /** null to set initial defaults */
    328494    public void loadConfig(String filename) {
     495        synchronized(_configLock) {
     496            locked_loadConfig(filename);
     497        }
     498    }
     499
     500    /** null to set initial defaults */
     501    private void locked_loadConfig(String filename) {
    329502        if (_config == null)
    330503            _config = new OrderedProperties();
     
    333506            if (!cfg.isAbsolute())
    334507                cfg = new File(_context.getConfigDir(), filename);
    335             _configFile = cfg;
    336             if (cfg.exists()) {
     508            _configDir = migrateConfig(cfg);
     509            _configFile = new File(_configDir, CONFIG_FILE);
     510            if (_configFile.exists()) {
    337511                try {
    338                     DataHelper.loadProps(_config, cfg);
     512                    DataHelper.loadProps(_config, _configFile);
    339513                } catch (IOException ioe) {
    340                    _log.error("Error loading I2PSnark config '" + filename + "'", ioe);
     514                   _log.error("Error loading I2PSnark config " + _configFile, ioe);
    341515                }
    342516            }
     
    372546        updateConfig();
    373547    }
     548
    374549    /**
    375550     * Get current theme.
     
    486661     */
    487662    public void updateConfig(String dataDir, boolean filesPublic, boolean autoStart, String refreshDelay,
     663                             String startDelay, String pageSize, String seedPct, String eepHost,
     664                             String eepPort, String i2cpHost, String i2cpPort, String i2cpOpts,
     665                             String upLimit, String upBW, boolean useOpenTrackers, boolean useDHT, String theme) {
     666        synchronized(_configLock) {
     667            locked_updateConfig(dataDir, filesPublic, autoStart, refreshDelay,
     668                                startDelay,  pageSize,  seedPct,  eepHost,
     669                                eepPort,  i2cpHost,  i2cpPort,  i2cpOpts,
     670                                upLimit,  upBW, useOpenTrackers, useDHT,  theme);
     671        }
     672    }
     673
     674    private void locked_updateConfig(String dataDir, boolean filesPublic, boolean autoStart, String refreshDelay,
    488675                             String startDelay, String pageSize, String seedPct, String eepHost,
    489676                             String eepPort, String i2cpHost, String i2cpPort, String i2cpOpts,
     
    8201007    public void saveConfig() {
    8211008        try {
    822             synchronized (_configFile) {
     1009            synchronized (_configLock) {
    8231010                DataHelper.storeProps(_config, _configFile);
    8241011            }
     
    8281015    }
    8291016   
    830     public Properties getConfig() { return _config; }
    831    
    832     /** @since Jetty 7 */
    833     public String getConfigFilename() {
    834         return _configFile.getAbsolutePath();
    835     }
    836 
    8371017    /** hardcoded for sanity.  perhaps this should be customizable, for people who increase their ulimit, etc. */
    8381018    public static final int MAX_FILES_PER_TORRENT = 512;
     
    12191399     */
    12201400    public long getSavedTorrentTime(Snark snark) {
    1221         byte[] ih = snark.getInfoHash();
    1222         String infohash = Base64.encode(ih);
    1223         infohash = infohash.replace('=', '$');
    1224         String time = _config.getProperty(PROP_META_PREFIX + infohash + PROP_META_BITFIELD_SUFFIX);
     1401        Properties config = getConfig(snark);
     1402        String time = config.getProperty(PROP_META_STAMP);
    12251403        if (time == null)
    12261404            return 0;
    1227         int comma = time.indexOf(',');
    1228         if (comma <= 0)
    1229             return 0;
    1230         time = time.substring(0, comma);
    12311405        try { return Long.parseLong(time); } catch (NumberFormatException nfe) {}
    12321406        return 0;
     
    12421416        if (metainfo == null)
    12431417            return null;
    1244         byte[] ih = snark.getInfoHash();
    1245         String infohash = Base64.encode(ih);
    1246         infohash = infohash.replace('=', '$');
    1247         String bf = _config.getProperty(PROP_META_PREFIX + infohash + PROP_META_BITFIELD_SUFFIX);
     1418        Properties config = getConfig(snark);
     1419        String bf = config.getProperty(PROP_META_BITFIELD);
    12481420        if (bf == null)
    12491421            return null;
    1250         int comma = bf.indexOf(',');
    1251         if (comma <= 0)
    1252             return null;
    1253         bf = bf.substring(comma + 1).trim();
    12541422        int len = metainfo.getPieces();
    12551423        if (bf.equals(".")) {
     
    12781446        if (metainfo.getFiles() == null)
    12791447            return;
    1280         byte[] ih = snark.getInfoHash();
    1281         String infohash = Base64.encode(ih);
    1282         infohash = infohash.replace('=', '$');
    1283         String pri = _config.getProperty(PROP_META_PREFIX + infohash + PROP_META_PRIORITY_SUFFIX);
     1448        Properties config = getConfig(snark);
     1449        String pri = config.getProperty(PROP_META_PRIORITY);
    12841450        if (pri == null)
    12851451            return;
     
    12991465    /**
    13001466     * Save the completion status of a torrent and the current time in the config file
    1301      * in the form "i2psnark.zmeta.$base64infohash=$time,$base64bitfield".
    1302      * The config file property key is appended with the Base64 of the infohash,
    1303      * with the '=' changed to '$' since a key can't contain '='.
     1467     * for that torrent.
    13041468     * The time is a standard long converted to string.
    13051469     * The status is either a bitfield converted to Base64 or "." for a completed
     
    13101474     */
    13111475    public void saveTorrentStatus(MetaInfo metainfo, BitField bitfield, int[] priorities) {
     1476        synchronized (_configLock) {
     1477            locked_saveTorrentStatus(metainfo, bitfield, priorities);
     1478        }
     1479    }
     1480
     1481    private void locked_saveTorrentStatus(MetaInfo metainfo, BitField bitfield, int[] priorities) {
    13121482        byte[] ih = metainfo.getInfoHash();
    1313         String infohash = Base64.encode(ih);
    1314         infohash = infohash.replace('=', '$');
    1315         String now = "" + System.currentTimeMillis();
    13161483        String bfs;
    13171484        if (bitfield.complete()) {
     
    13211488          bfs = Base64.encode(bf);
    13221489        }
    1323         _config.setProperty(PROP_META_PREFIX + infohash + PROP_META_BITFIELD_SUFFIX, now + "," + bfs);
     1490        Properties config = getConfig(ih);
     1491        config.setProperty(PROP_META_STAMP, Long.toString(System.currentTimeMillis()));
     1492        config.setProperty(PROP_META_BITFIELD, bfs);
    13241493
    13251494        // now the file priorities
    1326         String prop = PROP_META_PREFIX + infohash + PROP_META_PRIORITY_SUFFIX;
    13271495        if (priorities != null) {
    13281496            boolean nonzero = false;
     
    13421510                        buf.append(',');
    13431511                }
    1344                 _config.setProperty(prop, buf.toString());
     1512                config.setProperty(PROP_META_PRIORITY, buf.toString());
    13451513            } else {
    1346                 _config.remove(prop);
     1514                config.remove(PROP_META_PRIORITY);
    13471515            }
    13481516        } else {
    1349             _config.remove(prop);
     1517            config.remove(PROP_META_PRIORITY);
    13501518        }
    13511519
    13521520        // TODO save closest DHT nodes too
    13531521
    1354         saveConfig();
    1355     }
    1356    
    1357     /**
    1358      * Remove the status of a torrent from the config file.
    1359      * This may help the config file from growing too big.
     1522        File conf = configFile(_configDir, ih);
     1523        File subdir = conf.getParentFile();
     1524        if (!subdir.exists())
     1525            subdir.mkdirs();
     1526        try {
     1527            DataHelper.storeProps(config, conf);
     1528        } catch (IOException ioe) {
     1529            _log.error("Unable to save the config to " + conf);
     1530        }
     1531    }
     1532   
     1533    /**
     1534     * Remove the status of a torrent by removing the config file.
    13601535     */
    13611536    public void removeTorrentStatus(MetaInfo metainfo) {
    13621537        byte[] ih = metainfo.getInfoHash();
    1363         String infohash = Base64.encode(ih);
    1364         infohash = infohash.replace('=', '$');
    1365         _config.remove(PROP_META_PREFIX + infohash + PROP_META_BITFIELD_SUFFIX);
    1366         _config.remove(PROP_META_PREFIX + infohash + PROP_META_PRIORITY_SUFFIX);
    1367         saveConfig();
     1538        File conf = configFile(_configDir, ih);
     1539        synchronized (_configLock) {
     1540            conf.delete();
     1541            File subdir = conf.getParentFile();
     1542            String[] files = subdir.list();
     1543            if (files != null && files.length == 0)
     1544                subdir.delete();
     1545        }
    13681546    }
    13691547   
  • apps/i2psnark/java/src/org/klomp/snark/dht/KRPC.java

    r8cb503d r01b1534  
    4040import net.i2p.util.SimpleTimer2;
    4141
     42import org.klomp.snark.SnarkManager;
    4243import org.klomp.snark.TrackerClient;
    4344import org.klomp.snark.bencode.BDecoder;
     
    152153    private static final long EXPLORE_TIME = 877*1000;
    153154    private static final long BLACKLIST_CLEAN_TIME = 17*60*1000;
    154     private static final String DHT_FILE_SUFFIX = ".dht.dat";
     155    public static final String DHT_FILE_SUFFIX = ".dht.dat";
    155156
    156157    private static final int SEND_CRYPTO_TAGS = 8;
     
    185186        }
    186187        _myNodeInfo = new NodeInfo(_myNID, session.getMyDestination(), _qPort);
    187         _dhtFile = new File(ctx.getConfigDir(), baseName + DHT_FILE_SUFFIX);
    188         _backupDhtFile = baseName.equals("i2psnark") ? null : new File(ctx.getConfigDir(), "i2psnark" + DHT_FILE_SUFFIX);
     188        File conf = new File(ctx.getConfigDir(), baseName + ".config" + SnarkManager.CONFIG_DIR_SUFFIX);
     189        _dhtFile = new File(conf, "i2psnark" + DHT_FILE_SUFFIX);
     190        if (baseName.equals("i2psnark")) {
     191            _backupDhtFile = null;
     192        } else {
     193            File bconf = new File(ctx.getConfigDir(), "i2psnark.config" + SnarkManager.CONFIG_DIR_SUFFIX);
     194            _backupDhtFile = new File(bconf, "i2psnark" + DHT_FILE_SUFFIX);
     195        }
    189196        _knownNodes = new DHTNodes(ctx, _myNID);
    190197
Note: See TracChangeset for help on using the changeset viewer.