Changeset c9ce1751


Ignore:
Timestamp:
Aug 10, 2014 2:14:00 PM (6 years ago)
Author:
zzz <zzz@…>
Branches:
master
Children:
3b18cb7
Parents:
4ba40b3 (diff), 8f2dc67 (diff)
Note: this is a merge changeset, the changes displayed below correspond to the merge itself.
Use the (diff) links above to see all the changes relative to each parent.
Message:

propagate from branch 'i2p.i2p.zzz.snarkconfig' (head ad48ab1a9e769c58ea2e286337927f5c0e1568be)

to branch 'i2p.i2p' (head 0cd9e265bd38c40839e68de8f51233489acad346)

Location:
apps/i2psnark
Files:
9 edited

Legend:

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

    r4ba40b3 rc9ce1751  
    6767                        _log.warn("Closing tunnels on idle");
    6868                    _util.disconnect();
    69                     _mgr.addMessage(_util.getString("I2P tunnel closed."));
     69                    _mgr.addMessage(_util.getString("No more torrents running.") + ' ' +
     70                                    _util.getString("I2P tunnel closed."));
    7071                    schedule(3 * CHECK_TIME);
    7172                    return;
  • apps/i2psnark/java/src/org/klomp/snark/PeerState.java

    r4ba40b3 rc9ce1751  
    164164        if (bitfield != null)
    165165          {
    166             // XXX - Be liberal in what you except?
     166            // XXX - Be liberal in what you accept?
    167167            if (_log.shouldLog(Log.WARN))
    168168              _log.warn("Got unexpected bitfield message from " + peer);
  • apps/i2psnark/java/src/org/klomp/snark/Snark.java

    r4ba40b3 rc9ce1751  
    3535import net.i2p.data.Destination;
    3636import net.i2p.util.Log;
     37import net.i2p.util.SecureFile;
    3738
    3839/**
     
    222223  private ConnectionAcceptor acceptor;
    223224  private TrackerClient trackerclient;
    224   private String rootDataDir = ".";
     225  private final File rootDataDir;
    225226  private final CompleteListener completeListener;
    226227  private volatile boolean stopped;
     
    239240
    240241
    241   /** from main() via parseArguments() single torrent */
     242  /**
     243   * from main() via parseArguments() single torrent
     244   *
     245   * @deprecated unused
     246   */
    242247  Snark(I2PSnarkUtil util, String torrent, String ip, int user_port,
    243248        StorageListener slistener, CoordinatorListener clistener) {
     
    245250  }
    246251
    247   /** single torrent - via router */
     252  /**
     253   * single torrent - via router
     254   *
     255   * @deprecated unused
     256   */
    248257  public Snark(I2PAppContext ctx, Properties opts, String torrent,
    249258               StorageListener slistener, boolean start, String rootDir) {
     
    276285  }
    277286
    278   /** multitorrent */
     287  /**
     288   * multitorrent
     289   */
    279290  public Snark(I2PSnarkUtil util, String torrent, String ip, int user_port,
    280291        StorageListener slistener, CoordinatorListener clistener,
    281292        CompleteListener complistener, PeerCoordinatorSet peerCoordinatorSet,
    282293        ConnectionAcceptor connectionAcceptor, boolean start, String rootDir)
     294  {
     295      this(util, torrent, ip, user_port, slistener, clistener, complistener,
     296           peerCoordinatorSet, connectionAcceptor, start, rootDir, null);
     297  }
     298
     299  /**
     300   * multitorrent
     301   *
     302   * @param baseFile if null, use rootDir/torrentName; if non-null, use it instead
     303   * @since 0.9.11
     304   */
     305  public Snark(I2PSnarkUtil util, String torrent, String ip, int user_port,
     306        StorageListener slistener, CoordinatorListener clistener,
     307        CompleteListener complistener, PeerCoordinatorSet peerCoordinatorSet,
     308        ConnectionAcceptor connectionAcceptor, boolean start, String rootDir, File baseFile)
    283309  {
    284310    if (slistener == null)
     
    292318
    293319    this.torrent = torrent;
    294     this.rootDataDir = rootDir;
     320    this.rootDataDir = new File(rootDir);
    295321
    296322    stopped = true;
     
    395421          {
    396422            activity = "Checking storage";
    397             storage = new Storage(_util, meta, slistener);
     423            if (baseFile == null) {
     424                String base = Storage.filterName(meta.getName());
     425                if (_util.getFilesPublic())
     426                    baseFile = new File(rootDataDir, base);
     427                else
     428                    baseFile = new SecureFile(rootDataDir, base);
     429            }
     430            storage = new Storage(_util, baseFile, meta, slistener);
    398431            if (completeListener != null) {
    399                 storage.check(rootDataDir,
    400                               completeListener.getSavedTorrentTime(this),
     432                storage.check(completeListener.getSavedTorrentTime(this),
    401433                              completeListener.getSavedTorrentBitField(this));
    402434            } else {
    403                 storage.check(rootDataDir);
     435                storage.check();
    404436            }
    405437            // have to figure out when to reopen
     
    453485    this.infoHash = ih;
    454486    this.additionalTrackerURL = trackerURL;
    455     this.rootDataDir = rootDir;
     487    this.rootDataDir = new File(rootDir);
    456488    stopped = true;
    457489    id = generateID();
     
    548580        if (storage != null) {
    549581            try {
    550                  storage.reopen(rootDataDir);
     582                 storage.reopen();
    551583             }   catch (IOException ioe) {
    552584                 try { storage.close(); } catch (IOException ioee) {
     
    11031135  public void gotMetaInfo(PeerCoordinator coordinator, MetaInfo metainfo) {
    11041136      try {
     1137          String base = Storage.filterName(metainfo.getName());
     1138          File baseFile;
     1139          if (_util.getFilesPublic())
     1140              baseFile = new File(rootDataDir, base);
     1141          else
     1142              baseFile = new SecureFile(rootDataDir, base);
    11051143          // The following two may throw IOE...
    1106           storage = new Storage(_util, metainfo, this);
    1107           storage.check(rootDataDir);
     1144          storage = new Storage(_util, baseFile, metainfo, this);
     1145          storage.check();
    11081146          // ... so don't set meta until here
    11091147          meta = metainfo;
  • apps/i2psnark/java/src/org/klomp/snark/SnarkManager.java

    r4ba40b3 rc9ce1751  
    1616import java.util.HashMap;
    1717import java.util.HashSet;
     18import java.util.Iterator;
    1819import java.util.List;
    1920import java.util.Map;
     
    4142
    4243import org.klomp.snark.dht.DHT;
     44import org.klomp.snark.dht.KRPC;
    4345
    4446/**
     
    5658    private final Set<String> _magnets;
    5759    private final Object _addSnarkLock;
    58     private /* FIXME final FIXME */ File _configFile;
     60    private File _configFile;
     61    private File _configDir;
     62    /** one lock for all config, files for simplicity */
     63    private final Object _configLock = new Object();
    5964    private Properties _config;
    6065    private final I2PAppContext _context;
     
    8287    public static final String PROP_UPBW_MAX = "i2psnark.upbw.max";
    8388    public static final String PROP_DIR = "i2psnark.dir";
    84     public static final String PROP_META_PREFIX = "i2psnark.zmeta.";
    85     public static final String PROP_META_BITFIELD_SUFFIX = ".bitfield";
    86     public static final String PROP_META_PRIORITY_SUFFIX = ".priority";
    87     public static final String PROP_META_MAGNET_PREFIX = "i2psnark.magnet.";
     89    private static final String PROP_META_PREFIX = "i2psnark.zmeta.";
     90    private static final String PROP_META_STAMP = "stamp";
     91    private static final String PROP_META_BASE = "base";
     92    private static final String PROP_META_BITFIELD = "bitfield";
     93    private static final String PROP_META_PRIORITY = "priority";
     94    private static final String PROP_META_BITFIELD_SUFFIX = ".bitfield";
     95    private static final String PROP_META_PRIORITY_SUFFIX = ".priority";
     96    private static final String PROP_META_MAGNET_PREFIX = "i2psnark.magnet.";
    8897
    8998    private static final String CONFIG_FILE_SUFFIX = ".config";
     99    private static final String CONFIG_FILE = "i2psnark" + CONFIG_FILE_SUFFIX;
    90100    public static final String PROP_FILES_PUBLIC = "i2psnark.filesPublic";
    91     public static final String PROP_AUTO_START = "i2snark.autoStart";   // oops
     101    public static final String PROP_OLD_AUTO_START = "i2snark.autoStart";   // oops
     102    public static final String PROP_AUTO_START = "i2psnark.autoStart";      // convert in migration to new config file
    92103    public static final String DEFAULT_AUTO_START = "false";
    93104    //public static final String PROP_LINK_PREFIX = "i2psnark.linkPrefix";
     
    110121    public static final int DEFAULT_REFRESH_DELAY_SECS = 60;
    111122    private static final int DEFAULT_PAGE_SIZE = 50;
     123    public static final String CONFIG_DIR_SUFFIX = ".d";
     124    private static final String SUBDIR_PREFIX = "s";
     125    private static final String B64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-~";
    112126
    113127    /**
     
    170184        _util = new I2PSnarkUtil(_context, ctxName);
    171185        String cfile = ctxName + CONFIG_FILE_SUFFIX;
    172         _configFile = new File(cfile);
    173         if (!_configFile.isAbsolute())
    174             _configFile = new File(_context.getConfigDir(), cfile);
     186        File configFile = new File(cfile);
     187        if (!configFile.isAbsolute())
     188            configFile = new File(_context.getConfigDir(), cfile);
     189        _configDir = migrateConfig(configFile);
     190        _configFile = new File(_configDir, CONFIG_FILE);
    175191        _trackerMap = new ConcurrentHashMap<String, Tracker>(4);
    176192        loadConfig(null);
     
    342358    }
    343359
     360    /**
     361     *  Migrate the old flat config file to the new config dir
     362     *  containing the config file minus the per-torrent entries,
     363     *  the dht file, and 16 subdirs for per-torrent config files
     364     *  Caller must synch.
     365     *
     366     *  @return the new config directory, non-null
     367     *  @throws RuntimeException on creation fail
     368     *  @since 0.9.11
     369     */
     370    private File migrateConfig(File oldFile) {
     371        File dir = new SecureDirectory(oldFile + CONFIG_DIR_SUFFIX);
     372        if ((!dir.exists()) && (!dir.mkdirs())) {
     373            _log.error("Error creating I2PSnark config dir " + dir);
     374            throw new RuntimeException("Error creating I2PSnark config dir " + dir);
     375        }
     376        // move the DHT file as-is
     377        String oldName = oldFile.toString();
     378        if (oldName.endsWith(CONFIG_FILE_SUFFIX)) {
     379            String oldDHT = oldName.replace(CONFIG_FILE_SUFFIX, KRPC.DHT_FILE_SUFFIX);
     380            File oldDHTFile = new File(oldDHT);
     381            if (oldDHTFile.exists()) {
     382                File newDHTFile = new File(dir, "i2psnark" + KRPC.DHT_FILE_SUFFIX);
     383                FileUtil.rename(oldDHTFile, newDHTFile);
     384            }
     385        }
     386        if (!oldFile.exists())
     387            return dir;
     388        Properties oldProps = new Properties();
     389        try {
     390            DataHelper.loadProps(oldProps, oldFile);
     391            // a good time to fix this ancient typo
     392            String auto = (String) oldProps.remove(PROP_OLD_AUTO_START);
     393            if (auto != null)
     394                oldProps.setProperty(PROP_AUTO_START, auto);
     395        } catch (IOException ioe) {
     396           _log.error("Error loading I2PSnark config " + oldFile, ioe);
     397           return dir;
     398        }
     399        // Gather the props for each torrent, removing them from config
     400        // old b64 of hash as key
     401        Map<String, Properties> configs = new HashMap<String, Properties>(16);
     402        for (Iterator<Map.Entry<Object, Object>> iter = oldProps.entrySet().iterator(); iter.hasNext(); ) {
     403            Map.Entry<Object, Object> e = iter.next();
     404            String k = (String) e.getKey();
     405            if (k.startsWith(PROP_META_PREFIX)) {
     406                iter.remove();
     407                String v = (String) e.getValue();
     408                try {
     409                    k = k.substring(PROP_META_PREFIX.length());
     410                    String h = k.substring(0, 28);  // length of b64 of 160 bit infohash
     411                    k = k.substring(29); // skip '.'
     412                    Properties tprops = configs.get(h);
     413                    if (tprops == null) {
     414                        tprops = new OrderedProperties();
     415                        configs.put(h, tprops);
     416                    }
     417                    if (k.equals(PROP_META_BITFIELD)) {
     418                        // old config was timestamp,bitfield; split them
     419                        int comma = v.indexOf(',');
     420                        if (comma > 0 && v.length() > comma + 1) {
     421                            tprops.put(PROP_META_STAMP, v.substring(0, comma));
     422                            tprops.put(PROP_META_BITFIELD, v.substring(comma + 1));
     423                        } else {
     424                            // timestamp only??
     425                            tprops.put(PROP_META_STAMP, v);
     426                        }
     427                    } else {
     428                        tprops.put(k, v);
     429                    }
     430                } catch (IndexOutOfBoundsException ioobe) {
     431                    continue;
     432                }
     433            }
     434        }
     435        // Now make a config file for each torrent
     436        for (Map.Entry<String, Properties> e : configs.entrySet()) {
     437            String b64 = e.getKey();
     438            Properties props = e.getValue();
     439            if (props.isEmpty())
     440                continue;
     441            b64 = b64.replace('$', '=');
     442            byte[] ih = Base64.decode(b64);
     443            if (ih == null || ih.length != 20)
     444                continue;
     445            File cfg = configFile(dir, ih);
     446            if (!cfg.exists()) {
     447                File subdir = cfg.getParentFile();
     448                if (!subdir.exists())
     449                    subdir.mkdirs();
     450                try {
     451                    DataHelper.storeProps(props, cfg);
     452                } catch (IOException ioe) {
     453                    _log.error("Error storing I2PSnark config " + cfg, ioe);
     454                }
     455            }
     456        }
     457        // now store in new location, minus the zmeta entries
     458        File newFile = new File(dir, CONFIG_FILE);
     459        Properties newProps = new OrderedProperties();
     460        newProps.putAll(oldProps);
     461        try {
     462            DataHelper.storeProps(newProps, newFile);
     463        } catch (IOException ioe) {
     464            _log.error("Error storing I2PSnark config " + newFile, ioe);
     465            return dir;
     466        }
     467        oldFile.delete();
     468        if (_log.shouldLog(Log.WARN))
     469            _log.warn("Config migrated from " + oldFile + " to " + dir);
     470        return dir;
     471    }
     472
     473    /**
     474     *  The config for a torrent
     475     *  @return non-null, possibly empty
     476     *  @since 0.9.11
     477     */
     478    private Properties getConfig(Snark snark) {
     479        return getConfig(snark.getInfoHash());
     480    }
     481
     482    /**
     483     *  The config for a torrent
     484     *  @param ih 20-byte infohash
     485     *  @return non-null, possibly empty
     486     *  @since 0.9.11
     487     */
     488    private Properties getConfig(byte[] ih) {
     489        Properties rv = new OrderedProperties();
     490        File conf = configFile(_configDir, ih);
     491        synchronized(_configLock) {  // one lock for all
     492            try {
     493                DataHelper.loadProps(rv, conf);
     494            } catch (IOException ioe) {}
     495        }
     496        return rv;
     497    }
     498
     499    /**
     500     *  The config file for a torrent
     501     *  @param confDir the config directory
     502     *  @param ih 20-byte infohash
     503     *  @since 0.9.11
     504     */
     505    private static File configFile(File confDir, byte[] ih) {
     506        String hex = I2PSnarkUtil.toHex(ih);
     507        File subdir = new SecureDirectory(confDir, SUBDIR_PREFIX + B64.charAt((ih[0] >> 2) & 0x3f));
     508        return new File(subdir, hex + CONFIG_FILE_SUFFIX);
     509    }
     510
    344511    /** null to set initial defaults */
    345512    public void loadConfig(String filename) {
     513        synchronized(_configLock) {
     514            locked_loadConfig(filename);
     515        }
     516    }
     517
     518    /** null to set initial defaults */
     519    private void locked_loadConfig(String filename) {
    346520        if (_config == null)
    347521            _config = new OrderedProperties();
     
    350524            if (!cfg.isAbsolute())
    351525                cfg = new File(_context.getConfigDir(), filename);
    352             _configFile = cfg;
    353             if (cfg.exists()) {
     526            _configDir = migrateConfig(cfg);
     527            _configFile = new File(_configDir, CONFIG_FILE);
     528            if (_configFile.exists()) {
    354529                try {
    355                     DataHelper.loadProps(_config, cfg);
     530                    DataHelper.loadProps(_config, _configFile);
    356531                } catch (IOException ioe) {
    357                    _log.error("Error loading I2PSnark config '" + filename + "'", ioe);
     532                   _log.error("Error loading I2PSnark config " + _configFile, ioe);
    358533                }
    359534            }
     
    389564        updateConfig();
    390565    }
     566
    391567    /**
    392568     * Get current theme.
     
    503679     */
    504680    public void updateConfig(String dataDir, boolean filesPublic, boolean autoStart, String refreshDelay,
     681                             String startDelay, String pageSize, String seedPct, String eepHost,
     682                             String eepPort, String i2cpHost, String i2cpPort, String i2cpOpts,
     683                             String upLimit, String upBW, boolean useOpenTrackers, boolean useDHT, String theme) {
     684        synchronized(_configLock) {
     685            locked_updateConfig(dataDir, filesPublic, autoStart, refreshDelay,
     686                                startDelay,  pageSize,  seedPct,  eepHost,
     687                                eepPort,  i2cpHost,  i2cpPort,  i2cpOpts,
     688                                upLimit,  upBW, useOpenTrackers, useDHT,  theme);
     689        }
     690    }
     691
     692    private void locked_updateConfig(String dataDir, boolean filesPublic, boolean autoStart, String refreshDelay,
    505693                             String startDelay, String pageSize, String seedPct, String eepHost,
    506694                             String eepPort, String i2cpHost, String i2cpPort, String i2cpOpts,
     
    8371025    public void saveConfig() {
    8381026        try {
    839             synchronized (_configFile) {
     1027            synchronized (_configLock) {
    8401028                DataHelper.storeProps(_config, _configFile);
    8411029            }
     
    8451033    }
    8461034   
    847     public Properties getConfig() { return _config; }
    848    
    849     /** @since Jetty 7 */
    850     public String getConfigFilename() {
    851         return _configFile.getAbsolutePath();
    852     }
    853 
    8541035    /** hardcoded for sanity.  perhaps this should be customizable, for people who increase their ulimit, etc. */
    8551036    public static final int MAX_FILES_PER_TORRENT = 512;
     
    9091090    /**
    9101091     *  Caller must verify this torrent is not already added.
     1092     *
     1093     *  @param filename the absolute path to save the metainfo to, generally ending in ".torrent"
     1094     *  @param baseFile may be null, if so look in rootDataDir
    9111095     *  @throws RuntimeException via Snark.fatal()
    9121096     */
    913     private void addTorrent(String filename) { addTorrent(filename, false); }
     1097    private void addTorrent(String filename) {
     1098        addTorrent(filename, null, false);
     1099    }
    9141100
    9151101    /**
    9161102     *  Caller must verify this torrent is not already added.
     1103     *
     1104     *  @param filename the absolute path to save the metainfo to, generally ending in ".torrent"
     1105     *  @param baseFile may be null, if so look in rootDataDir
    9171106     *  @throws RuntimeException via Snark.fatal()
    9181107     */
    919     private void addTorrent(String filename, boolean dontAutoStart) {
     1108    private void addTorrent(String filename, File baseFile, boolean dontAutoStart) {
    9201109        if ((!dontAutoStart) && !_util.connected()) {
    9211110            addMessage(_("Connecting to I2P"));
     
    9981187                        // TODO load saved closest DHT nodes and pass to the Snark ?
    9991188                        // This may take a LONG time
     1189                        if (baseFile == null)
     1190                            baseFile = getSavedBaseFile(info.getInfoHash());
     1191                        if (_log.shouldLog(Log.INFO))
     1192                            _log.info("New Snark, torrent: " + filename + " base: " + baseFile);
    10001193                        torrent = new Snark(_util, filename, null, -1, null, null, this,
    10011194                                            _peerCoordinatorSet, _connectionAcceptor,
    1002                                             false, dataDir.getPath());
     1195                                            false, dataDir.getPath(), baseFile);
    10031196                        loadSavedFilePriorities(torrent);
    10041197                        synchronized (_snarks) {
     
    11431336     * This may take a LONG time to create or check the storage.
    11441337     *
     1338     * Called from servlet.
     1339     *
    11451340     * @param metainfo the metainfo for the torrent
    11461341     * @param bitfield the current completion status of the torrent
    11471342     * @param filename the absolute path to save the metainfo to, generally ending in ".torrent", which is also the name of the torrent
    11481343     *                 Must be a filesystem-safe name.
     1344     * @param baseFile may be null, if so look in rootDataDir
    11491345     * @throws RuntimeException via Snark.fatal()
    11501346     * @since 0.8.4
    11511347     */
    1152     public void addTorrent(MetaInfo metainfo, BitField bitfield, String filename, boolean dontAutoStart) throws IOException {
     1348    public void addTorrent(MetaInfo metainfo, BitField bitfield, String filename, File baseFile, boolean dontAutoStart) throws IOException {
    11531349        // prevent interference by DirMonitor
    11541350        synchronized (_snarks) {
     
    11591355            }
    11601356            // so addTorrent won't recheck
    1161             saveTorrentStatus(metainfo, bitfield, null); // no file priorities
     1357            saveTorrentStatus(metainfo, bitfield, null, baseFile); // no file priorities
    11621358            try {
    11631359                locked_writeMetaInfo(metainfo, filename, areFilesPublic());
    11641360                // hold the lock for a long time
    1165                 addTorrent(filename, dontAutoStart);
     1361                addTorrent(filename, baseFile, dontAutoStart);
    11661362            } catch (IOException ioe) {
    11671363                addMessage(_("Failed to copy torrent file to {0}", filename));
     
    12361432     */
    12371433    public long getSavedTorrentTime(Snark snark) {
    1238         byte[] ih = snark.getInfoHash();
    1239         String infohash = Base64.encode(ih);
    1240         infohash = infohash.replace('=', '$');
    1241         String time = _config.getProperty(PROP_META_PREFIX + infohash + PROP_META_BITFIELD_SUFFIX);
     1434        Properties config = getConfig(snark);
     1435        String time = config.getProperty(PROP_META_STAMP);
    12421436        if (time == null)
    12431437            return 0;
    1244         int comma = time.indexOf(',');
    1245         if (comma <= 0)
    1246             return 0;
    1247         time = time.substring(0, comma);
    12481438        try { return Long.parseLong(time); } catch (NumberFormatException nfe) {}
    12491439        return 0;
     
    12591449        if (metainfo == null)
    12601450            return null;
    1261         byte[] ih = snark.getInfoHash();
    1262         String infohash = Base64.encode(ih);
    1263         infohash = infohash.replace('=', '$');
    1264         String bf = _config.getProperty(PROP_META_PREFIX + infohash + PROP_META_BITFIELD_SUFFIX);
     1451        Properties config = getConfig(snark);
     1452        String bf = config.getProperty(PROP_META_BITFIELD);
    12651453        if (bf == null)
    12661454            return null;
    1267         int comma = bf.indexOf(',');
    1268         if (comma <= 0)
    1269             return null;
    1270         bf = bf.substring(comma + 1).trim();
    12711455        int len = metainfo.getPieces();
    12721456        if (bf.equals(".")) {
     
    12951479        if (metainfo.getFiles() == null)
    12961480            return;
    1297         byte[] ih = snark.getInfoHash();
    1298         String infohash = Base64.encode(ih);
    1299         infohash = infohash.replace('=', '$');
    1300         String pri = _config.getProperty(PROP_META_PREFIX + infohash + PROP_META_PRIORITY_SUFFIX);
     1481        Properties config = getConfig(snark);
     1482        String pri = config.getProperty(PROP_META_PRIORITY);
    13011483        if (pri == null)
    13021484            return;
     
    13131495        storage.setFilePriorities(rv);
    13141496    }
     1497
     1498    /**
     1499     * Get the base location for a torrent from the config file.
     1500     * @return File or null, doesn't necessarily exist
     1501     * @since 0.9.11
     1502     */
     1503    public File getSavedBaseFile(byte[] ih) {
     1504        Properties config = getConfig(ih);
     1505        String base = config.getProperty(PROP_META_BASE);
     1506        if (base == null)
     1507            return null;
     1508        return new File(base);
     1509    }
    13151510   
    13161511    /**
    13171512     * Save the completion status of a torrent and the current time in the config file
    1318      * in the form "i2psnark.zmeta.$base64infohash=$time,$base64bitfield".
    1319      * The config file property key is appended with the Base64 of the infohash,
    1320      * with the '=' changed to '$' since a key can't contain '='.
     1513     * for that torrent.
    13211514     * The time is a standard long converted to string.
    13221515     * The status is either a bitfield converted to Base64 or "." for a completed
     
    13251518     * @param bitfield non-null
    13261519     * @param priorities may be null
    1327      */
    1328     public void saveTorrentStatus(MetaInfo metainfo, BitField bitfield, int[] priorities) {
     1520     * @param base may be null
     1521     */
     1522    public void saveTorrentStatus(MetaInfo metainfo, BitField bitfield, int[] priorities, File base) {
     1523        synchronized (_configLock) {
     1524            locked_saveTorrentStatus(metainfo, bitfield, priorities, base);
     1525        }
     1526    }
     1527
     1528    private void locked_saveTorrentStatus(MetaInfo metainfo, BitField bitfield, int[] priorities, File base) {
    13291529        byte[] ih = metainfo.getInfoHash();
    1330         String infohash = Base64.encode(ih);
    1331         infohash = infohash.replace('=', '$');
    1332         String now = "" + System.currentTimeMillis();
    13331530        String bfs;
    13341531        if (bitfield.complete()) {
     
    13381535          bfs = Base64.encode(bf);
    13391536        }
    1340         _config.setProperty(PROP_META_PREFIX + infohash + PROP_META_BITFIELD_SUFFIX, now + "," + bfs);
     1537        Properties config = getConfig(ih);
     1538        config.setProperty(PROP_META_STAMP, Long.toString(System.currentTimeMillis()));
     1539        config.setProperty(PROP_META_BITFIELD, bfs);
     1540        if (base != null)
     1541            config.setProperty(PROP_META_BASE, base.getAbsolutePath());
    13411542
    13421543        // now the file priorities
    1343         String prop = PROP_META_PREFIX + infohash + PROP_META_PRIORITY_SUFFIX;
    13441544        if (priorities != null) {
    13451545            boolean nonzero = false;
     
    13591559                        buf.append(',');
    13601560                }
    1361                 _config.setProperty(prop, buf.toString());
     1561                config.setProperty(PROP_META_PRIORITY, buf.toString());
    13621562            } else {
    1363                 _config.remove(prop);
     1563                config.remove(PROP_META_PRIORITY);
    13641564            }
    13651565        } else {
    1366             _config.remove(prop);
     1566            config.remove(PROP_META_PRIORITY);
    13671567        }
    13681568
    13691569        // TODO save closest DHT nodes too
    13701570
    1371         saveConfig();
    1372     }
    1373    
    1374     /**
    1375      * Remove the status of a torrent from the config file.
    1376      * This may help the config file from growing too big.
     1571        File conf = configFile(_configDir, ih);
     1572        File subdir = conf.getParentFile();
     1573        if (!subdir.exists())
     1574            subdir.mkdirs();
     1575        try {
     1576            DataHelper.storeProps(config, conf);
     1577        } catch (IOException ioe) {
     1578            _log.error("Unable to save the config to " + conf);
     1579        }
     1580    }
     1581   
     1582    /**
     1583     * Remove the status of a torrent by removing the config file.
    13771584     */
    13781585    public void removeTorrentStatus(MetaInfo metainfo) {
    13791586        byte[] ih = metainfo.getInfoHash();
    1380         String infohash = Base64.encode(ih);
    1381         infohash = infohash.replace('=', '$');
    1382         _config.remove(PROP_META_PREFIX + infohash + PROP_META_BITFIELD_SUFFIX);
    1383         _config.remove(PROP_META_PREFIX + infohash + PROP_META_PRIORITY_SUFFIX);
    1384         saveConfig();
     1587        File conf = configFile(_configDir, ih);
     1588        synchronized (_configLock) {
     1589            conf.delete();
     1590            File subdir = conf.getParentFile();
     1591            String[] files = subdir.list();
     1592            if (files != null && files.length == 0)
     1593                subdir.delete();
     1594        }
    13851595    }
    13861596   
     
    15821792        Storage storage = snark.getStorage();
    15831793        if (meta != null && storage != null)
    1584             saveTorrentStatus(meta, storage.getBitField(), storage.getFilePriorities());
     1794            saveTorrentStatus(meta, storage.getBitField(), storage.getFilePriorities(), storage.getBase());
    15851795    }
    15861796   
     
    16041814                return null;
    16051815            }
    1606             saveTorrentStatus(meta, storage.getBitField(), null); // no file priorities
     1816            saveTorrentStatus(meta, storage.getBitField(), null, storage.getBase()); // no file priorities
    16071817            // temp for addMessage() in case canonical throws
    16081818            String name = storage.getBaseName();
     
    17051915                    // Snark.fatal() throws a RuntimeException
    17061916                    // don't let one bad torrent kill the whole loop
    1707                     addTorrent(name, !shouldAutoStart());
     1917                    addTorrent(name, null, !shouldAutoStart());
    17081918                } catch (Exception e) {
    17091919                    addMessage(_("Error: Could not add the torrent {0}", name) + ": " + e);
  • apps/i2psnark/java/src/org/klomp/snark/Storage.java

    r4ba40b3 rc9ce1751  
    3333import java.util.List;
    3434import java.util.Map;
     35import java.util.SortedSet;
    3536import java.util.StringTokenizer;
     37import java.util.TreeSet;
    3638import java.util.concurrent.ConcurrentHashMap;
    3739import java.util.concurrent.atomic.AtomicInteger;
     
    5355  private final MetaInfo metainfo;
    5456  private final List<TorrentFile> _torrentFiles;
     57  private final File _base;
    5558  private final StorageListener listener;
    5659  private final I2PSnarkUtil _util;
     
    8487
    8588  /**
    86    * Creates a new storage based on the supplied MetaInfo.  This will
     89   * Creates a new storage based on the supplied MetaInfo.
     90   *
     91   * Does not check storage. Caller MUST call check(), which will
    8792   * try to create and/or check all needed files in the MetaInfo.
    8893   *
    89    * Does not check storage. Caller MUST call check()
    90    */
    91   public Storage(I2PSnarkUtil util, MetaInfo metainfo, StorageListener listener)
     94   * @param baseFile the torrent data file or dir
     95   */
     96  public Storage(I2PSnarkUtil util, File baseFile, MetaInfo metainfo, StorageListener listener)
    9297  {
    9398    _util = util;
    9499    _log = util.getContext().logManager().getLog(Storage.class);
     100    _base = baseFile;
    95101    this.metainfo = metainfo;
    96102    this.listener = listener;
     
    122128  {
    123129    _util = util;
     130    _base = baseFile;
    124131    _log = util.getContext().logManager().getLog(Storage.class);
    125132    this.listener = listener;
     
    306313
    307314  /**
    308    *  @param file canonical path (non-directory)
     315   *  @param file non-canonical path (non-directory)
    309316   *  @return number of bytes remaining; -1 if unknown file
    310317   *  @since 0.7.14
    311318   */
    312   public long remaining(String file) {
     319  public long remaining(File file) {
    313320      long bytes = 0;
    314321      for (TorrentFile tf : _torrentFiles) {
    315322          File f = tf.RAFfile;
    316           // use canonical in case snark dir or sub dirs are symlinked
    317           String canonical = null;
    318           if (f != null) {
    319               try {
    320                   canonical = f.getCanonicalPath();
    321               } catch (IOException ioe) {
    322                   f = null;
    323               }
    324           }
    325           if (f != null && canonical.equals(file)) {
     323          if (f.equals(file)) {
    326324              if (complete())
    327325                  return 0;
     
    349347
    350348  /**
    351    *  @param file canonical path (non-directory)
     349   *  @param file non-canonical path (non-directory)
    352350   *  @since 0.8.1
    353351   */
    354   public int getPriority(String file) {
     352  public int getPriority(File file) {
    355353      if (complete() || metainfo.getFiles() == null)
    356354          return 0;
    357355      for (TorrentFile tf : _torrentFiles) {
    358356          File f = tf.RAFfile;
    359           // use canonical in case snark dir or sub dirs are symlinked
    360           if (f != null) {
    361               try {
    362                   String canonical = f.getCanonicalPath();
    363                   if (canonical.equals(file))
    364                       return tf.priority;
    365               } catch (IOException ioe) {}
    366           }
     357          if (f.equals(file))
     358              return tf.priority;
    367359      }
    368360      return 0;
     
    372364   *  Must call Snark.updatePiecePriorities()
    373365   *  (which calls getPiecePriorities()) after calling this.
    374    *  @param file canonical path (non-directory)
     366   *  @param file non-canonical path (non-directory)
    375367   *  @param pri default 0; <0 to disable
    376368   *  @since 0.8.1
    377369   */
    378   public void setPriority(String file, int pri) {
     370  public void setPriority(File file, int pri) {
    379371      if (complete() || metainfo.getFiles() == null)
    380372          return;
    381373      for (TorrentFile tf : _torrentFiles) {
    382374          File f = tf.RAFfile;
    383           // use canonical in case snark dir or sub dirs are symlinked
    384           if (f != null) {
    385               try {
    386                   String canonical = f.getCanonicalPath();
    387                   if (canonical.equals(file)) {
    388                       tf.priority = pri;
    389                       return;
    390                   }
    391               } catch (IOException ioe) {}
     375          if (f.equals(file)) {
     376              tf.priority = pri;
     377              return;
    392378          }
    393379      }
     
    491477   * Only call this once, and only after the constructor with the metainfo.
    492478   */
    493   public void check(String rootDir) throws IOException
    494   {
    495     check(rootDir, 0, null);
     479  public void check() throws IOException
     480  {
     481    check(0, null);
    496482  }
    497483
     
    501487   * Only call this once, and only after the constructor with the metainfo.
    502488   */
    503   public void check(String rootDir, long savedTime, BitField savedBitField) throws IOException
    504   {
    505     File base;
     489  public void check(long savedTime, BitField savedBitField) throws IOException
     490  {
    506491    boolean areFilesPublic = _util.getFilesPublic();
    507     if (areFilesPublic)
    508         base = new File(rootDir, filterName(metainfo.getName()));
    509     else
    510         base = new SecureFile(rootDir, filterName(metainfo.getName()));
    511492    boolean useSavedBitField = savedTime > 0 && savedBitField != null;
    512493
     
    518499        // Create base as file.
    519500        if (_log.shouldLog(Log.INFO))
    520             _log.info("Creating/Checking file: " + base);
     501            _log.info("Creating/Checking file: " + _base);
    521502        // createNewFile() can throw a "Permission denied" IOE even if the file exists???
    522503        // so do it second
    523         if (!base.exists() && !base.createNewFile())
    524           throw new IOException("Could not create file " + base);
    525 
    526         _torrentFiles.add(new TorrentFile(base, base, metainfo.getTotalLength()));
     504        if (!_base.exists() && !_base.createNewFile())
     505          throw new IOException("Could not create file " + _base);
     506
     507        _torrentFiles.add(new TorrentFile(_base, _base, metainfo.getTotalLength()));
    527508        if (useSavedBitField) {
    528             long lm = base.lastModified();
     509            long lm = _base.lastModified();
    529510            if (lm <= 0 || lm > savedTime)
    530511                useSavedBitField = false;
    531             else if (base.length() != metainfo.getTotalLength())
     512            else if (_base.length() != metainfo.getTotalLength())
    532513                useSavedBitField = false;
    533514        }
     
    537518        // Create base as dir.
    538519        if (_log.shouldLog(Log.INFO))
    539             _log.info("Creating/Checking directory: " + base);
    540         if (!base.mkdir() && !base.isDirectory())
    541           throw new IOException("Could not create directory " + base);
     520            _log.info("Creating/Checking directory: " + _base);
     521        if (!_base.mkdir() && !_base.isDirectory())
     522          throw new IOException("Could not create directory " + _base);
    542523
    543524        List<Long> ls = metainfo.getLengths();
     
    547528          {
    548529            List<String> path = files.get(i);
    549             File f = createFileFromNames(base, path, areFilesPublic);
     530            File f = createFileFromNames(_base, path, areFilesPublic);
    550531            // dup file name check after filtering
    551532            for (int j = 0; j < i; j++) {
     
    563544                        lastPath = '_' + lastPath;
    564545                    path.set(last, lastPath);
    565                     f = createFileFromNames(base, path, areFilesPublic);
     546                    f = createFileFromNames(_base, path, areFilesPublic);
    566547                    j = 0;
    567548                }
    568549            }
    569550            long len = ls.get(i).longValue();
    570             _torrentFiles.add(new TorrentFile(base, f, len));
     551            _torrentFiles.add(new TorrentFile(_base, f, len));
    571552            total += len;
    572553            if (useSavedBitField) {
     
    615596   * @throws IOE on fail
    616597   */
    617   public void reopen(String rootDir) throws IOException
     598  public void reopen() throws IOException
    618599  {
    619600      if (_torrentFiles.isEmpty())
     
    691672   *  things going in the wrong place if there are duplicates
    692673   *  in intermediate path elements after filtering.
     674   *
     675   *  @param names path elements
    693676   */
    694677  private static File createFileFromNames(File base, List<String> names, boolean areFilesPublic) throws IOException
     
    726709  }
    727710
    728   public static File getFileFromNames(File base, List<String> names)
    729   {
    730     Iterator<String> it = names.iterator();
    731     while (it.hasNext())
    732       {
    733         String name = filterName(it.next());
    734         base = new File(base, name);
    735       }
    736     return base;
     711  /**
     712   *  The base file or directory.
     713   *  @return the File
     714   *  @since 0.9.11
     715   */
     716  public File getBase() {
     717      return _base;
     718  }
     719
     720  /**
     721   *  Does not include directories. Unsorted.
     722   *  @return a new List
     723   *  @since 0.9.11
     724   */
     725  public List<File> getFiles() {
     726      List<File> rv = new ArrayList<File>(_torrentFiles.size());
     727      for (TorrentFile tf : _torrentFiles) {
     728          rv.add(tf.RAFfile);
     729      }
     730      return rv;
     731  }
     732
     733  /**
     734   *  Includes the base for a multi-file torrent.
     735   *  Sorted bottom-up for easy deletion.
     736   *  Slow. Use for deletion only.
     737   *  @since 0.9.11
     738   *  @return a new Set or null for a single-file torrent
     739   */
     740  public SortedSet<File> getDirectories() {
     741      if (!_base.isDirectory())
     742          return null;
     743      SortedSet<File> rv = new TreeSet<File>(Collections.reverseOrder());
     744      rv.add(_base);
     745      for (TorrentFile tf : _torrentFiles) {
     746          File f = tf.RAFfile;
     747          do {
     748              f = f.getParentFile();
     749          } while (f != null && rv.add(f));
     750      }
     751      return rv;
    737752  }
    738753
  • apps/i2psnark/java/src/org/klomp/snark/dht/KRPC.java

    r4ba40b3 rc9ce1751  
    4141import net.i2p.util.SimpleTimer2;
    4242
     43import org.klomp.snark.SnarkManager;
    4344import org.klomp.snark.TrackerClient;
    4445import org.klomp.snark.bencode.BDecoder;
     
    153154    private static final long EXPLORE_TIME = 877*1000;
    154155    private static final long BLACKLIST_CLEAN_TIME = 17*60*1000;
    155     private static final String DHT_FILE_SUFFIX = ".dht.dat";
     156    public static final String DHT_FILE_SUFFIX = ".dht.dat";
    156157
    157158    private static final int SEND_CRYPTO_TAGS = 8;
     
    186187        }
    187188        _myNodeInfo = new NodeInfo(_myNID, session.getMyDestination(), _qPort);
    188         _dhtFile = new File(ctx.getConfigDir(), baseName + DHT_FILE_SUFFIX);
    189         _backupDhtFile = baseName.equals("i2psnark") ? null : new File(ctx.getConfigDir(), "i2psnark" + DHT_FILE_SUFFIX);
     189        File conf = new File(ctx.getConfigDir(), baseName + ".config" + SnarkManager.CONFIG_DIR_SUFFIX);
     190        _dhtFile = new File(conf, "i2psnark" + DHT_FILE_SUFFIX);
     191        if (baseName.equals("i2psnark")) {
     192            _backupDhtFile = null;
     193        } else {
     194            File bconf = new File(ctx.getConfigDir(), "i2psnark.config" + SnarkManager.CONFIG_DIR_SUFFIX);
     195            _backupDhtFile = new File(bconf, "i2psnark" + DHT_FILE_SUFFIX);
     196        }
    190197        _knownNodes = new DHTNodes(ctx, _myNID);
    191198
  • apps/i2psnark/java/src/org/klomp/snark/web/BasicServlet.java

    r4ba40b3 rc9ce1751  
    182182        if (_warBase != null && pathInContext.startsWith(_warBase)) {
    183183            r = new JarContent(pathInContext);
    184         } else if (!pathInContext.contains("..") &&
    185                    !pathInContext.endsWith("/")) {
    186             File f = new File(_resourceBase, pathInContext);
     184        } else {
     185            File f = getResource(pathInContext);
    187186            // exists && !directory
    188             if (f.isFile())
     187            if (f != null && f.isFile())
    189188                r = new FileContent(f);
    190189        }
  • apps/i2psnark/java/src/org/klomp/snark/web/I2PSnarkServlet.java

    r4ba40b3 rc9ce1751  
    1919import java.util.Set;
    2020import java.util.TreeMap;
    21 import java.util.TreeSet;
    2221
    2322import javax.servlet.ServletConfig;
     
    6059    private static final String DEFAULT_NAME = "i2psnark";
    6160    public static final String PROP_CONFIG_FILE = "i2psnark.configFile";
     61    private static final String WARBASE = "/.icons/";
    6262 
    6363    public I2PSnarkServlet() {
     
    8585        loadMimeMap("org/klomp/snark/web/mime");
    8686        setResourceBase(_manager.getDataDir());
    87         setWarBase("/.icons/");
     87        setWarBase(WARBASE);
    8888    }
    8989   
     
    9696
    9797    /**
    98      *  We override this instead of passing a resource base to super(), because
    99      *  if a resource base is set, super.getResource() always uses that base,
    100      *  and we can't get any resources (like icons) out of the .war
     98     *  We override this to set the file relative to the storage dirctory
     99     *  for the torrent.
     100     *
     101     *  @param pathInContext should always start with /
    101102     */
    102103    @Override
     
    104105    {
    105106        if (pathInContext == null || pathInContext.equals("/") || pathInContext.equals("/index.jsp") ||
    106             pathInContext.equals("/index.html") || pathInContext.startsWith("/.icons/"))
     107            !pathInContext.startsWith("/") || pathInContext.length() == 0 ||
     108            pathInContext.equals("/index.html") || pathInContext.startsWith(WARBASE))
    107109            return super.getResource(pathInContext);
    108110        // files in the i2psnark/ directory
     111        // get top level
     112        pathInContext = pathInContext.substring(1);
     113        File top = new File(pathInContext);
     114        File parent;
     115        while ((parent = top.getParentFile()) != null) {
     116            top = parent;
     117        }
     118        Snark snark = _manager.getTorrentByBaseName(top.getPath());
     119        if (snark != null) {
     120            Storage storage = snark.getStorage();
     121            if (storage != null) {
     122                File sbase = storage.getBase();
     123                String child = pathInContext.substring(top.getPath().length());
     124                return new File(sbase, child);
     125            }
     126        }
    109127        return new File(_resourceBase, pathInContext);
    110128    }
     
    192210        }
    193211
     212        // in-war icons etc.
     213        if (path != null && path.startsWith(WARBASE)) {
     214            if (method.equals("GET") || method.equals("HEAD"))
     215                super.doGet(req, resp);
     216            else  // no POST either
     217                resp.sendError(405);
     218        }
     219
    194220        boolean isConfigure = "/configure".equals(path);
    195221        // index.jsp doesn't work, it is grabbed by the war handler before here
    196         if (!(path == null || path.equals("/") || path.equals("/index.jsp") || path.equals("/index.html") || path.equals("/_post") || isConfigure)) {
     222        if (!(path == null || path.equals("/") || path.equals("/index.jsp") ||
     223              path.equals("/index.html") || path.equals("/_post") || isConfigure)) {
    197224            if (path.endsWith("/")) {
    198225                // Listing of a torrent (torrent detail page)
     
    849876                                break;
    850877                            }
     878                            Storage storage = snark.getStorage();
     879                            if (storage == null)
     880                                break;
    851881                            // step 1 delete files
    852                             for (int i = 0; i < files.size(); i++) {
    853                                 // multifile torrents have the getFiles() return lists of lists of filenames, but
    854                                 // each of those lists just contain a single file afaict...
    855                                 File df = Storage.getFileFromNames(f, files.get(i));
     882                            for (File df : storage.getFiles()) {
    856883                                if (df.delete()) {
    857884                                    //_manager.addMessage(_("Data file deleted: {0}", df.getAbsolutePath()));
     
    860887                                }
    861888                            }
    862                             // step 2 make Set of dirs with reverse sort
    863                             Set<File> dirs = new TreeSet<File>(Collections.reverseOrder());
    864                             for (List<String> list : files) {
    865                                 for (int i = 1; i < list.size(); i++) {
    866                                     dirs.add(Storage.getFileFromNames(f, list.subList(0, i)));
    867                                 }
    868                             }
    869                             // step 3 delete dirs bottom-up
     889                            // step 2 delete dirs bottom-up
     890                            Set<File> dirs = storage.getDirectories();
     891                            if (_log.shouldLog(Log.INFO))
     892                                _log.info("Dirs to delete: " + DataHelper.toString(dirs));
     893                            boolean ok = false;
    870894                            for (File df : dirs) {
    871895                                if (df.delete()) {
     896                                    ok = true;
    872897                                    //_manager.addMessage(_("Data dir deleted: {0}", df.getAbsolutePath()));
    873898                                } else {
     899                                    ok = false;
    874900                                    _manager.addMessage(_("Directory could not be deleted: {0}", df.getAbsolutePath()));
    875901                                    if (_log.shouldLog(Log.WARN))
     
    877903                                }
    878904                            }
    879                             // step 4 delete base
    880                             if (f.delete()) {
    881                                 _manager.addMessage(_("Directory deleted: {0}", f.getAbsolutePath()));
    882                             } else {
    883                                 _manager.addMessage(_("Directory could not be deleted: {0}", f.getAbsolutePath()));
    884                                 if (_log.shouldLog(Log.WARN))
    885                                     _log.warn("Could not delete dir " + f);
    886                             }
     905                            // step 3 message for base (last one)
     906                            if (ok)
     907                                _manager.addMessage(_("Directory deleted: {0}", storage.getBase()));
    887908                            break;
    888909                        }
     
    923944            String baseData = req.getParameter("baseFile");
    924945            if (baseData != null && baseData.trim().length() > 0) {
    925                 File baseFile = new File(_manager.getDataDir(), baseData);
     946                File baseFile = new File(baseData.trim());
     947                if (!baseFile.isAbsolute())
     948                    baseFile = new File(_manager.getDataDir(), baseData);
    926949                String announceURL = req.getParameter("announceURL");
    927950                // make the user add a tracker on the config form now
     
    9831006                        // FIXME is the storage going to stay around thanks to the info reference?
    9841007                        // now add it, but don't automatically start it
    985                         _manager.addTorrent(info, s.getBitField(), torrentFile.getAbsolutePath(), true);
     1008                        _manager.addTorrent(info, s.getBitField(), torrentFile.getAbsolutePath(), baseFile, true);
    9861009                        _manager.addMessage(_("Torrent created for \"{0}\"", baseFile.getName()) + ": " + torrentFile.getAbsolutePath());
    9871010                        if (announceURL != null && !_manager.util().getOpenTrackers().contains(announceURL))
     
    17841807        //out.write("From file: <input type=\"file\" name=\"newFile\" size=\"50\" value=\"" + newFile + "\" /><br>\n");
    17851808        out.write(_("Data to seed"));
    1786         out.write(":<td><code>" + _manager.getDataDir().getAbsolutePath() + File.separatorChar
    1787                   + "</code><input type=\"text\" name=\"baseFile\" size=\"58\" value=\"" + baseFile
     1809        out.write(":<td>"
     1810                  + "<input type=\"text\" name=\"baseFile\" size=\"58\" value=\"" + baseFile
    17881811                  + "\" spellcheck=\"false\" title=\"");
    1789         out.write(_("File or directory to seed (must be within the specified path)"));
     1812        out.write(_("File or directory to seed (full path or within the directory {0} )",
     1813                    _manager.getDataDir().getAbsolutePath() + File.separatorChar));
    17901814        out.write("\" ><tr><td>\n");
    17911815        out.write(_("Trackers"));
     
    22312255
    22322256        public int compare(File l, File r) {
    2233             if (l.isDirectory() && !r.isDirectory())
     2257            boolean ld = l.isDirectory();
     2258            boolean rd = r.isDirectory();
     2259            if (ld && !rd)
    22342260                return -1;
    2235             if (r.isDirectory() && !l.isDirectory())
     2261            if (rd && !ld)
    22362262                return 1;
    22372263            return Collator.getInstance().compare(l.getName(), r.getName());
     
    22622288     *
    22632289     * Get the resource list as a HTML directory listing.
    2264      * @param r The Resource
     2290     * @param xxxr The Resource unused
    22652291     * @param base The base URL
    22662292     * @param parent True if the parent directory should be included
     
    22692295     * @since 0.7.14
    22702296     */
    2271     private String getListHTML(File r, String base, boolean parent, Map<String, String[]> postParams)
     2297    private String getListHTML(File xxxr, String base, boolean parent, Map<String, String[]> postParams)
    22722298        throws IOException
    22732299    {
    2274         File[] ls = null;
    2275         if (r.isDirectory()) {
    2276             ls = r.listFiles();
    2277             Arrays.sort(ls, new ListingComparator());
    2278         }  // if r is not a directory, we are only showing torrent info section
    2279        
    22802300        String title = decodePath(base);
    22812301        String cpath = _contextPath + '/';
     
    22852305        // Get the snark associated with this directory
    22862306        String torrentName;
     2307        String pathInTorrent;
    22872308        int slash = title.indexOf('/');
    2288         if (slash > 0)
     2309        if (slash > 0) {
    22892310            torrentName = title.substring(0, slash);
    2290         else
     2311            pathInTorrent = title.substring(slash);
     2312        } else {
    22912313            torrentName = title;
     2314            pathInTorrent = "/";
     2315        }
    22922316        Snark snark = _manager.getTorrentByBaseName(torrentName);
    22932317
    22942318        if (snark != null && postParams != null) {
    22952319            // caller must P-R-G
    2296             savePriorities(snark, postParams);
     2320            String[] val = postParams.get("nonce");
     2321            if (val != null) {
     2322                String nonce = val[0];
     2323                if (String.valueOf(_nonce).equals(nonce))
     2324                    savePriorities(snark, postParams);
     2325                else
     2326                    _manager.addMessage("Please retry form submission (bad nonce)");
     2327            }
    22972328            return null;
    22982329        }
    22992330
     2331        File r;
     2332        if (snark != null) {
     2333            Storage storage = snark.getStorage();
     2334            if (storage != null) {
     2335                File sbase = storage.getBase();
     2336                if (pathInTorrent.equals("/"))
     2337                    r = sbase;
     2338                else
     2339                    r = new File(sbase, pathInTorrent);
     2340            } else {
     2341                // magnet, dummy
     2342                r = new File("");
     2343            }
     2344        } else {
     2345            // dummy
     2346            r = new File("");
     2347        }
    23002348        StringBuilder buf=new StringBuilder(4096);
    23012349        buf.append(DOCTYPE).append("<HTML><HEAD><TITLE>");
     
    23072355        buf.append("</TITLE>").append(HEADER_A).append(_themePath).append(HEADER_B).append("<link rel=\"shortcut icon\" href=\"" + _themePath + "favicon.ico\">" +
    23082356             "</HEAD><BODY>\n<center><div class=\"snarknavbar\"><a href=\"").append(_contextPath).append("/\" title=\"Torrents\"");
    2309         buf.append(" class=\"snarkRefresh\"><img alt=\"\" border=\"0\" src=\"" + _imgPath + "arrow_refresh.png\">&nbsp;&nbsp;");
     2357        buf.append(" class=\"snarkRefresh\"><img alt=\"\" border=\"0\" src=\"").append(_imgPath).append("arrow_refresh.png\">&nbsp;&nbsp;");
    23102358        if (_contextName.equals(DEFAULT_NAME))
    23112359            buf.append(_("I2PSnark"));
     
    23162364        if (parent)  // always true
    23172365            buf.append("<div class=\"page\"><div class=\"mainsection\">");
    2318         boolean showPriority = ls != null && snark != null && snark.getStorage() != null && !snark.getStorage().complete();
    2319         if (showPriority)
     2366        boolean showPriority = snark != null && snark.getStorage() != null && !snark.getStorage().complete() &&
     2367                               r.isDirectory();
     2368        if (showPriority) {
    23202369            buf.append("<form action=\"").append(base).append("\" method=\"POST\">\n");
     2370            buf.append("<input type=\"hidden\" name=\"nonce\" value=\"").append(_nonce).append("\" >\n");
     2371        }
    23212372        if (snark != null) {
    23222373            // first table - torrent info
     
    23312382            String baseName = urlEncode((new File(fullPath)).getName());
    23322383            buf.append("<tr><td>")
    2333                .append("<img alt=\"\" border=\"0\" src=\"" + _imgPath + "file.png\" >&nbsp;<b>")
     2384               .append("<img alt=\"\" border=\"0\" src=\"").append(_imgPath).append("file.png\" >&nbsp;<b>")
    23342385               .append(_("Torrent file"))
    23352386               .append(":</b> <a href=\"").append(_contextPath).append('/').append(baseName).append("\">")
    23362387               .append(fullPath)
    23372388               .append("</a></td></tr>\n");
     2389            buf.append("<tr><td>")
     2390               .append("<img alt=\"\" border=\"0\" src=\"").append(_imgPath).append("file.png\" >&nbsp;<b>")
     2391               .append(_("Data location"))
     2392               .append(":</b> ")
     2393               .append(urlEncode(snark.getStorage().getBase().getPath()))
     2394               .append("</td></tr>\n");
    23382395
    23392396            String announce = null;
     
    24362493
    24372494            buf.append("<tr><td>")
    2438                .append("<img alt=\"\" border=\"0\" src=\"" + _imgPath + "size.png\" >&nbsp;<b>")
     2495               .append("<img alt=\"\" border=\"0\" src=\"").append(_imgPath).append("size.png\" >&nbsp;<b>")
    24392496               .append(_("Size"))
    24402497               .append(":</b> ")
     
    24432500            double completion = (pieces - snark.getNeeded()) / (double) pieces;
    24442501            if (completion < 1.0)
    2445                 buf.append("&nbsp;<img alt=\"\" border=\"0\" src=\"" + _imgPath + "head_rx.png\" >&nbsp;<b>")
     2502                buf.append("&nbsp;<img alt=\"\" border=\"0\" src=\"").append(_imgPath).append("head_rx.png\" >&nbsp;<b>")
    24462503                   .append(_("Completion"))
    24472504                   .append(":</b> ")
    24482505                   .append((new DecimalFormat("0.00%")).format(completion));
    24492506            else
    2450                 buf.append("&nbsp;<img alt=\"\" border=\"0\" src=\"" + _imgPath + "head_rx.png\" >&nbsp;")
     2507                buf.append("&nbsp;<img alt=\"\" border=\"0\" src=\"").append(_imgPath).append("head_rx.png\" >&nbsp;")
    24512508                   .append(_("Complete"));
    24522509            // else unknown
    24532510            long needed = snark.getNeededLength();
    24542511            if (needed > 0)
    2455                 buf.append("&nbsp;<img alt=\"\" border=\"0\" src=\"" + _imgPath + "head_rx.png\" >&nbsp;<b>")
     2512                buf.append("&nbsp;<img alt=\"\" border=\"0\" src=\"").append(_imgPath).append("head_rx.png\" >&nbsp;<b>")
    24562513                   .append(_("Remaining"))
    24572514                   .append(":</b> ")
     
    24602517                List<List<String>> files = meta.getFiles();
    24612518                int fileCount = files != null ? files.size() : 1;
    2462                 buf.append("&nbsp;<img alt=\"\" border=\"0\" src=\"" + _imgPath + "file.png\" >&nbsp;<b>")
     2519                buf.append("&nbsp;<img alt=\"\" border=\"0\" src=\"").append(_imgPath).append("file.png\" >&nbsp;<b>")
    24632520                   .append(_("Files"))
    24642521                   .append(":</b> ")
    24652522                   .append(fileCount);
    24662523            }
    2467             buf.append("&nbsp;<img alt=\"\" border=\"0\" src=\"" + _imgPath + "file.png\" >&nbsp;<b>")
     2524            buf.append("&nbsp;<img alt=\"\" border=\"0\" src=\"").append(_imgPath).append("file.png\" >&nbsp;<b>")
    24682525               .append(_("Pieces"))
    24692526               .append(":</b> ")
    24702527               .append(pieces);
    2471             buf.append("&nbsp;<img alt=\"\" border=\"0\" src=\"" + _imgPath + "file.png\" >&nbsp;<b>")
     2528            buf.append("&nbsp;<img alt=\"\" border=\"0\" src=\"").append(_imgPath).append("file.png\" >&nbsp;<b>")
    24722529               .append(_("Piece size"))
    24732530               .append(":</b> ")
     
    24822539        }
    24832540        buf.append("</table>\n");
     2541
     2542        if (snark != null && !r.exists()) {
     2543            // fixup TODO
     2544            buf.append("<p>Does not exist<br>resource=\"").append(r.toString())
     2545               .append("\"<br>base=\"").append(base)
     2546               .append("\"<br>torrent=\"").append(torrentName)
     2547               .append("\"</p></div></div></BODY></HTML>");
     2548            return buf.toString();
     2549        }
     2550
     2551        File[] ls = null;
     2552        if (r.isDirectory()) {
     2553            ls = r.listFiles();
     2554            Arrays.sort(ls, new ListingComparator());
     2555        }  // if r is not a directory, we are only showing torrent info section
     2556       
    24842557        if (ls == null) {
    24852558            // We are only showing the torrent info section
     
    24922565        buf.append("<tr>\n")
    24932566           .append("<th colspan=2>")
    2494            .append("<img border=\"0\" src=\"" + _imgPath + "file.png\" title=\"")
     2567           .append("<img border=\"0\" src=\"").append(_imgPath).append("file.png\" title=\"")
    24952568           .append(_("Directory"))
    24962569           .append(": ")
     
    25002573           .append("\"></th>\n");
    25012574        buf.append("<th align=\"right\">")
    2502            .append("<img border=\"0\" src=\"" + _imgPath + "size.png\" title=\"")
     2575           .append("<img border=\"0\" src=\"").append(_imgPath).append("size.png\" title=\"")
    25032576           .append(_("Size"))
    25042577           .append("\" alt=\"")
     
    25062579           .append("\"></th>\n");
    25072580        buf.append("<th class=\"headerstatus\">")
    2508            .append("<img border=\"0\" src=\"" + _imgPath + "status.png\" title=\"")
     2581           .append("<img border=\"0\" src=\"").append(_imgPath).append("status.png\" title=\"")
    25092582           .append(_("Status"))
    25102583           .append("\" alt=\"")
     
    25132586        if (showPriority)
    25142587            buf.append("<th class=\"headerpriority\">")
    2515                .append("<img border=\"0\" src=\"" + _imgPath + "priority.png\" title=\"")
     2588               .append("<img border=\"0\" src=\"").append(_imgPath).append("priority.png\" title=\"")
    25162589               .append(_("Priority"))
    25172590               .append("\" alt=\"")
     
    25212594        buf.append("<tr><td colspan=\"" + (showPriority ? '5' : '4') + "\" class=\"ParentDir\"><A HREF=\"");
    25222595        buf.append(addPaths(base,"../"));
    2523         buf.append("\"><img alt=\"\" border=\"0\" src=\"" + _imgPath + "up.png\"> ")
     2596        buf.append("\"><img alt=\"\" border=\"0\" src=\"").append(_imgPath).append("up.png\"> ")
    25242597           .append(_("Up to higher level directory"))
    25252598           .append("</A></td></tr>\n");
     
    25452618            String status = "";
    25462619            long length = item.length();
     2620            int priority = 0;
    25472621            if (item.isDirectory()) {
    25482622                complete = true;
     
    25552629                } else {
    25562630                    Storage storage = snark.getStorage();
    2557                     try {
    2558                         File f = item;
    2559                             long remaining = storage.remaining(f.getCanonicalPath());
     2631
     2632                            long remaining = storage.remaining(item);
    25602633                            if (remaining < 0) {
    25612634                                complete = true;
     
    25652638                                status = toImg("tick") + ' ' + _("Complete");
    25662639                            } else {
    2567                                 int priority = storage.getPriority(f.getCanonicalPath());
     2640                                priority = storage.getPriority(item);
    25682641                                if (priority < 0)
    25692642                                    status = toImg("cancel");
     
    25762649                                         " (" + DataHelper.formatSize2(remaining) + "B " + _("remaining") + ")";
    25772650                            }
    2578                     } catch (IOException ioe) {
    2579                         status = "Not a file? " + ioe;
    2580                     }
     2651
    25812652                }
    25822653            }
     
    26182689            if (showPriority) {
    26192690                buf.append("<td class=\"priority\">");
    2620                 File f = item;
    26212691                if ((!complete) && (!item.isDirectory())) {
    2622                     int pri = snark.getStorage().getPriority(f.getCanonicalPath());
    2623                     buf.append("<input type=\"radio\" value=\"5\" name=\"pri.").append(f.getCanonicalPath()).append("\" ");
    2624                     if (pri > 0)
     2692                    buf.append("<input type=\"radio\" value=\"5\" name=\"pri.").append(item).append("\" ");
     2693                    if (priority > 0)
    26252694                        buf.append("checked=\"true\"");
    26262695                    buf.append('>').append(_("High"));
    26272696
    2628                     buf.append("<input type=\"radio\" value=\"0\" name=\"pri.").append(f.getCanonicalPath()).append("\" ");
    2629                     if (pri == 0)
     2697                    buf.append("<input type=\"radio\" value=\"0\" name=\"pri.").append(item).append("\" ");
     2698                    if (priority == 0)
    26302699                        buf.append("checked=\"true\"");
    26312700                    buf.append('>').append(_("Normal"));
    26322701
    2633                     buf.append("<input type=\"radio\" value=\"-9\" name=\"pri.").append(f.getCanonicalPath()).append("\" ");
    2634                     if (pri < 0)
     2702                    buf.append("<input type=\"radio\" value=\"-9\" name=\"pri.").append(item).append("\" ");
     2703                    if (priority < 0)
    26352704                        buf.append("checked=\"true\"");
    26362705                    buf.append('>').append(_("Skip"));
     
    27042773        else if (plc.endsWith(".iso"))
    27052774            icon = "cd";
     2775        else if (mime.equals("application/x-bittorrent"))
     2776            icon = "magnet";
    27062777        else
    27072778            icon = "page_white";
     
    27282799            if (key.startsWith("pri.")) {
    27292800                try {
    2730                     String file = key.substring(4);
     2801                    File file = new File(key.substring(4));
    27312802                    String val = entry.getValue()[0];   // jetty arrays
    27322803                    int pri = Integer.parseInt(val);
     
    27372808        }
    27382809         snark.updatePiecePriorities();
    2739         _manager.saveTorrentStatus(snark.getMetaInfo(), storage.getBitField(), storage.getFilePriorities());
     2810        _manager.saveTorrentStatus(snark.getMetaInfo(), storage.getBitField(), storage.getFilePriorities(), storage.getBase());
    27402811    }
    27412812}
  • apps/i2psnark/mime.properties

    r4ba40b3 rc9ce1751  
    2525sud     = application/zip
    2626tbz     = application/x-bzip2
     27torrent = application/x-bittorrent
    2728txt     = text/plain
    2829war     = application/java-archive
Note: See TracChangeset for help on using the changeset viewer.