From a80c34c1df06eabcd6725ca6324404fe7edcde4d Mon Sep 17 00:00:00 2001 From: zzz <zzz@mail.i2p> Date: Wed, 18 Sep 2013 15:41:10 +0000 Subject: [PATCH] * i2psnark: - Refactor Storage file data structures - Sort files when creating torrents --- .../java/src/org/klomp/snark/Storage.java | 518 ++++++++++-------- 1 file changed, 295 insertions(+), 223 deletions(-) diff --git a/apps/i2psnark/java/src/org/klomp/snark/Storage.java b/apps/i2psnark/java/src/org/klomp/snark/Storage.java index 9a0a18a476..b16e49fa76 100644 --- a/apps/i2psnark/java/src/org/klomp/snark/Storage.java +++ b/apps/i2psnark/java/src/org/klomp/snark/Storage.java @@ -28,6 +28,7 @@ import java.nio.charset.Charset; import java.nio.charset.CharsetEncoder; import java.security.MessageDigest; import java.util.ArrayList; +import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.Map; @@ -50,17 +51,7 @@ import net.i2p.util.SystemVersion; public class Storage { private final MetaInfo metainfo; - private long[] lengths; - private RandomAccessFile[] rafs; - private String[] names; - private Object[] RAFlock; // lock on RAF access - private long[] RAFtime; // when was RAF last accessed, or 0 if closed - private File[] RAFfile; // File to make it easier to reopen - /** priorities by file; default 0; may be null. @since 0.8.1 */ - private int[] priorities; - /** is the file empty and sparse? */ - private boolean[] isSparse; - + private final List<TorrentFile> _torrentFiles; private final StorageListener listener; private final I2PSnarkUtil _util; private final Log _log; @@ -108,6 +99,9 @@ public class Storage piece_size = metainfo.getPieceLength(0); pieces = needed; total_length = metainfo.getTotalLength(); + List<List<String>> files = metainfo.getFiles(); + int sz = files != null ? files.size() : 1; + _torrentFiles = new ArrayList(sz); } /** @@ -130,13 +124,13 @@ public class Storage _log = util.getContext().logManager().getLog(Storage.class); this.listener = listener; // Create names, rafs and lengths arrays. - getFiles(baseFile); + _torrentFiles = getFiles(baseFile); long total = 0; ArrayList<Long> lengthsList = new ArrayList(); - for (int i = 0; i < lengths.length; i++) + for (TorrentFile tf : _torrentFiles) { - long length = lengths[i]; + long length = tf.length; total += length; lengthsList.add(Long.valueOf(length)); } @@ -167,10 +161,10 @@ public class Storage needed = 0; List<List<String>> files = new ArrayList(); - for (int i = 0; i < names.length; i++) + for (TorrentFile tf : _torrentFiles) { List<String> file = new ArrayList(); - StringTokenizer st = new StringTokenizer(names[i], File.separator); + StringTokenizer st = new StringTokenizer(tf.name, File.separator); while (st.hasMoreTokens()) { String part = st.nextToken(); @@ -220,42 +214,29 @@ public class Storage return piece_hashes; } - private void getFiles(File base) throws IOException + private List<TorrentFile> getFiles(File base) throws IOException { if (base.getAbsolutePath().equals("/")) throw new IOException("Don't seed root"); - ArrayList files = new ArrayList(); + List<File> files = new ArrayList(); addFiles(files, base); int size = files.size(); - names = new String[size]; - lengths = new long[size]; - rafs = new RandomAccessFile[size]; - RAFlock = new Object[size]; - RAFtime = new long[size]; - RAFfile = new File[size]; - priorities = new int[size]; - isSparse = new boolean[size]; + List<TorrentFile> rv = new ArrayList(size); - int i = 0; - Iterator it = files.iterator(); - while (it.hasNext()) - { - File f = (File)it.next(); - names[i] = f.getPath(); - if (base.isDirectory() && names[i].startsWith(base.getPath())) - names[i] = names[i].substring(base.getPath().length() + 1); - lengths[i] = f.length(); - RAFlock[i] = new Object(); - RAFfile[i] = f; - i++; - } + for (File f : files) { + rv.add(new TorrentFile(base, f)); + } + // Sort to prevent exposing OS type, and to make it more likely + // the same torrent created twice will have the same infohash. + Collections.sort(rv); + return rv; } /** * @throws IOException if too many total files */ - private void addFiles(List l, File f) throws IOException { + private void addFiles(List<File> l, File f) throws IOException { if (!f.isDirectory()) { if (l.size() >= SnarkManager.MAX_FILES_PER_TORRENT) throw new IOException("Too many files, limit is " + SnarkManager.MAX_FILES_PER_TORRENT + ", zip them?"); @@ -330,8 +311,8 @@ public class Storage */ public long remaining(String file) { long bytes = 0; - for (int i = 0; i < rafs.length; i++) { - File f = RAFfile[i]; + for (TorrentFile tf : _torrentFiles) { + File f = tf.RAFfile; // use canonical in case snark dir or sub dirs are symlinked String canonical = null; if (f != null) { @@ -346,11 +327,11 @@ public class Storage return 0; int psz = piece_size; long start = bytes; - long end = start + lengths[i]; + long end = start + tf.length; int pc = (int) (bytes / psz); long rv = 0; if (!bitfield.get(pc)) - rv = Math.min(psz - (start % psz), lengths[i]); + rv = Math.min(psz - (start % psz), tf.length); for (int j = pc + 1; (((long)j) * psz) < end && j < pieces; j++) { if (!bitfield.get(j)) { if (((long)(j+1))*psz < end) @@ -361,7 +342,7 @@ public class Storage } return rv; } - bytes += lengths[i]; + bytes += tf.length; } return -1; } @@ -371,16 +352,16 @@ public class Storage * @since 0.8.1 */ public int getPriority(String file) { - if (complete() || metainfo.getFiles() == null || priorities == null) + if (complete() || metainfo.getFiles() == null) return 0; - for (int i = 0; i < rafs.length; i++) { - File f = RAFfile[i]; + for (TorrentFile tf : _torrentFiles) { + File f = tf.RAFfile; // use canonical in case snark dir or sub dirs are symlinked if (f != null) { try { String canonical = f.getCanonicalPath(); if (canonical.equals(file)) - return priorities[i]; + return tf.priority; } catch (IOException ioe) {} } } @@ -395,16 +376,16 @@ public class Storage * @since 0.8.1 */ public void setPriority(String file, int pri) { - if (complete() || metainfo.getFiles() == null || priorities == null) + if (complete() || metainfo.getFiles() == null) return; - for (int i = 0; i < rafs.length; i++) { - File f = RAFfile[i]; + for (TorrentFile tf : _torrentFiles) { + File f = tf.RAFfile; // use canonical in case snark dir or sub dirs are symlinked if (f != null) { try { String canonical = f.getCanonicalPath(); if (canonical.equals(file)) { - priorities[i] = pri; + tf.priority = pri; return; } } catch (IOException ioe) {} @@ -418,6 +399,15 @@ public class Storage * @since 0.8.1 */ public int[] getFilePriorities() { + if (complete()) + return null; + int sz = _torrentFiles.size(); + if (sz <= 1) + return null; + int[] priorities = new int[sz]; + for (int i = 0; i < sz; i++) { + priorities[i] = _torrentFiles.get(i).priority; + } return priorities; } @@ -428,7 +418,18 @@ public class Storage * @since 0.8.1 */ void setFilePriorities(int[] p) { - priorities = p; + if (p == null) { + for (TorrentFile tf : _torrentFiles) { + tf.priority = 0; + } + } else { + int sz = _torrentFiles.size(); + if (p.length != sz) + throw new IllegalArgumentException(); + for (int i = 0; i < sz; i++) { + _torrentFiles.get(i).priority = p[i]; + } + } } /** @@ -441,22 +442,23 @@ public class Storage * @since 0.8.1 */ public int[] getPiecePriorities() { - if (complete() || metainfo.getFiles() == null || priorities == null) + if (complete() || metainfo.getFiles() == null) return null; int[] rv = new int[metainfo.getPieces()]; int file = 0; long pcEnd = -1; - long fileEnd = lengths[0] - 1; + long fileEnd = _torrentFiles.get(0).length - 1; int psz = piece_size; for (int i = 0; i < rv.length; i++) { pcEnd += psz; - int pri = priorities[file]; - while (fileEnd <= pcEnd && file < lengths.length - 1) { + int pri = _torrentFiles.get(file).priority; + while (fileEnd <= pcEnd && file < _torrentFiles.size() - 1) { file++; + TorrentFile tf = _torrentFiles.get(file); long oldFileEnd = fileEnd; - fileEnd += lengths[file]; - if (priorities[file] > pri && oldFileEnd < pcEnd) - pri = priorities[file]; + fileEnd += tf.length; + if (tf.priority > pri && oldFileEnd < pcEnd) + pri = tf.priority; } rv[i] = pri; } @@ -486,13 +488,18 @@ public class Storage /** * Creates (and/or checks) all files from the metainfo file list. + * Only call this once, and only after the constructor with the metainfo. */ public void check(String rootDir) throws IOException { check(rootDir, 0, null); } - /** use a saved bitfield and timestamp from a config file */ + /** + * Creates (and/or checks) all files from the metainfo file list. + * Use a saved bitfield and timestamp from a config file. + * Only call this once, and only after the constructor with the metainfo. + */ public void check(String rootDir, long savedTime, BitField savedBitField) throws IOException { File base; @@ -503,6 +510,8 @@ public class Storage base = new SecureFile(rootDir, filterName(metainfo.getName())); boolean useSavedBitField = savedTime > 0 && savedBitField != null; + if (!_torrentFiles.isEmpty()) + throw new IllegalStateException(); List<List<String>> files = metainfo.getFiles(); if (files == null) { @@ -512,22 +521,12 @@ public class Storage if (!base.createNewFile() && !base.exists()) throw new IOException("Could not create file " + base); - lengths = new long[1]; - rafs = new RandomAccessFile[1]; - names = new String[1]; - RAFlock = new Object[1]; - RAFtime = new long[1]; - RAFfile = new File[1]; - isSparse = new boolean[1]; - lengths[0] = metainfo.getTotalLength(); - RAFlock[0] = new Object(); - RAFfile[0] = base; + _torrentFiles.add(new TorrentFile(base, base, metainfo.getTotalLength())); if (useSavedBitField) { long lm = base.lastModified(); if (lm <= 0 || lm > savedTime) useSavedBitField = false; } - names[0] = base.getName(); } else { @@ -540,20 +539,13 @@ public class Storage List<Long> ls = metainfo.getLengths(); int size = files.size(); long total = 0; - lengths = new long[size]; - rafs = new RandomAccessFile[size]; - names = new String[size]; - RAFlock = new Object[size]; - RAFtime = new long[size]; - RAFfile = new File[size]; - isSparse = new boolean[size]; for (int i = 0; i < size; i++) { List<String> path = files.get(i); File f = createFileFromNames(base, path, areFilesPublic); // dup file name check after filtering for (int j = 0; j < i; j++) { - if (f.equals(RAFfile[j])) { + if (f.equals(_torrentFiles.get(j).RAFfile)) { // Rename and start the check over again // Copy path since metainfo list is unmodifiable path = new ArrayList(path); @@ -570,16 +562,14 @@ public class Storage j = 0; } } - lengths[i] = ls.get(i).longValue(); - RAFlock[i] = new Object(); - RAFfile[i] = f; - total += lengths[i]; + long len = ls.get(i).longValue(); + _torrentFiles.add(new TorrentFile(base, f, len)); + total += len; if (useSavedBitField) { long lm = f.lastModified(); if (lm <= 0 || lm > savedTime) useSavedBitField = false; } - names[i] = f.getName(); } // Sanity check for metainfo file. @@ -604,8 +594,6 @@ public class Storage _log.info("Torrent is complete"); } else { // fixme saved priorities - if (files != null) - priorities = new int[files.size()]; if (_log.shouldLog(Log.INFO)) _log.info("Still need " + needed + " out of " + metainfo.getPieces() + " pieces"); } @@ -620,11 +608,11 @@ public class Storage */ public void reopen(String rootDir) throws IOException { - if (RAFfile == null) + if (_torrentFiles.isEmpty()) throw new IOException("Storage not checked yet"); - for (int i = 0; i < RAFfile.length; i++) { - if (!RAFfile[i].exists()) - throw new IOException("File does not exist: " + RAFfile[i]); + for (TorrentFile tf : _torrentFiles) { + if (!tf.RAFfile.exists()) + throw new IOException("File does not exist: " + tf); } } @@ -778,10 +766,10 @@ public class Storage // Make sure all files are available and of correct length // The files should all exist as they have been created with zero length by createFilesFromNames() - for (int i = 0; i < rafs.length; i++) + for (TorrentFile tf : _torrentFiles) { - long length = RAFfile[i].length(); - if(RAFfile[i].exists() && length == lengths[i]) + long length = tf.RAFfile.length(); + if(tf.RAFfile.exists() && length == tf.length) { if (listener != null) listener.storageAllocated(this, length); @@ -789,27 +777,27 @@ public class Storage } else if (length == 0) { changed = true; - synchronized(RAFlock[i]) { - allocateFile(i); + synchronized(tf) { + allocateFile(tf); // close as we go so we don't run out of file descriptors try { - closeRAF(i); + tf.closeRAF(); } catch (IOException ioe) {} } } else { - String msg = "File '" + names[i] + "' exists, but has wrong length (expected " + - lengths[i] + " but found " + length + ") - repairing corruption"; + String msg = "File '" + tf.name + "' exists, but has wrong length (expected " + + tf.length + " but found " + length + ") - repairing corruption"; if (listener != null) listener.addMessage(msg); _log.error(msg); changed = true; resume = true; _probablyComplete = false; // to force RW - synchronized(RAFlock[i]) { - checkRAF(i); - rafs[i].setLength(lengths[i]); + synchronized(tf) { + RandomAccessFile raf = tf.checkRAF(); + raf.setLength(tf.length); try { - closeRAF(i); + tf.closeRAF(); } catch (IOException ioe) {} } } @@ -820,7 +808,7 @@ public class Storage { byte[] piece = new byte[piece_size]; int file = 0; - long fileEnd = lengths[0]; + long fileEnd = _torrentFiles.get(0).length; long pieceEnd = 0; for (int i = 0; i < pieces; i++) { @@ -829,14 +817,15 @@ public class Storage // close as we go so we don't run out of file descriptors pieceEnd += length; while (fileEnd <= pieceEnd) { - synchronized(RAFlock[file]) { + TorrentFile tf = _torrentFiles.get(file); + synchronized(tf) { try { - closeRAF(file); + tf.closeRAF(); } catch (IOException ioe) {} } - if (++file >= rafs.length) + if (++file >= _torrentFiles.size()) break; - fileEnd += lengths[file]; + fileEnd += tf.length; } if (correctHash) { @@ -882,59 +871,17 @@ public class Storage * Sets isSparse[nr] = true. balloonFile(nr) should be called later to * defrag the file. * - * This calls openRAF(); caller must synchronize and call closeRAF(). + * This calls OpenRAF(); caller must synchronize and call closeRAF(). */ - private void allocateFile(int nr) throws IOException + private void allocateFile(TorrentFile tf) throws IOException { // caller synchronized - openRAF(nr, false); // RW - long remaining = lengths[nr]; - if (listener != null) - listener.storageCreateFile(this, names[nr], remaining); - rafs[nr].setLength(remaining); - // don't bother ballooning later on Windows since there is no sparse file support - // until JDK7 using the JSR-203 interface. - // RAF seeks/writes do not create sparse files. - // Windows will zero-fill up to the point of the write, which - // will make the file fairly unfragmented, on average, at least until - // near the end where it will get exponentially more fragmented. - if (!_isWindows) - isSparse[nr] = true; - // caller will close rafs[nr] - if (listener != null) - listener.storageAllocated(this, lengths[nr]); - } - - /** - * This "balloons" the file with zeros to eliminate disk fragmentation., - * Overwrites the entire file with zeros. Sets isSparse[nr] = false. - * - * Caller must synchronize and call checkRAF() or openRAF(). - * @since 0.9.1 - */ - private void balloonFile(int nr) throws IOException - { - if (_log.shouldLog(Log.INFO)) - _log.info("Ballooning " + nr + ": " + RAFfile[nr]); - long remaining = lengths[nr]; - final int ZEROBLOCKSIZE = (int) Math.min(remaining, 32*1024); - byte[] zeros = new byte[ZEROBLOCKSIZE]; - rafs[nr].seek(0); - // don't bother setting flag for small files - if (remaining > 20*1024*1024) - _allocateCount.incrementAndGet(); - try { - while (remaining > 0) { - int size = (int) Math.min(remaining, ZEROBLOCKSIZE); - rafs[nr].write(zeros, 0, size); - remaining -= size; - } - } finally { - remaining = lengths[nr]; - if (remaining > 20*1024*1024) - _allocateCount.decrementAndGet(); + tf.allocateFile(); + if (listener != null) { + listener.storageCreateFile(this, tf.name, tf.length); + listener.storageAllocated(this, tf.length); } - isSparse[nr] = false; + // caller will close rafs[nr] } @@ -944,18 +891,14 @@ public class Storage */ public void close() throws IOException { - if (rafs == null) return; - for (int i = 0; i < rafs.length; i++) + for (TorrentFile tf : _torrentFiles) { - // if we had an IOE in check(), the RAFlock may be null - if (RAFlock[i] == null) - continue; try { - synchronized(RAFlock[i]) { - closeRAF(i); + synchronized(tf) { + tf.closeRAF(); } } catch (IOException ioe) { - _log.error("Error closing " + RAFfile[i], ioe); + _log.error("Error closing " + tf, ioe); // gobble gobble } } @@ -1020,11 +963,11 @@ public class Storage // Early typecast, avoid possibly overflowing a temp integer long start = (long) piece * (long) piece_size; int i = 0; - long raflen = lengths[i]; + long raflen = _torrentFiles.get(i).length; while (start > raflen) { i++; start -= raflen; - raflen = lengths[i]; + raflen = _torrentFiles.get(i).length; } int written = 0; @@ -1033,27 +976,31 @@ public class Storage while (written < length) { int need = length - written; int len = (start + need < raflen) ? need : (int)(raflen - start); - synchronized(RAFlock[i]) { - checkRAF(i); - if (isSparse[i]) { + TorrentFile tf = _torrentFiles.get(i); + synchronized(tf) { + RandomAccessFile raf = tf.checkRAF(); + if (tf.isSparse) { // If the file is a newly created sparse file, // AND we aren't skipping it, balloon it with all // zeros to un-sparse it by allocating the space. // Obviously this could take a while. // Once we have written to it, it isn't empty/sparse any more. - if (priorities == null || priorities[i] >= 0) - balloonFile(i); - else - isSparse[i] = false; + if (tf.priority >= 0) { + if (_log.shouldLog(Log.INFO)) + _log.info("Ballooning " + tf); + tf.balloonFile(); + } else { + tf.isSparse = false; + } } - rafs[i].seek(start); + raf.seek(start); //rafs[i].write(bs, off + written, len); - pp.write(rafs[i], off + written, len); + pp.write(raf, off + written, len); } written += len; if (need - len > 0) { i++; - raflen = lengths[i]; + raflen = tf.length; start = 0; } } @@ -1130,12 +1077,12 @@ public class Storage long start = ((long) piece * (long) piece_size) + off; int i = 0; - long raflen = lengths[i]; + long raflen = _torrentFiles.get(i).length; while (start > raflen) { i++; start -= raflen; - raflen = lengths[i]; + raflen = _torrentFiles.get(i).length; } int read = 0; @@ -1143,17 +1090,18 @@ public class Storage { int need = length - read; int len = (start + need < raflen) ? need : (int)(raflen - start); - synchronized(RAFlock[i]) + TorrentFile tf = _torrentFiles.get(i); + synchronized(tf) { - checkRAF(i); - rafs[i].seek(start); - rafs[i].readFully(bs, read, len); + RandomAccessFile raf = tf.checkRAF(); + raf.seek(start); + raf.readFully(bs, read, len); } read += len; if (need - len > 0) { i++; - raflen = lengths[i]; + raflen = _torrentFiles.get(i).length; start = 0; } } @@ -1161,58 +1109,182 @@ public class Storage return length; } + private static final long RAFCloseDelay = 4*60*1000; + /** * Close unused RAFs - call periodically */ - private static final long RAFCloseDelay = 4*60*1000; public void cleanRAFs() { long cutoff = System.currentTimeMillis() - RAFCloseDelay; - for (int i = 0; i < RAFlock.length; i++) { - synchronized(RAFlock[i]) { - if (RAFtime[i] > 0 && RAFtime[i] < cutoff) { - try { - closeRAF(i); - } catch (IOException ioe) {} - } + for (TorrentFile tf : _torrentFiles) { + synchronized(tf) { + tf.closeRAF(cutoff); } } } - /* - * For each of the following, - * caller must synchronize on RAFlock[i] - * ... except at the beginning if you're careful - */ - /** - * This must be called before using the RAF to ensure it is open + * A single file in a torrent. + * @since 0.9.9 */ - private void checkRAF(int i) throws IOException { - if (RAFtime[i] > 0) { - RAFtime[i] = System.currentTimeMillis(); - return; - } - openRAF(i); - } + private class TorrentFile implements Comparable<TorrentFile> { + public final long length; + public final String name; + public final File RAFfile; + /** + * when was RAF last accessed, or 0 if closed + * locking: this + */ + private long RAFtime; + /** + * null when closed + * locking: this + */ + private RandomAccessFile raf; + /** + * is the file empty and sparse? + * locking: this + */ + public boolean isSparse; + /** priority by file; default 0 */ + public volatile int priority; + + /** + * For new metainfo from files; + * use base == f for single-file torrent + */ + public TorrentFile(File base, File f) { + this(base, f, f.length()); + } - private void openRAF(int i) throws IOException { - openRAF(i, _probablyComplete); - } + /** + * For existing metainfo with specified file length; + * use base == f for single-file torrent + */ + public TorrentFile(File base, File f, long len) { + String n = f.getPath(); + if (base.isDirectory() && n.startsWith(base.getPath())) + n = n.substring(base.getPath().length() + 1); + name = n; + length = len; + RAFfile = f; + } - private void openRAF(int i, boolean readonly) throws IOException { - rafs[i] = new RandomAccessFile(RAFfile[i], (readonly || !RAFfile[i].canWrite()) ? "r" : "rw"); - RAFtime[i] = System.currentTimeMillis(); - } + /* + * For each of the following, + * caller must synchronize on RAFlock[i] + * ... except at the beginning if you're careful + */ + + /** + * This must be called before using the RAF to ensure it is open + * locking: this + */ + public RandomAccessFile checkRAF() throws IOException { + if (RAFtime > 0) + RAFtime = System.currentTimeMillis(); + else + openRAF(); + return raf; + } - /** - * Can be called even if not open - */ - private void closeRAF(int i) throws IOException { - RAFtime[i] = 0; - if (rafs[i] == null) - return; - rafs[i].close(); - rafs[i] = null; + /** + * locking: this + */ + private void openRAF() throws IOException { + openRAF(_probablyComplete); + } + + /** + * locking: this + */ + private void openRAF(boolean readonly) throws IOException { + raf = new RandomAccessFile(RAFfile, (readonly || !RAFfile.canWrite()) ? "r" : "rw"); + RAFtime = System.currentTimeMillis(); + } + + /** + * Close if last used time older than cutoff. + * locking: this + */ + public void closeRAF(long cutoff) { + if (RAFtime > 0 && RAFtime < cutoff) { + try { + closeRAF(); + } catch (IOException ioe) {} + } + } + + /** + * Can be called even if not open + * locking: this + */ + public void closeRAF() throws IOException { + RAFtime = 0; + if (raf == null) + return; + raf.close(); + raf = null; + } + + + /** + * This creates a (presumably) sparse file so that reads won't fail with IOE. + * Sets isSparse[nr] = true. balloonFile(nr) should be called later to + * defrag the file. + * + * This calls openRAF(); caller must synchronize and call closeRAF(). + */ + public void allocateFile() throws IOException { + // caller synchronized + openRAF(false); // RW + raf.setLength(length); + // don't bother ballooning later on Windows since there is no sparse file support + // until JDK7 using the JSR-203 interface. + // RAF seeks/writes do not create sparse files. + // Windows will zero-fill up to the point of the write, which + // will make the file fairly unfragmented, on average, at least until + // near the end where it will get exponentially more fragmented. + if (!_isWindows) + isSparse = true; + } + + /** + * This "balloons" the file with zeros to eliminate disk fragmentation., + * Overwrites the entire file with zeros. Sets isSparse[nr] = false. + * + * Caller must synchronize and call checkRAF() or openRAF(). + * @since 0.9.1 + */ + public void balloonFile() throws IOException + { + long remaining = length; + final int ZEROBLOCKSIZE = (int) Math.min(remaining, 32*1024); + byte[] zeros = new byte[ZEROBLOCKSIZE]; + raf.seek(0); + // don't bother setting flag for small files + if (remaining > 20*1024*1024) + _allocateCount.incrementAndGet(); + try { + while (remaining > 0) { + int size = (int) Math.min(remaining, ZEROBLOCKSIZE); + raf.write(zeros, 0, size); + remaining -= size; + } + } finally { + remaining = length; + if (remaining > 20*1024*1024) + _allocateCount.decrementAndGet(); + } + isSparse = false; + } + + public int compareTo(TorrentFile tf) { + return name.compareTo(tf.name); + } + + @Override + public String toString() { return name; } } /** -- GitLab