Changeset 3b9549c


Ignore:
Timestamp:
Jun 21, 2014 1:02:22 PM (6 years ago)
Author:
zzz <zzz@…>
Branches:
master
Children:
c987a97
Parents:
2dcc9b7a (diff), 47712a3 (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' (head 1f9b91f318a0f2369243844a3cf7f485528492d7)

to branch 'i2p.i2p.zzz.snarkconfig' (head 37b27b6d354d62487294fd9276504b98a23f1057)

Location:
apps/i2psnark
Files:
8 edited

Legend:

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

    r2dcc9b7a r3b9549c  
    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

    r2dcc9b7a r3b9549c  
    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

    r2dcc9b7a r3b9549c  
    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

    r2dcc9b7a r3b9549c  
    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);
     
    329345    }
    330346
     347    /**
     348     *  Migrate the old flat config file to the new config dir
     349     *  containing the config file minus the per-torrent entries,
     350     *  the dht file, and 16 subdirs for per-torrent config files
     351     *  Caller must synch.
     352     *
     353     *  @return the new config directory, non-null
     354     *  @throws RuntimeException on creation fail
     355     *  @since 0.9.11
     356     */
     357    private File migrateConfig(File oldFile) {
     358        File dir = new SecureDirectory(oldFile + CONFIG_DIR_SUFFIX);
     359        if ((!dir.exists()) && (!dir.mkdirs())) {
     360            _log.error("Error creating I2PSnark config dir " + dir);
     361            throw new RuntimeException("Error creating I2PSnark config dir " + dir);
     362        }
     363        // move the DHT file as-is
     364        String oldName = oldFile.toString();
     365        if (oldName.endsWith(CONFIG_FILE_SUFFIX)) {
     366            String oldDHT = oldName.replace(CONFIG_FILE_SUFFIX, KRPC.DHT_FILE_SUFFIX);
     367            File oldDHTFile = new File(oldDHT);
     368            if (oldDHTFile.exists()) {
     369                File newDHTFile = new File(dir, "i2psnark" + KRPC.DHT_FILE_SUFFIX);
     370                FileUtil.rename(oldDHTFile, newDHTFile);
     371            }
     372        }
     373        if (!oldFile.exists())
     374            return dir;
     375        Properties oldProps = new Properties();
     376        try {
     377            DataHelper.loadProps(oldProps, oldFile);
     378            // a good time to fix this ancient typo
     379            String auto = (String) oldProps.remove(PROP_OLD_AUTO_START);
     380            if (auto != null)
     381                oldProps.setProperty(PROP_AUTO_START, auto);
     382        } catch (IOException ioe) {
     383           _log.error("Error loading I2PSnark config " + oldFile, ioe);
     384           return dir;
     385        }
     386        // Gather the props for each torrent, removing them from config
     387        // old b64 of hash as key
     388        Map<String, Properties> configs = new HashMap<String, Properties>(16);
     389        for (Iterator<Map.Entry<Object, Object>> iter = oldProps.entrySet().iterator(); iter.hasNext(); ) {
     390            Map.Entry<Object, Object> e = iter.next();
     391            String k = (String) e.getKey();
     392            if (k.startsWith(PROP_META_PREFIX)) {
     393                iter.remove();
     394                String v = (String) e.getValue();
     395                try {
     396                    k = k.substring(PROP_META_PREFIX.length());
     397                    String h = k.substring(0, 28);  // length of b64 of 160 bit infohash
     398                    k = k.substring(29); // skip '.'
     399                    Properties tprops = configs.get(h);
     400                    if (tprops == null) {
     401                        tprops = new OrderedProperties();
     402                        configs.put(h, tprops);
     403                    }
     404                    if (k.equals(PROP_META_BITFIELD)) {
     405                        // old config was timestamp,bitfield; split them
     406                        int comma = v.indexOf(',');
     407                        if (comma > 0 && v.length() > comma + 1) {
     408                            tprops.put(PROP_META_STAMP, v.substring(0, comma));
     409                            tprops.put(PROP_META_BITFIELD, v.substring(comma + 1));
     410                        } else {
     411                            // timestamp only??
     412                            tprops.put(PROP_META_STAMP, v);
     413                        }
     414                    } else {
     415                        tprops.put(k, v);
     416                    }
     417                } catch (IndexOutOfBoundsException ioobe) {
     418                    continue;
     419                }
     420            }
     421        }
     422        // Now make a config file for each torrent
     423        for (Map.Entry<String, Properties> e : configs.entrySet()) {
     424            String b64 = e.getKey();
     425            Properties props = e.getValue();
     426            if (props.isEmpty())
     427                continue;
     428            b64 = b64.replace('$', '=');
     429            byte[] ih = Base64.decode(b64);
     430            if (ih == null || ih.length != 20)
     431                continue;
     432            File cfg = configFile(dir, ih);
     433            if (!cfg.exists()) {
     434                File subdir = cfg.getParentFile();
     435                if (!subdir.exists())
     436                    subdir.mkdirs();
     437                try {
     438                    DataHelper.storeProps(props, cfg);
     439                } catch (IOException ioe) {
     440                    _log.error("Error storing I2PSnark config " + cfg, ioe);
     441                }
     442            }
     443        }
     444        // now store in new location, minus the zmeta entries
     445        File newFile = new File(dir, CONFIG_FILE);
     446        Properties newProps = new OrderedProperties();
     447        newProps.putAll(oldProps);
     448        try {
     449            DataHelper.storeProps(newProps, newFile);
     450        } catch (IOException ioe) {
     451            _log.error("Error storing I2PSnark config " + newFile, ioe);
     452            return dir;
     453        }
     454        oldFile.delete();
     455        if (_log.shouldLog(Log.WARN))
     456            _log.warn("Config migrated from " + oldFile + " to " + dir);
     457        return dir;
     458    }
     459
     460    /**
     461     *  The config for a torrent
     462     *  @return non-null, possibly empty
     463     *  @since 0.9.11
     464     */
     465    private Properties getConfig(Snark snark) {
     466        return getConfig(snark.getInfoHash());
     467    }
     468
     469    /**
     470     *  The config for a torrent
     471     *  @param ih 20-byte infohash
     472     *  @return non-null, possibly empty
     473     *  @since 0.9.11
     474     */
     475    private Properties getConfig(byte[] ih) {
     476        Properties rv = new OrderedProperties();
     477        File conf = configFile(_configDir, ih);
     478        synchronized(_configLock) {  // one lock for all
     479            try {
     480                DataHelper.loadProps(rv, conf);
     481            } catch (IOException ioe) {}
     482        }
     483        return rv;
     484    }
     485
     486    /**
     487     *  The config file for a torrent
     488     *  @param confDir the config directory
     489     *  @param ih 20-byte infohash
     490     *  @since 0.9.11
     491     */
     492    private static File configFile(File confDir, byte[] ih) {
     493        String hex = I2PSnarkUtil.toHex(ih);
     494        File subdir = new SecureDirectory(confDir, SUBDIR_PREFIX + B64.charAt((ih[0] >> 2) & 0x3f));
     495        return new File(subdir, hex + CONFIG_FILE_SUFFIX);
     496    }
     497
    331498    /** null to set initial defaults */
    332499    public void loadConfig(String filename) {
     500        synchronized(_configLock) {
     501            locked_loadConfig(filename);
     502        }
     503    }
     504
     505    /** null to set initial defaults */
     506    private void locked_loadConfig(String filename) {
    333507        if (_config == null)
    334508            _config = new OrderedProperties();
     
    337511            if (!cfg.isAbsolute())
    338512                cfg = new File(_context.getConfigDir(), filename);
    339             _configFile = cfg;
    340             if (cfg.exists()) {
     513            _configDir = migrateConfig(cfg);
     514            _configFile = new File(_configDir, CONFIG_FILE);
     515            if (_configFile.exists()) {
    341516                try {
    342                     DataHelper.loadProps(_config, cfg);
     517                    DataHelper.loadProps(_config, _configFile);
    343518                } catch (IOException ioe) {
    344                    _log.error("Error loading I2PSnark config '" + filename + "'", ioe);
     519                   _log.error("Error loading I2PSnark config " + _configFile, ioe);
    345520                }
    346521            }
     
    376551        updateConfig();
    377552    }
     553
    378554    /**
    379555     * Get current theme.
     
    490666     */
    491667    public void updateConfig(String dataDir, boolean filesPublic, boolean autoStart, String refreshDelay,
     668                             String startDelay, String pageSize, String seedPct, String eepHost,
     669                             String eepPort, String i2cpHost, String i2cpPort, String i2cpOpts,
     670                             String upLimit, String upBW, boolean useOpenTrackers, boolean useDHT, String theme) {
     671        synchronized(_configLock) {
     672            locked_updateConfig(dataDir, filesPublic, autoStart, refreshDelay,
     673                                startDelay,  pageSize,  seedPct,  eepHost,
     674                                eepPort,  i2cpHost,  i2cpPort,  i2cpOpts,
     675                                upLimit,  upBW, useOpenTrackers, useDHT,  theme);
     676        }
     677    }
     678
     679    private void locked_updateConfig(String dataDir, boolean filesPublic, boolean autoStart, String refreshDelay,
    492680                             String startDelay, String pageSize, String seedPct, String eepHost,
    493681                             String eepPort, String i2cpHost, String i2cpPort, String i2cpOpts,
     
    8241012    public void saveConfig() {
    8251013        try {
    826             synchronized (_configFile) {
     1014            synchronized (_configLock) {
    8271015                DataHelper.storeProps(_config, _configFile);
    8281016            }
     
    8321020    }
    8331021   
    834     public Properties getConfig() { return _config; }
    835    
    836     /** @since Jetty 7 */
    837     public String getConfigFilename() {
    838         return _configFile.getAbsolutePath();
    839     }
    840 
    8411022    /** hardcoded for sanity.  perhaps this should be customizable, for people who increase their ulimit, etc. */
    8421023    public static final int MAX_FILES_PER_TORRENT = 512;
     
    8961077    /**
    8971078     *  Caller must verify this torrent is not already added.
     1079     *
     1080     *  @param filename the absolute path to save the metainfo to, generally ending in ".torrent"
     1081     *  @param baseFile may be null, if so look in rootDataDir
    8981082     *  @throws RuntimeException via Snark.fatal()
    8991083     */
    900     private void addTorrent(String filename) { addTorrent(filename, false); }
     1084    private void addTorrent(String filename) {
     1085        addTorrent(filename, null, false);
     1086    }
    9011087
    9021088    /**
    9031089     *  Caller must verify this torrent is not already added.
     1090     *
     1091     *  @param filename the absolute path to save the metainfo to, generally ending in ".torrent"
     1092     *  @param baseFile may be null, if so look in rootDataDir
    9041093     *  @throws RuntimeException via Snark.fatal()
    9051094     */
    906     private void addTorrent(String filename, boolean dontAutoStart) {
     1095    private void addTorrent(String filename, File baseFile, boolean dontAutoStart) {
    9071096        if ((!dontAutoStart) && !_util.connected()) {
    9081097            addMessage(_("Connecting to I2P"));
     
    9851174                        // TODO load saved closest DHT nodes and pass to the Snark ?
    9861175                        // This may take a LONG time
     1176                        if (baseFile == null)
     1177                            baseFile = getSavedBaseFile(info.getInfoHash());
     1178                        if (_log.shouldLog(Log.INFO))
     1179                            _log.info("New Snark, torrent: " + filename + " base: " + baseFile);
    9871180                        torrent = new Snark(_util, filename, null, -1, null, null, this,
    9881181                                            _peerCoordinatorSet, _connectionAcceptor,
    989                                             false, dataDir.getPath());
     1182                                            false, dataDir.getPath(), baseFile);
    9901183                        loadSavedFilePriorities(torrent);
    9911184                        synchronized (_snarks) {
     
    11301323     * This may take a LONG time to create or check the storage.
    11311324     *
     1325     * Called from servlet.
     1326     *
    11321327     * @param metainfo the metainfo for the torrent
    11331328     * @param bitfield the current completion status of the torrent
    11341329     * @param filename the absolute path to save the metainfo to, generally ending in ".torrent", which is also the name of the torrent
    11351330     *                 Must be a filesystem-safe name.
     1331     * @param baseFile may be null, if so look in rootDataDir
    11361332     * @throws RuntimeException via Snark.fatal()
    11371333     * @since 0.8.4
    11381334     */
    1139     public void addTorrent(MetaInfo metainfo, BitField bitfield, String filename, boolean dontAutoStart) throws IOException {
     1335    public void addTorrent(MetaInfo metainfo, BitField bitfield, String filename, File baseFile, boolean dontAutoStart) throws IOException {
    11401336        // prevent interference by DirMonitor
    11411337        synchronized (_snarks) {
     
    11461342            }
    11471343            // so addTorrent won't recheck
    1148             saveTorrentStatus(metainfo, bitfield, null); // no file priorities
     1344            saveTorrentStatus(metainfo, bitfield, null, baseFile); // no file priorities
    11491345            try {
    11501346                locked_writeMetaInfo(metainfo, filename, areFilesPublic());
    11511347                // hold the lock for a long time
    1152                 addTorrent(filename, dontAutoStart);
     1348                addTorrent(filename, baseFile, dontAutoStart);
    11531349            } catch (IOException ioe) {
    11541350                addMessage(_("Failed to copy torrent file to {0}", filename));
     
    12231419     */
    12241420    public long getSavedTorrentTime(Snark snark) {
    1225         byte[] ih = snark.getInfoHash();
    1226         String infohash = Base64.encode(ih);
    1227         infohash = infohash.replace('=', '$');
    1228         String time = _config.getProperty(PROP_META_PREFIX + infohash + PROP_META_BITFIELD_SUFFIX);
     1421        Properties config = getConfig(snark);
     1422        String time = config.getProperty(PROP_META_STAMP);
    12291423        if (time == null)
    12301424            return 0;
    1231         int comma = time.indexOf(',');
    1232         if (comma <= 0)
    1233             return 0;
    1234         time = time.substring(0, comma);
    12351425        try { return Long.parseLong(time); } catch (NumberFormatException nfe) {}
    12361426        return 0;
     
    12461436        if (metainfo == null)
    12471437            return null;
    1248         byte[] ih = snark.getInfoHash();
    1249         String infohash = Base64.encode(ih);
    1250         infohash = infohash.replace('=', '$');
    1251         String bf = _config.getProperty(PROP_META_PREFIX + infohash + PROP_META_BITFIELD_SUFFIX);
     1438        Properties config = getConfig(snark);
     1439        String bf = config.getProperty(PROP_META_BITFIELD);
    12521440        if (bf == null)
    12531441            return null;
    1254         int comma = bf.indexOf(',');
    1255         if (comma <= 0)
    1256             return null;
    1257         bf = bf.substring(comma + 1).trim();
    12581442        int len = metainfo.getPieces();
    12591443        if (bf.equals(".")) {
     
    12821466        if (metainfo.getFiles() == null)
    12831467            return;
    1284         byte[] ih = snark.getInfoHash();
    1285         String infohash = Base64.encode(ih);
    1286         infohash = infohash.replace('=', '$');
    1287         String pri = _config.getProperty(PROP_META_PREFIX + infohash + PROP_META_PRIORITY_SUFFIX);
     1468        Properties config = getConfig(snark);
     1469        String pri = config.getProperty(PROP_META_PRIORITY);
    12881470        if (pri == null)
    12891471            return;
     
    13001482        storage.setFilePriorities(rv);
    13011483    }
     1484
     1485    /**
     1486     * Get the base location for a torrent from the config file.
     1487     * @return File or null, doesn't necessarily exist
     1488     * @since 0.9.11
     1489     */
     1490    public File getSavedBaseFile(byte[] ih) {
     1491        Properties config = getConfig(ih);
     1492        String base = config.getProperty(PROP_META_BASE);
     1493        if (base == null)
     1494            return null;
     1495        return new File(base);
     1496    }
    13021497   
    13031498    /**
    13041499     * Save the completion status of a torrent and the current time in the config file
    1305      * in the form "i2psnark.zmeta.$base64infohash=$time,$base64bitfield".
    1306      * The config file property key is appended with the Base64 of the infohash,
    1307      * with the '=' changed to '$' since a key can't contain '='.
     1500     * for that torrent.
    13081501     * The time is a standard long converted to string.
    13091502     * The status is either a bitfield converted to Base64 or "." for a completed
     
    13121505     * @param bitfield non-null
    13131506     * @param priorities may be null
    1314      */
    1315     public void saveTorrentStatus(MetaInfo metainfo, BitField bitfield, int[] priorities) {
     1507     * @param base may be null
     1508     */
     1509    public void saveTorrentStatus(MetaInfo metainfo, BitField bitfield, int[] priorities, File base) {
     1510        synchronized (_configLock) {
     1511            locked_saveTorrentStatus(metainfo, bitfield, priorities, base);
     1512        }
     1513    }
     1514
     1515    private void locked_saveTorrentStatus(MetaInfo metainfo, BitField bitfield, int[] priorities, File base) {
    13161516        byte[] ih = metainfo.getInfoHash();
    1317         String infohash = Base64.encode(ih);
    1318         infohash = infohash.replace('=', '$');
    1319         String now = "" + System.currentTimeMillis();
    13201517        String bfs;
    13211518        if (bitfield.complete()) {
     
    13251522          bfs = Base64.encode(bf);
    13261523        }
    1327         _config.setProperty(PROP_META_PREFIX + infohash + PROP_META_BITFIELD_SUFFIX, now + "," + bfs);
     1524        Properties config = getConfig(ih);
     1525        config.setProperty(PROP_META_STAMP, Long.toString(System.currentTimeMillis()));
     1526        config.setProperty(PROP_META_BITFIELD, bfs);
     1527        if (base != null)
     1528            config.setProperty(PROP_META_BASE, base.getAbsolutePath());
    13281529
    13291530        // now the file priorities
    1330         String prop = PROP_META_PREFIX + infohash + PROP_META_PRIORITY_SUFFIX;
    13311531        if (priorities != null) {
    13321532            boolean nonzero = false;
     
    13461546                        buf.append(',');
    13471547                }
    1348                 _config.setProperty(prop, buf.toString());
     1548                config.setProperty(PROP_META_PRIORITY, buf.toString());
    13491549            } else {
    1350                 _config.remove(prop);
     1550                config.remove(PROP_META_PRIORITY);
    13511551            }
    13521552        } else {
    1353             _config.remove(prop);
     1553            config.remove(PROP_META_PRIORITY);
    13541554        }
    13551555
    13561556        // TODO save closest DHT nodes too
    13571557
    1358         saveConfig();
    1359     }
    1360    
    1361     /**
    1362      * Remove the status of a torrent from the config file.
    1363      * This may help the config file from growing too big.
     1558        File conf = configFile(_configDir, ih);
     1559        File subdir = conf.getParentFile();
     1560        if (!subdir.exists())
     1561            subdir.mkdirs();
     1562        try {
     1563            DataHelper.storeProps(config, conf);
     1564        } catch (IOException ioe) {
     1565            _log.error("Unable to save the config to " + conf);
     1566        }
     1567    }
     1568   
     1569    /**
     1570     * Remove the status of a torrent by removing the config file.
    13641571     */
    13651572    public void removeTorrentStatus(MetaInfo metainfo) {
    13661573        byte[] ih = metainfo.getInfoHash();
    1367         String infohash = Base64.encode(ih);
    1368         infohash = infohash.replace('=', '$');
    1369         _config.remove(PROP_META_PREFIX + infohash + PROP_META_BITFIELD_SUFFIX);
    1370         _config.remove(PROP_META_PREFIX + infohash + PROP_META_PRIORITY_SUFFIX);
    1371         saveConfig();
     1574        File conf = configFile(_configDir, ih);
     1575        synchronized (_configLock) {
     1576            conf.delete();
     1577            File subdir = conf.getParentFile();
     1578            String[] files = subdir.list();
     1579            if (files != null && files.length == 0)
     1580                subdir.delete();
     1581        }
    13721582    }
    13731583   
     
    15691779        Storage storage = snark.getStorage();
    15701780        if (meta != null && storage != null)
    1571             saveTorrentStatus(meta, storage.getBitField(), storage.getFilePriorities());
     1781            saveTorrentStatus(meta, storage.getBitField(), storage.getFilePriorities(), storage.getBase());
    15721782    }
    15731783   
     
    15911801                return null;
    15921802            }
    1593             saveTorrentStatus(meta, storage.getBitField(), null); // no file priorities
     1803            saveTorrentStatus(meta, storage.getBitField(), null, storage.getBase()); // no file priorities
    15941804            // temp for addMessage() in case canonical throws
    15951805            String name = storage.getBaseName();
     
    16921902                    // Snark.fatal() throws a RuntimeException
    16931903                    // don't let one bad torrent kill the whole loop
    1694                     addTorrent(name, !shouldAutoStart());
     1904                    addTorrent(name, null, !shouldAutoStart());
    16951905                } catch (Exception e) {
    16961906                    addMessage(_("Error: Could not add the torrent {0}", name) + ": " + e);
  • apps/i2psnark/java/src/org/klomp/snark/Storage.java

    r2dcc9b7a r3b9549c  
    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
     
    524505          throw new IOException("Could not create file " + base);
    525506
    526         _torrentFiles.add(new TorrentFile(base, base, metainfo.getTotalLength()));
     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

    r2dcc9b7a r3b9549c  
    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/I2PSnarkServlet.java

    r2dcc9b7a r3b9549c  
    1919import java.util.Set;
    2020import java.util.TreeMap;
    21 import java.util.TreeSet;
    2221
    2322import javax.servlet.ServletConfig;
     
    191190        boolean isConfigure = "/configure".equals(path);
    192191        // index.jsp doesn't work, it is grabbed by the war handler before here
    193         if (!(path == null || path.equals("/") || path.equals("/index.jsp") || path.equals("/index.html") || path.equals("/_post") || isConfigure)) {
     192        if (!(path == null || path.equals("/") || path.equals("/index.jsp") ||
     193              path.equals("/index.html") || path.equals("/_post") || isConfigure)) {
    194194            if (path.endsWith("/")) {
    195195                // Listing of a torrent (torrent detail page)
     
    841841                                break;
    842842                            }
     843                            Storage storage = snark.getStorage();
     844                            if (storage == null)
     845                                break;
    843846                            // step 1 delete files
    844                             for (int i = 0; i < files.size(); i++) {
    845                                 // multifile torrents have the getFiles() return lists of lists of filenames, but
    846                                 // each of those lists just contain a single file afaict...
    847                                 File df = Storage.getFileFromNames(f, files.get(i));
     847                            for (File df : storage.getFiles()) {
    848848                                if (df.delete()) {
    849849                                    //_manager.addMessage(_("Data file deleted: {0}", df.getAbsolutePath()));
     
    852852                                }
    853853                            }
    854                             // step 2 make Set of dirs with reverse sort
    855                             Set<File> dirs = new TreeSet<File>(Collections.reverseOrder());
    856                             for (List<String> list : files) {
    857                                 for (int i = 1; i < list.size(); i++) {
    858                                     dirs.add(Storage.getFileFromNames(f, list.subList(0, i)));
    859                                 }
    860                             }
    861                             // step 3 delete dirs bottom-up
     854                            // step 2 delete dirs bottom-up
     855                            Set<File> dirs = storage.getDirectories();
     856                            if (_log.shouldLog(Log.INFO))
     857                                _log.info("Dirs to delete: " + DataHelper.toString(dirs));
     858                            boolean ok = false;
    862859                            for (File df : dirs) {
    863860                                if (df.delete()) {
     861                                    ok = true;
    864862                                    //_manager.addMessage(_("Data dir deleted: {0}", df.getAbsolutePath()));
    865863                                } else {
     864                                    ok = false;
    866865                                    _manager.addMessage(_("Directory could not be deleted: {0}", df.getAbsolutePath()));
    867866                                    if (_log.shouldLog(Log.WARN))
     
    869868                                }
    870869                            }
    871                             // step 4 delete base
    872                             if (f.delete()) {
    873                                 _manager.addMessage(_("Directory deleted: {0}", f.getAbsolutePath()));
    874                             } else {
    875                                 _manager.addMessage(_("Directory could not be deleted: {0}", f.getAbsolutePath()));
    876                                 if (_log.shouldLog(Log.WARN))
    877                                     _log.warn("Could not delete dir " + f);
    878                             }
     870                            // step 3 message for base (last one)
     871                            if (ok)
     872                                _manager.addMessage(_("Directory deleted: {0}", storage.getBase()));
    879873                            break;
    880874                        }
     
    915909            String baseData = req.getParameter("baseFile");
    916910            if (baseData != null && baseData.trim().length() > 0) {
    917                 File baseFile = new File(_manager.getDataDir(), baseData);
     911                File baseFile = new File(baseData.trim());
     912                if (!baseFile.isAbsolute())
     913                    baseFile = new File(_manager.getDataDir(), baseData);
    918914                String announceURL = req.getParameter("announceURL");
    919915                // make the user add a tracker on the config form now
     
    975971                        // FIXME is the storage going to stay around thanks to the info reference?
    976972                        // now add it, but don't automatically start it
    977                         _manager.addTorrent(info, s.getBitField(), torrentFile.getAbsolutePath(), true);
     973                        _manager.addTorrent(info, s.getBitField(), torrentFile.getAbsolutePath(), baseFile, true);
    978974                        _manager.addMessage(_("Torrent created for \"{0}\"", baseFile.getName()) + ": " + torrentFile.getAbsolutePath());
    979975                        if (announceURL != null && !_manager.util().getOpenTrackers().contains(announceURL))
     
    17761772        //out.write("From file: <input type=\"file\" name=\"newFile\" size=\"50\" value=\"" + newFile + "\" /><br>\n");
    17771773        out.write(_("Data to seed"));
    1778         out.write(":<td><code>" + _manager.getDataDir().getAbsolutePath() + File.separatorChar
    1779                   + "</code><input type=\"text\" name=\"baseFile\" size=\"58\" value=\"" + baseFile
     1774        out.write(":<td>"
     1775                  + "<input type=\"text\" name=\"baseFile\" size=\"58\" value=\"" + baseFile
    17801776                  + "\" spellcheck=\"false\" title=\"");
    1781         out.write(_("File or directory to seed (must be within the specified path)"));
     1777        out.write(_("File or directory to seed (full path or within the directory {0} )",
     1778                    _manager.getDataDir().getAbsolutePath() + File.separatorChar));
    17821779        out.write("\" ><tr><td>\n");
    17831780        out.write(_("Trackers"));
     
    22222219
    22232220        public int compare(File l, File r) {
    2224             if (l.isDirectory() && !r.isDirectory())
     2221            boolean ld = l.isDirectory();
     2222            boolean rd = r.isDirectory();
     2223            if (ld && !rd)
    22252224                return -1;
    2226             if (r.isDirectory() && !l.isDirectory())
     2225            if (rd && !ld)
    22272226                return 1;
    22282227            return Collator.getInstance().compare(l.getName(), r.getName());
     
    22632262        throws IOException
    22642263    {
    2265         File[] ls = null;
    2266         if (r.isDirectory()) {
    2267             ls = r.listFiles();
    2268             Arrays.sort(ls, new ListingComparator());
    2269         }  // if r is not a directory, we are only showing torrent info section
    2270        
    22712264        String title = decodePath(base);
    22722265        String cpath = _contextPath + '/';
     
    22852278        if (snark != null && postParams != null) {
    22862279            // caller must P-R-G
    2287             savePriorities(snark, postParams);
     2280            String[] val = postParams.get("nonce");
     2281            if (val != null) {
     2282                String nonce = val[0];
     2283                if (String.valueOf(_nonce).equals(nonce))
     2284                    savePriorities(snark, postParams);
     2285                else
     2286                    _manager.addMessage("Please retry form submission (bad nonce)");
     2287            }
    22882288            return null;
    22892289        }
     
    22982298        buf.append("</TITLE>").append(HEADER_A).append(_themePath).append(HEADER_B).append("<link rel=\"shortcut icon\" href=\"" + _themePath + "favicon.ico\">" +
    22992299             "</HEAD><BODY>\n<center><div class=\"snarknavbar\"><a href=\"").append(_contextPath).append("/\" title=\"Torrents\"");
    2300         buf.append(" class=\"snarkRefresh\"><img alt=\"\" border=\"0\" src=\"" + _imgPath + "arrow_refresh.png\">&nbsp;&nbsp;");
     2300        buf.append(" class=\"snarkRefresh\"><img alt=\"\" border=\"0\" src=\"").append(_imgPath).append("arrow_refresh.png\">&nbsp;&nbsp;");
    23012301        if (_contextName.equals(DEFAULT_NAME))
    23022302            buf.append(_("I2PSnark"));
     
    23072307        if (parent)  // always true
    23082308            buf.append("<div class=\"page\"><div class=\"mainsection\">");
    2309         boolean showPriority = ls != null && snark != null && snark.getStorage() != null && !snark.getStorage().complete();
    2310         if (showPriority)
     2309        boolean showPriority = snark != null && snark.getStorage() != null && !snark.getStorage().complete() &&
     2310                               r.isDirectory();
     2311        if (showPriority) {
    23112312            buf.append("<form action=\"").append(base).append("\" method=\"POST\">\n");
     2313            buf.append("<input type=\"hidden\" name=\"nonce\" value=\"").append(_nonce).append("\" >\n");
     2314        }
    23122315        if (snark != null) {
    23132316            // first table - torrent info
     
    23222325            String baseName = urlEncode((new File(fullPath)).getName());
    23232326            buf.append("<tr><td>")
    2324                .append("<img alt=\"\" border=\"0\" src=\"" + _imgPath + "file.png\" >&nbsp;<b>")
     2327               .append("<img alt=\"\" border=\"0\" src=\"").append(_imgPath).append("file.png\" >&nbsp;<b>")
    23252328               .append(_("Torrent file"))
    23262329               .append(":</b> <a href=\"").append(_contextPath).append('/').append(baseName).append("\">")
    23272330               .append(fullPath)
    23282331               .append("</a></td></tr>\n");
     2332            buf.append("<tr><td>")
     2333               .append("<img alt=\"\" border=\"0\" src=\"").append(_imgPath).append("file.png\" >&nbsp;<b>")
     2334               .append(_("Data location"))
     2335               .append(":</b> ")
     2336               .append(urlEncode(snark.getStorage().getBase().getPath()))
     2337               .append("</td></tr>\n");
    23292338
    23302339            String announce = null;
     
    24272436
    24282437            buf.append("<tr><td>")
    2429                .append("<img alt=\"\" border=\"0\" src=\"" + _imgPath + "size.png\" >&nbsp;<b>")
     2438               .append("<img alt=\"\" border=\"0\" src=\"").append(_imgPath).append("size.png\" >&nbsp;<b>")
    24302439               .append(_("Size"))
    24312440               .append(":</b> ")
     
    24342443            double completion = (pieces - snark.getNeeded()) / (double) pieces;
    24352444            if (completion < 1.0)
    2436                 buf.append("&nbsp;<img alt=\"\" border=\"0\" src=\"" + _imgPath + "head_rx.png\" >&nbsp;<b>")
     2445                buf.append("&nbsp;<img alt=\"\" border=\"0\" src=\"").append(_imgPath).append("head_rx.png\" >&nbsp;<b>")
    24372446                   .append(_("Completion"))
    24382447                   .append(":</b> ")
    24392448                   .append((new DecimalFormat("0.00%")).format(completion));
    24402449            else
    2441                 buf.append("&nbsp;<img alt=\"\" border=\"0\" src=\"" + _imgPath + "head_rx.png\" >&nbsp;")
     2450                buf.append("&nbsp;<img alt=\"\" border=\"0\" src=\"").append(_imgPath).append("head_rx.png\" >&nbsp;")
    24422451                   .append(_("Complete"));
    24432452            // else unknown
    24442453            long needed = snark.getNeededLength();
    24452454            if (needed > 0)
    2446                 buf.append("&nbsp;<img alt=\"\" border=\"0\" src=\"" + _imgPath + "head_rx.png\" >&nbsp;<b>")
     2455                buf.append("&nbsp;<img alt=\"\" border=\"0\" src=\"").append(_imgPath).append("head_rx.png\" >&nbsp;<b>")
    24472456                   .append(_("Remaining"))
    24482457                   .append(":</b> ")
     
    24512460                List<List<String>> files = meta.getFiles();
    24522461                int fileCount = files != null ? files.size() : 1;
    2453                 buf.append("&nbsp;<img alt=\"\" border=\"0\" src=\"" + _imgPath + "file.png\" >&nbsp;<b>")
     2462                buf.append("&nbsp;<img alt=\"\" border=\"0\" src=\"").append(_imgPath).append("file.png\" >&nbsp;<b>")
    24542463                   .append(_("Files"))
    24552464                   .append(":</b> ")
    24562465                   .append(fileCount);
    24572466            }
    2458             buf.append("&nbsp;<img alt=\"\" border=\"0\" src=\"" + _imgPath + "file.png\" >&nbsp;<b>")
     2467            buf.append("&nbsp;<img alt=\"\" border=\"0\" src=\"").append(_imgPath).append("file.png\" >&nbsp;<b>")
    24592468               .append(_("Pieces"))
    24602469               .append(":</b> ")
    24612470               .append(pieces);
    2462             buf.append("&nbsp;<img alt=\"\" border=\"0\" src=\"" + _imgPath + "file.png\" >&nbsp;<b>")
     2471            buf.append("&nbsp;<img alt=\"\" border=\"0\" src=\"").append(_imgPath).append("file.png\" >&nbsp;<b>")
    24632472               .append(_("Piece size"))
    24642473               .append(":</b> ")
     
    24732482        }
    24742483        buf.append("</table>\n");
     2484
     2485        if (snark != null && !r.exists()) {
     2486            // fixup TODO
     2487            buf.append("<p>Does not exist<br>resource=\"").append(r.toString())
     2488               .append("\"<br>base=\"").append(base)
     2489               .append("\"<br>torrent=\"").append(torrentName)
     2490               .append("\"</p></div></div></BODY></HTML>");
     2491            return buf.toString();
     2492        }
     2493
     2494        File[] ls = null;
     2495        if (r.isDirectory()) {
     2496            ls = r.listFiles();
     2497            Arrays.sort(ls, new ListingComparator());
     2498        }  // if r is not a directory, we are only showing torrent info section
     2499       
    24752500        if (ls == null) {
    24762501            // We are only showing the torrent info section
     
    24832508        buf.append("<tr>\n")
    24842509           .append("<th colspan=2>")
    2485            .append("<img border=\"0\" src=\"" + _imgPath + "file.png\" title=\"")
     2510           .append("<img border=\"0\" src=\"").append(_imgPath).append("file.png\" title=\"")
    24862511           .append(_("Directory"))
    24872512           .append(": ")
     
    24912516           .append("\"></th>\n");
    24922517        buf.append("<th align=\"right\">")
    2493            .append("<img border=\"0\" src=\"" + _imgPath + "size.png\" title=\"")
     2518           .append("<img border=\"0\" src=\"").append(_imgPath).append("size.png\" title=\"")
    24942519           .append(_("Size"))
    24952520           .append("\" alt=\"")
     
    24972522           .append("\"></th>\n");
    24982523        buf.append("<th class=\"headerstatus\">")
    2499            .append("<img border=\"0\" src=\"" + _imgPath + "status.png\" title=\"")
     2524           .append("<img border=\"0\" src=\"").append(_imgPath).append("status.png\" title=\"")
    25002525           .append(_("Status"))
    25012526           .append("\" alt=\"")
     
    25042529        if (showPriority)
    25052530            buf.append("<th class=\"headerpriority\">")
    2506                .append("<img border=\"0\" src=\"" + _imgPath + "priority.png\" title=\"")
     2531               .append("<img border=\"0\" src=\"").append(_imgPath).append("priority.png\" title=\"")
    25072532               .append(_("Priority"))
    25082533               .append("\" alt=\"")
     
    25122537        buf.append("<tr><td colspan=\"" + (showPriority ? '5' : '4') + "\" class=\"ParentDir\"><A HREF=\"");
    25132538        buf.append(addPaths(base,"../"));
    2514         buf.append("\"><img alt=\"\" border=\"0\" src=\"" + _imgPath + "up.png\"> ")
     2539        buf.append("\"><img alt=\"\" border=\"0\" src=\"").append(_imgPath).append("up.png\"> ")
    25152540           .append(_("Up to higher level directory"))
    25162541           .append("</A></td></tr>\n");
     
    25362561            String status = "";
    25372562            long length = item.length();
     2563            int priority = 0;
    25382564            if (item.isDirectory()) {
    25392565                complete = true;
     
    25462572                } else {
    25472573                    Storage storage = snark.getStorage();
    2548                     try {
    2549                         File f = item;
    2550                             long remaining = storage.remaining(f.getCanonicalPath());
     2574
     2575                            long remaining = storage.remaining(item);
    25512576                            if (remaining < 0) {
    25522577                                complete = true;
     
    25562581                                status = toImg("tick") + ' ' + _("Complete");
    25572582                            } else {
    2558                                 int priority = storage.getPriority(f.getCanonicalPath());
     2583                                priority = storage.getPriority(item);
    25592584                                if (priority < 0)
    25602585                                    status = toImg("cancel");
     
    25672592                                         " (" + DataHelper.formatSize2(remaining) + "B " + _("remaining") + ")";
    25682593                            }
    2569                     } catch (IOException ioe) {
    2570                         status = "Not a file? " + ioe;
    2571                     }
     2594
    25722595                }
    25732596            }
     
    26092632            if (showPriority) {
    26102633                buf.append("<td class=\"priority\">");
    2611                 File f = item;
    26122634                if ((!complete) && (!item.isDirectory())) {
    2613                     int pri = snark.getStorage().getPriority(f.getCanonicalPath());
    2614                     buf.append("<input type=\"radio\" value=\"5\" name=\"pri.").append(f.getCanonicalPath()).append("\" ");
    2615                     if (pri > 0)
     2635                    buf.append("<input type=\"radio\" value=\"5\" name=\"pri.").append(item).append("\" ");
     2636                    if (priority > 0)
    26162637                        buf.append("checked=\"true\"");
    26172638                    buf.append('>').append(_("High"));
    26182639
    2619                     buf.append("<input type=\"radio\" value=\"0\" name=\"pri.").append(f.getCanonicalPath()).append("\" ");
    2620                     if (pri == 0)
     2640                    buf.append("<input type=\"radio\" value=\"0\" name=\"pri.").append(item).append("\" ");
     2641                    if (priority == 0)
    26212642                        buf.append("checked=\"true\"");
    26222643                    buf.append('>').append(_("Normal"));
    26232644
    2624                     buf.append("<input type=\"radio\" value=\"-9\" name=\"pri.").append(f.getCanonicalPath()).append("\" ");
    2625                     if (pri < 0)
     2645                    buf.append("<input type=\"radio\" value=\"-9\" name=\"pri.").append(item).append("\" ");
     2646                    if (priority < 0)
    26262647                        buf.append("checked=\"true\"");
    26272648                    buf.append('>').append(_("Skip"));
     
    26952716        else if (plc.endsWith(".iso"))
    26962717            icon = "cd";
     2718        else if (mime.equals("application/x-bittorrent"))
     2719            icon = "magnet";
    26972720        else
    26982721            icon = "page_white";
     
    27192742            if (key.startsWith("pri.")) {
    27202743                try {
    2721                     String file = key.substring(4);
     2744                    File file = new File(key.substring(4));
    27222745                    String val = entry.getValue()[0];   // jetty arrays
    27232746                    int pri = Integer.parseInt(val);
     
    27282751        }
    27292752         snark.updatePiecePriorities();
    2730         _manager.saveTorrentStatus(snark.getMetaInfo(), storage.getBitField(), storage.getFilePriorities());
     2753        _manager.saveTorrentStatus(snark.getMetaInfo(), storage.getBitField(), storage.getFilePriorities(), storage.getBase());
    27312754    }
    27322755}
  • apps/i2psnark/mime.properties

    r2dcc9b7a r3b9549c  
    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.