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.Set;
|
||||||
import java.util.StringTokenizer;
|
import java.util.StringTokenizer;
|
||||||
import java.util.TreeMap;
|
import java.util.TreeMap;
|
||||||
|
import java.util.Queue;
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
import java.util.concurrent.LinkedBlockingQueue;
|
||||||
|
|
||||||
import net.i2p.I2PAppContext;
|
import net.i2p.I2PAppContext;
|
||||||
import net.i2p.data.Base64;
|
import net.i2p.data.Base64;
|
||||||
@@ -50,7 +52,7 @@ public class SnarkManager implements Snark.CompleteListener {
|
|||||||
private Properties _config;
|
private Properties _config;
|
||||||
private final I2PAppContext _context;
|
private final I2PAppContext _context;
|
||||||
private final Log _log;
|
private final Log _log;
|
||||||
private final List<String> _messages;
|
private final Queue<String> _messages;
|
||||||
private final I2PSnarkUtil _util;
|
private final I2PSnarkUtil _util;
|
||||||
private PeerCoordinatorSet _peerCoordinatorSet;
|
private PeerCoordinatorSet _peerCoordinatorSet;
|
||||||
private ConnectionAcceptor _connectionAcceptor;
|
private ConnectionAcceptor _connectionAcceptor;
|
||||||
@@ -123,7 +125,7 @@ public class SnarkManager implements Snark.CompleteListener {
|
|||||||
_addSnarkLock = new Object();
|
_addSnarkLock = new Object();
|
||||||
_context = I2PAppContext.getGlobalContext();
|
_context = I2PAppContext.getGlobalContext();
|
||||||
_log = _context.logManager().getLog(SnarkManager.class);
|
_log = _context.logManager().getLog(SnarkManager.class);
|
||||||
_messages = new ArrayList(16);
|
_messages = new LinkedBlockingQueue();
|
||||||
_util = new I2PSnarkUtil(_context);
|
_util = new I2PSnarkUtil(_context);
|
||||||
_configFile = new File(CONFIG_FILE);
|
_configFile = new File(CONFIG_FILE);
|
||||||
if (!_configFile.isAbsolute())
|
if (!_configFile.isAbsolute())
|
||||||
@@ -154,13 +156,12 @@ public class SnarkManager implements Snark.CompleteListener {
|
|||||||
/** hook to I2PSnarkUtil for the servlet */
|
/** hook to I2PSnarkUtil for the servlet */
|
||||||
public I2PSnarkUtil util() { return _util; }
|
public I2PSnarkUtil util() { return _util; }
|
||||||
|
|
||||||
private static final int MAX_MESSAGES = 5;
|
private static final int MAX_MESSAGES = 100;
|
||||||
|
|
||||||
public void addMessage(String message) {
|
public void addMessage(String message) {
|
||||||
synchronized (_messages) {
|
_messages.offer(message);
|
||||||
_messages.add(message);
|
while (_messages.size() > MAX_MESSAGES) {
|
||||||
while (_messages.size() > MAX_MESSAGES)
|
_messages.poll();
|
||||||
_messages.remove(0);
|
|
||||||
}
|
}
|
||||||
if (_log.shouldLog(Log.INFO))
|
if (_log.shouldLog(Log.INFO))
|
||||||
_log.info("MSG: " + message);
|
_log.info("MSG: " + message);
|
||||||
@@ -168,16 +169,14 @@ public class SnarkManager implements Snark.CompleteListener {
|
|||||||
|
|
||||||
/** newest last */
|
/** newest last */
|
||||||
public List<String> getMessages() {
|
public List<String> getMessages() {
|
||||||
synchronized (_messages) {
|
if (_messages.isEmpty())
|
||||||
return new ArrayList(_messages);
|
return Collections.EMPTY_LIST;
|
||||||
}
|
return new ArrayList(_messages);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @since 0.9 */
|
/** @since 0.9 */
|
||||||
public void clearMessages() {
|
public void clearMessages() {
|
||||||
synchronized (_messages) {
|
|
||||||
_messages.clear();
|
_messages.clear();
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -1192,13 +1191,11 @@ public class SnarkManager implements Snark.CompleteListener {
|
|||||||
// don't bother delaying if auto start is false
|
// don't bother delaying if auto start is false
|
||||||
long delay = 60 * 1000 * getStartupDelayMinutes();
|
long delay = 60 * 1000 * getStartupDelayMinutes();
|
||||||
if (delay > 0 && shouldAutoStart()) {
|
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) {}
|
try { Thread.sleep(delay); } catch (InterruptedException ie) {}
|
||||||
// the first message was a "We are starting up in 1m"
|
// Remove that first message
|
||||||
synchronized (_messages) {
|
if (_messages.size() == 1)
|
||||||
if (_messages.size() == 1)
|
_messages.poll();
|
||||||
_messages.remove(0);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// here because we need to delay until I2CP is up
|
// 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
|
private File[] RAFfile; // File to make it easier to reopen
|
||||||
/** priorities by file; default 0; may be null. @since 0.8.1 */
|
/** priorities by file; default 0; may be null. @since 0.8.1 */
|
||||||
private int[] priorities;
|
private int[] priorities;
|
||||||
|
/** is the file empty and sparse? */
|
||||||
|
private boolean[] isSparse;
|
||||||
|
|
||||||
private final StorageListener listener;
|
private final StorageListener listener;
|
||||||
private final I2PSnarkUtil _util;
|
private final I2PSnarkUtil _util;
|
||||||
@@ -73,6 +75,8 @@ public class Storage
|
|||||||
|
|
||||||
private static final Map<String, String> _filterNameCache = new ConcurrentHashMap();
|
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
|
* Creates a new storage based on the supplied MetaInfo. This will
|
||||||
* try to create and/or check all needed files in the MetaInfo.
|
* try to create and/or check all needed files in the MetaInfo.
|
||||||
@@ -202,7 +206,7 @@ public class Storage
|
|||||||
RAFtime = new long[size];
|
RAFtime = new long[size];
|
||||||
RAFfile = new File[size];
|
RAFfile = new File[size];
|
||||||
priorities = new int[size];
|
priorities = new int[size];
|
||||||
|
isSparse = new boolean[size];
|
||||||
|
|
||||||
int i = 0;
|
int i = 0;
|
||||||
Iterator it = files.iterator();
|
Iterator it = files.iterator();
|
||||||
@@ -462,6 +466,7 @@ public class Storage
|
|||||||
RAFlock = new Object[1];
|
RAFlock = new Object[1];
|
||||||
RAFtime = new long[1];
|
RAFtime = new long[1];
|
||||||
RAFfile = new File[1];
|
RAFfile = new File[1];
|
||||||
|
isSparse = new boolean[1];
|
||||||
lengths[0] = metainfo.getTotalLength();
|
lengths[0] = metainfo.getTotalLength();
|
||||||
RAFlock[0] = new Object();
|
RAFlock[0] = new Object();
|
||||||
RAFfile[0] = base;
|
RAFfile[0] = base;
|
||||||
@@ -488,6 +493,7 @@ public class Storage
|
|||||||
RAFlock = new Object[size];
|
RAFlock = new Object[size];
|
||||||
RAFtime = new long[size];
|
RAFtime = new long[size];
|
||||||
RAFfile = new File[size];
|
RAFfile = new File[size];
|
||||||
|
isSparse = new boolean[size];
|
||||||
for (int i = 0; i < size; i++)
|
for (int i = 0; i < size; i++)
|
||||||
{
|
{
|
||||||
List<String> path = files.get(i);
|
List<String> path = files.get(i);
|
||||||
@@ -701,6 +707,7 @@ public class Storage
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Make sure all files are available and of correct length
|
// 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 (int i = 0; i < rafs.length; i++)
|
||||||
{
|
{
|
||||||
long length = RAFfile[i].length();
|
long length = RAFfile[i].length();
|
||||||
@@ -725,6 +732,7 @@ public class Storage
|
|||||||
SnarkManager.instance().addMessage(msg);
|
SnarkManager.instance().addMessage(msg);
|
||||||
_util.debug(msg, Snark.ERROR);
|
_util.debug(msg, Snark.ERROR);
|
||||||
changed = true;
|
changed = true;
|
||||||
|
resume = true;
|
||||||
_probablyComplete = false; // to force RW
|
_probablyComplete = false; // to force RW
|
||||||
synchronized(RAFlock[i]) {
|
synchronized(RAFlock[i]) {
|
||||||
checkRAF(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
|
private void allocateFile(int nr) throws IOException
|
||||||
{
|
{
|
||||||
// caller synchronized
|
// caller synchronized
|
||||||
openRAF(nr, false); // RW
|
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];
|
long remaining = lengths[nr];
|
||||||
if (listener != null)
|
if (listener != null)
|
||||||
listener.storageCreateFile(this, names[nr], remaining);
|
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);
|
final int ZEROBLOCKSIZE = (int) Math.min(remaining, 32*1024);
|
||||||
byte[] zeros = new byte[ZEROBLOCKSIZE];
|
byte[] zeros = new byte[ZEROBLOCKSIZE];
|
||||||
|
rafs[nr].seek(0);
|
||||||
while (remaining > 0) {
|
while (remaining > 0) {
|
||||||
int size = (int) Math.min(remaining, ZEROBLOCKSIZE);
|
int size = (int) Math.min(remaining, ZEROBLOCKSIZE);
|
||||||
rafs[nr].write(zeros, 0, size);
|
rafs[nr].write(zeros, 0, size);
|
||||||
remaining -= size;
|
remaining -= size;
|
||||||
}
|
}
|
||||||
// caller will close rafs[nr]
|
isSparse[nr] = false;
|
||||||
if (listener != null)
|
|
||||||
listener.storageAllocated(this, lengths[nr]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -909,6 +945,17 @@ public class Storage
|
|||||||
int len = (start + need < raflen) ? need : (int)(raflen - start);
|
int len = (start + need < raflen) ? need : (int)(raflen - start);
|
||||||
synchronized(RAFlock[i]) {
|
synchronized(RAFlock[i]) {
|
||||||
checkRAF(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].seek(start);
|
||||||
//rafs[i].write(bs, off + written, len);
|
//rafs[i].write(bs, off + written, len);
|
||||||
pp.write(rafs[i], off + written, len);
|
pp.write(rafs[i], off + written, len);
|
||||||
@@ -958,7 +1005,7 @@ public class Storage
|
|||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This is a dup of MetaInfo.getPieceLength() but we need it
|
* 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 {
|
private void writeMessages(PrintWriter out, boolean isConfigure, String peerString) throws IOException {
|
||||||
List<String> msgs = _manager.getMessages();
|
List<String> msgs = _manager.getMessages();
|
||||||
if (!msgs.isEmpty()) {
|
if (!msgs.isEmpty()) {
|
||||||
out.write("<div class=\"snarkMessages\"><ul>");
|
out.write("<div class=\"snarkMessages\">");
|
||||||
for (int i = msgs.size()-1; i >= 0; i--) {
|
out.write("<a href=\"/i2psnark/");
|
||||||
String msg = msgs.get(i);
|
|
||||||
out.write("<li>" + msg + "</li>\n");
|
|
||||||
}
|
|
||||||
out.write("</ul><p><a href=\"/i2psnark/");
|
|
||||||
if (isConfigure)
|
if (isConfigure)
|
||||||
out.write("configure");
|
out.write("configure");
|
||||||
if (peerString.length() > 0)
|
if (peerString.length() > 0)
|
||||||
out.write(peerString + "&");
|
out.write(peerString + "&");
|
||||||
else
|
else
|
||||||
out.write("?");
|
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>");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -93,7 +93,7 @@ body {
|
|||||||
border: 1px solid #000;
|
border: 1px solid #000;
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
color: #26f;
|
color: #26f;
|
||||||
max-height: 82px;
|
max-height: 76px;
|
||||||
min-height: 45px;
|
min-height: 45px;
|
||||||
width: auto;
|
width: auto;
|
||||||
background: #2a192a url('/themes/snark/ubergine/images/hat.png') no-repeat scroll right center;
|
background: #2a192a url('/themes/snark/ubergine/images/hat.png') no-repeat scroll right center;
|
||||||
@@ -118,6 +118,12 @@ body {
|
|||||||
text-align: right;
|
text-align: right;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.snarkMessages img {
|
||||||
|
float: right;
|
||||||
|
margin: -3px -4px 4px 4px;
|
||||||
|
opacity: 0.8;
|
||||||
|
}
|
||||||
|
|
||||||
.logshim {
|
.logshim {
|
||||||
margin-top: -10px !important;
|
margin-top: -10px !important;
|
||||||
}
|
}
|
||||||
|
@@ -92,7 +92,7 @@ body {
|
|||||||
border: 1px solid #000;
|
border: 1px solid #000;
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
color: #26f;
|
color: #26f;
|
||||||
max-height: 82px;
|
max-height: 76px;
|
||||||
min-height: 45px;
|
min-height: 45px;
|
||||||
width: auto;
|
width: auto;
|
||||||
background: #eda url('/themes/snark/ubergine/images/hat.png') no-repeat scroll right center;
|
background: #eda url('/themes/snark/ubergine/images/hat.png') no-repeat scroll right center;
|
||||||
@@ -121,6 +121,12 @@ body {
|
|||||||
text-align: right;
|
text-align: right;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.snarkMessages img {
|
||||||
|
float: right;
|
||||||
|
margin: -3px -4px 4px 4px;
|
||||||
|
opacity: 0.8;
|
||||||
|
}
|
||||||
|
|
||||||
.logshim {
|
.logshim {
|
||||||
margin-top: -10px !important;
|
margin-top: -10px !important;
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user