forked from I2P_Developers/i2p.i2p
* i2psnark:
- Create sparse files at torrent creation and delay "ballooning" until first write (ticket #641) - Redo clear messages button - Concurrent message queue
This commit is contained in:
@ -19,7 +19,9 @@ import java.util.Properties;
|
||||
import java.util.Set;
|
||||
import java.util.StringTokenizer;
|
||||
import java.util.TreeMap;
|
||||
import java.util.Queue;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.data.Base64;
|
||||
@ -50,7 +52,7 @@ public class SnarkManager implements Snark.CompleteListener {
|
||||
private Properties _config;
|
||||
private final I2PAppContext _context;
|
||||
private final Log _log;
|
||||
private final List<String> _messages;
|
||||
private final Queue<String> _messages;
|
||||
private final I2PSnarkUtil _util;
|
||||
private PeerCoordinatorSet _peerCoordinatorSet;
|
||||
private ConnectionAcceptor _connectionAcceptor;
|
||||
@ -123,7 +125,7 @@ public class SnarkManager implements Snark.CompleteListener {
|
||||
_addSnarkLock = new Object();
|
||||
_context = I2PAppContext.getGlobalContext();
|
||||
_log = _context.logManager().getLog(SnarkManager.class);
|
||||
_messages = new ArrayList(16);
|
||||
_messages = new LinkedBlockingQueue();
|
||||
_util = new I2PSnarkUtil(_context);
|
||||
_configFile = new File(CONFIG_FILE);
|
||||
if (!_configFile.isAbsolute())
|
||||
@ -154,13 +156,12 @@ public class SnarkManager implements Snark.CompleteListener {
|
||||
/** hook to I2PSnarkUtil for the servlet */
|
||||
public I2PSnarkUtil util() { return _util; }
|
||||
|
||||
private static final int MAX_MESSAGES = 5;
|
||||
private static final int MAX_MESSAGES = 100;
|
||||
|
||||
public void addMessage(String message) {
|
||||
synchronized (_messages) {
|
||||
_messages.add(message);
|
||||
while (_messages.size() > MAX_MESSAGES)
|
||||
_messages.remove(0);
|
||||
_messages.offer(message);
|
||||
while (_messages.size() > MAX_MESSAGES) {
|
||||
_messages.poll();
|
||||
}
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("MSG: " + message);
|
||||
@ -168,16 +169,14 @@ public class SnarkManager implements Snark.CompleteListener {
|
||||
|
||||
/** newest last */
|
||||
public List<String> getMessages() {
|
||||
synchronized (_messages) {
|
||||
return new ArrayList(_messages);
|
||||
}
|
||||
if (_messages.isEmpty())
|
||||
return Collections.EMPTY_LIST;
|
||||
return new ArrayList(_messages);
|
||||
}
|
||||
|
||||
/** @since 0.9 */
|
||||
public void clearMessages() {
|
||||
synchronized (_messages) {
|
||||
_messages.clear();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1192,13 +1191,11 @@ public class SnarkManager implements Snark.CompleteListener {
|
||||
// don't bother delaying if auto start is false
|
||||
long delay = 60 * 1000 * getStartupDelayMinutes();
|
||||
if (delay > 0 && shouldAutoStart()) {
|
||||
_messages.add(_("Adding torrents in {0}", DataHelper.formatDuration2(delay)));
|
||||
addMessage(_("Adding torrents in {0}", DataHelper.formatDuration2(delay)));
|
||||
try { Thread.sleep(delay); } catch (InterruptedException ie) {}
|
||||
// the first message was a "We are starting up in 1m"
|
||||
synchronized (_messages) {
|
||||
if (_messages.size() == 1)
|
||||
_messages.remove(0);
|
||||
}
|
||||
// Remove that first message
|
||||
if (_messages.size() == 1)
|
||||
_messages.poll();
|
||||
}
|
||||
|
||||
// here because we need to delay until I2CP is up
|
||||
|
@ -50,6 +50,8 @@ public class Storage
|
||||
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 StorageListener listener;
|
||||
private final I2PSnarkUtil _util;
|
||||
@ -73,6 +75,8 @@ public class Storage
|
||||
|
||||
private static final Map<String, String> _filterNameCache = new ConcurrentHashMap();
|
||||
|
||||
private static final boolean _isWindows = System.getProperty("os.name").startsWith("Win");
|
||||
|
||||
/**
|
||||
* Creates a new storage based on the supplied MetaInfo. This will
|
||||
* try to create and/or check all needed files in the MetaInfo.
|
||||
@ -202,7 +206,7 @@ public class Storage
|
||||
RAFtime = new long[size];
|
||||
RAFfile = new File[size];
|
||||
priorities = new int[size];
|
||||
|
||||
isSparse = new boolean[size];
|
||||
|
||||
int i = 0;
|
||||
Iterator it = files.iterator();
|
||||
@ -462,6 +466,7 @@ public class Storage
|
||||
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;
|
||||
@ -488,6 +493,7 @@ public class Storage
|
||||
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);
|
||||
@ -701,6 +707,7 @@ 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++)
|
||||
{
|
||||
long length = RAFfile[i].length();
|
||||
@ -725,6 +732,7 @@ public class Storage
|
||||
SnarkManager.instance().addMessage(msg);
|
||||
_util.debug(msg, Snark.ERROR);
|
||||
changed = true;
|
||||
resume = true;
|
||||
_probablyComplete = false; // to force RW
|
||||
synchronized(RAFlock[i]) {
|
||||
checkRAF(i);
|
||||
@ -798,26 +806,54 @@ public class Storage
|
||||
}
|
||||
}
|
||||
|
||||
/** this calls openRAF(); caller must synnchronize and call closeRAF() */
|
||||
/**
|
||||
* 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().
|
||||
*/
|
||||
private void allocateFile(int nr) throws IOException
|
||||
{
|
||||
// caller synchronized
|
||||
openRAF(nr, false); // RW
|
||||
// XXX - Is this the best way to make sure we have enough space for
|
||||
// the whole file?
|
||||
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
|
||||
{
|
||||
_util.debug("Ballooning " + nr + ": " + RAFfile[nr], Snark.INFO);
|
||||
long remaining = lengths[nr];
|
||||
final int ZEROBLOCKSIZE = (int) Math.min(remaining, 32*1024);
|
||||
byte[] zeros = new byte[ZEROBLOCKSIZE];
|
||||
rafs[nr].seek(0);
|
||||
while (remaining > 0) {
|
||||
int size = (int) Math.min(remaining, ZEROBLOCKSIZE);
|
||||
rafs[nr].write(zeros, 0, size);
|
||||
remaining -= size;
|
||||
}
|
||||
// caller will close rafs[nr]
|
||||
if (listener != null)
|
||||
listener.storageAllocated(this, lengths[nr]);
|
||||
isSparse[nr] = false;
|
||||
}
|
||||
|
||||
|
||||
@ -909,6 +945,17 @@ public class Storage
|
||||
int len = (start + need < raflen) ? need : (int)(raflen - start);
|
||||
synchronized(RAFlock[i]) {
|
||||
checkRAF(i);
|
||||
if (isSparse[i]) {
|
||||
// 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;
|
||||
}
|
||||
rafs[i].seek(start);
|
||||
//rafs[i].write(bs, off + written, len);
|
||||
pp.write(rafs[i], off + written, len);
|
||||
@ -958,7 +1005,7 @@ public class Storage
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This is a dup of MetaInfo.getPieceLength() but we need it
|
||||
|
@ -297,19 +297,23 @@ public class I2PSnarkServlet extends DefaultServlet {
|
||||
private void writeMessages(PrintWriter out, boolean isConfigure, String peerString) throws IOException {
|
||||
List<String> msgs = _manager.getMessages();
|
||||
if (!msgs.isEmpty()) {
|
||||
out.write("<div class=\"snarkMessages\"><ul>");
|
||||
for (int i = msgs.size()-1; i >= 0; i--) {
|
||||
String msg = msgs.get(i);
|
||||
out.write("<li>" + msg + "</li>\n");
|
||||
}
|
||||
out.write("</ul><p><a href=\"/i2psnark/");
|
||||
out.write("<div class=\"snarkMessages\">");
|
||||
out.write("<a href=\"/i2psnark/");
|
||||
if (isConfigure)
|
||||
out.write("configure");
|
||||
if (peerString.length() > 0)
|
||||
out.write(peerString + "&");
|
||||
else
|
||||
out.write("?");
|
||||
out.write("action=Clear&nonce=" + _nonce + "\">" + _("clear messages") + "</a></p></div>");
|
||||
out.write("action=Clear&nonce=" + _nonce + "\">" +
|
||||
"<img src=\"" + _imgPath + "delete.png\" title=\"" + _("clear messages") +
|
||||
"\" alt=\"" + _("clear messages") + "\"></a>" +
|
||||
"<ul>");
|
||||
for (int i = msgs.size()-1; i >= 0; i--) {
|
||||
String msg = msgs.get(i);
|
||||
out.write("<li>" + msg + "</li>\n");
|
||||
}
|
||||
out.write("</ul></div>");
|
||||
}
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user