diff --git a/src/freenet/clients/http/FirstTimeWizardNewToadlet.java b/src/freenet/clients/http/FirstTimeWizardNewToadlet.java
index eb0d0de802f..e279fc2f5a0 100644
--- a/src/freenet/clients/http/FirstTimeWizardNewToadlet.java
+++ b/src/freenet/clients/http/FirstTimeWizardNewToadlet.java
@@ -6,6 +6,8 @@
import freenet.clients.http.wizardsteps.DATASTORE_SIZE;
import freenet.config.Config;
import freenet.config.ConfigException;
+import freenet.config.InvalidConfigValueException;
+import freenet.config.NodeNeedRestartException;
import freenet.config.Option;
import freenet.l10n.NodeL10n;
import freenet.node.*;
@@ -21,6 +23,7 @@
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
+import java.util.Objects;
import static java.util.concurrent.TimeUnit.SECONDS;
@@ -33,6 +36,10 @@ public class FirstTimeWizardNewToadlet extends WebTemplateToadlet {
public static final String TOADLET_URL = "/wiz/";
private static final long MIN_STORAGE_LIMIT = Node.MIN_STORE_SIZE * 5 / 4; // min store size + 10% for client cache + 10% for slashdot cache
+ private static final String CLIENT_CACHE_TYPE_KEY = "clientCacheType";
+ private static final String STORE_TYPE_KEY = "storeType";
+ private static final String STORE_TYPE_VALUE_RAM = "ram";
+ private static final String STORE_TYPE_SALT_HASH = "salt-hash";
private final NodeClientCore core;
@@ -117,6 +124,8 @@ private class FormModel {
private String bandwidthMonthlyLimit = "500";
+ private String donateDiskSpace = "checked";
+
private String storageLimit;
private final String minStorageLimit = String.format(Locale.ENGLISH, "%.2f", (float) MIN_STORAGE_LIMIT / DatastoreUtil.oneGiB);
@@ -132,6 +141,13 @@ private class FormModel {
private Map errors = new HashMap<>();
FormModel() {
+ String storeType = config.get("node").getString(STORE_TYPE_KEY);
+ String cacheType = config.get("node").getString(CLIENT_CACHE_TYPE_KEY);
+ donateDiskSpace = (
+ Objects.equals(storeType, STORE_TYPE_VALUE_RAM)
+ && Objects.equals(cacheType, STORE_TYPE_VALUE_RAM))
+ ? ""
+ : "checked" ;
float storage = 100;
@SuppressWarnings("unchecked")
Option sizeOption = (Option) config.get("node").getOption("storeSize");
@@ -168,6 +184,7 @@ private class FormModel {
downloadLimit = request.getPartAsStringFailsafe("downLimit", 100);
uploadLimit = request.getPartAsStringFailsafe("upLimit", 100);
bandwidthMonthlyLimit = request.getPartAsStringFailsafe("monthlyLimit", 100);
+ donateDiskSpace = request.getPartAsStringFailsafe("donateDiskSpace", 20);
storageLimit = request.getPartAsStringFailsafe("storage", 100);
setPassword = request.getPartAsStringFailsafe("setPassword", 20);
password = request.getPartAsStringFailsafe("password", SecurityLevelsToadlet.MAX_PASSWORD_LENGTH + 1);
@@ -273,6 +290,7 @@ private Map toModel() {
model.put("uploadLimit", uploadLimit);
model.put("bandwidthMonthlyLimit", bandwidthMonthlyLimit);
model.put("minBandwidthMonthlyLimit", String.format("%.2f", BandwidthLimit.minMonthlyLimit));
+ model.put("donateDiskSpace", donateDiskSpace);
model.put("storageLimit", storageLimit);
model.put("minStorageLimit", minStorageLimit);
if (!isPasswordAlreadySet) {
@@ -318,7 +336,27 @@ private void save() {
Logger.error(this, "Should not happen, please report! " + e, e);
}
- DATASTORE_SIZE.setDatastoreSize(storageLimit + "GiB", config, this);
+ try {
+ if (donateDiskSpace.isEmpty()) {
+ // set a fixed size: large RAM stores aren't useful. Storage limit must be hidden if donate disk space is disabled.
+ DATASTORE_SIZE.setDatastoreSize( "128MiB", false, config, this);
+ try {
+ config.get("node").set(STORE_TYPE_KEY, STORE_TYPE_VALUE_RAM);
+ } catch (NodeNeedRestartException e) {
+ // ignore: can only happen when switching from ram store to salt-hash.
+ }
+ try {
+ config.get("node").set(CLIENT_CACHE_TYPE_KEY, STORE_TYPE_VALUE_RAM);
+ } catch (NodeNeedRestartException e) {
+ // ignore: can only happen when switching from ram store to salt-hash.
+ }
+ } else {
+ // sets the store to salt-hash internally
+ DATASTORE_SIZE.setDatastoreSize(storageLimit + "GiB", config, this);
+ }
+ } catch (InvalidConfigValueException e) {
+ Logger.warning(this, "Should not happen, please report! " + e, e);
+ }
if (!isPasswordAlreadySet) {
try {
diff --git a/src/freenet/clients/http/staticfiles/js/firsttimewizard.js b/src/freenet/clients/http/staticfiles/js/firsttimewizard.js
index f8a660bacc7..948c4113d8b 100644
--- a/src/freenet/clients/http/staticfiles/js/firsttimewizard.js
+++ b/src/freenet/clients/http/staticfiles/js/firsttimewizard.js
@@ -77,7 +77,7 @@ slideToggler = (function() {
instance = (new Date).getTime() - init;
step = start + disp * instance / time;
- if (instance <= time) {
+ if (time > 1 && instance <= time) {
el.style.height = step + 'px';
} else {
el.style.cssText = "display: " + (end === 0 ? 'none' : 'block');
@@ -166,4 +166,5 @@ function initMonthlyLimit() {
checkChange('knowSomeone', data = {'checked': 'checkDarknet', 'unchecked': 'noDarknet'});
checkChange('setPassword', data = {'checked': 'givePassword', 'unchecked': ''});
+checkChange('donateDiskSpace', data = {'checked': 'chooseStoreSize', 'unchecked': ''});
initMonthlyLimit();
diff --git a/src/freenet/clients/http/staticfiles/themes/winterfacey/script.js b/src/freenet/clients/http/staticfiles/themes/winterfacey/script.js
index 7e91b9202c3..3f8fe75b98e 100644
--- a/src/freenet/clients/http/staticfiles/themes/winterfacey/script.js
+++ b/src/freenet/clients/http/staticfiles/themes/winterfacey/script.js
@@ -68,9 +68,11 @@ var mobileMenu = function() {
}
function attach() {
- navbarDomElement.appendChild(hamburgerContent);
- hamburgerDomElement = document.getElementById(hamburgerId);
- customAddEventListener(hamburgerDomElement, 'click', toggleCssClasses);
+ if (navbarDomElement) {
+ navbarDomElement.appendChild(hamburgerContent);
+ hamburgerDomElement = document.getElementById(hamburgerId);
+ customAddEventListener(hamburgerDomElement, 'click', toggleCssClasses);
+ }
}
function detach() {
@@ -99,7 +101,9 @@ var toggleInnerMenu = function() {
var notSelectedLinkList = document.querySelectorAll('#navlist > .navlist-not-selected > a');
// add active class for selected element after load page
- selectedList[0].className += " active";
+ if (selectedList.length > 0) {
+ selectedList[0].className += " active";
+ }
// function toggle (add and remove active class)
function toggle(selectedItem) {
diff --git a/src/freenet/clients/http/templates/first-time-wizard.html b/src/freenet/clients/http/templates/first-time-wizard.html
index 93c417be598..8aa323cb388 100644
--- a/src/freenet/clients/http/templates/first-time-wizard.html
+++ b/src/freenet/clients/http/templates/first-time-wizard.html
@@ -81,7 +81,10 @@ {{ l10n("bandwidthCommonInternetConnectionSpeeds") }}
{% if setPassword is not null %}
diff --git a/src/freenet/clients/http/wizardsteps/DATASTORE_SIZE.java b/src/freenet/clients/http/wizardsteps/DATASTORE_SIZE.java
index 960218a136f..82126403ce7 100644
--- a/src/freenet/clients/http/wizardsteps/DATASTORE_SIZE.java
+++ b/src/freenet/clients/http/wizardsteps/DATASTORE_SIZE.java
@@ -119,9 +119,13 @@ public static void setDatastoreSize(String selectedStoreSize, Config config, Obj
_setDatastoreSize(selectedStoreSize, true, config, callback);
}
+ public static void setDatastoreSize(String selectedStoreSize, boolean chooseSaltHash, Config config, Object callback) {
+ _setDatastoreSize(selectedStoreSize, chooseSaltHash, config, callback);
+ }
+
private static void _setDatastoreSize(
String selectedStoreSize,
- boolean firsttime,
+ boolean chooseSaltHash,
Config config,
Object callback) {
try {
@@ -154,10 +158,10 @@ private static void _setDatastoreSize(
System.out.println("Setting datastore size to "+Fields.longToString(storeSize, true));
config.get("node").set("storeSize", Fields.longToString(storeSize, true));
- if (firsttime) config.get("node").set("storeType", "salt-hash");
+ if (chooseSaltHash) config.get("node").set("storeType", "salt-hash");
System.out.println("Setting client cache size to "+Fields.longToString(clientCacheSize, true));
config.get("node").set("clientCacheSize", Fields.longToString(clientCacheSize, true));
- if (firsttime) config.get("node").set("clientCacheType", "salt-hash");
+ if (chooseSaltHash) config.get("node").set("clientCacheType", "salt-hash");
System.out.println("Setting slashdot/ULPR/recent requests cache size to "+Fields.longToString(slashdotCacheSize, true));
config.get("node").set("slashdotCacheSize", Fields.longToString(slashdotCacheSize, true));
diff --git a/src/freenet/l10n/freenet.l10n.en.properties b/src/freenet/l10n/freenet.l10n.en.properties
index 9e3b11a52c5..b299a7d1e10 100644
--- a/src/freenet/l10n/freenet.l10n.en.properties
+++ b/src/freenet/l10n/freenet.l10n.en.properties
@@ -562,6 +562,7 @@ FirstTimeWizardToadlet.bandwidthMonthlyNetTransfer=Monthly Net Transfer
FirstTimeWizardToadlet.bandwidthCouldNotParse=Unable to parse limit "${limit}", please try again.
FirstTimeWizardToadlet.bandwidthErrorSettingTitle=Error Setting Bandwidth Limit
FirstTimeWizardToadlet.storage=Storage
+FirstTimeWizardToadlet.donateDiskSpace=Donate disk space to support the network?
FirstTimeWizardToadlet.storageLimitDescription=GiB of storage space to Freenet.
FirstTimeWizardToadlet.requirePassword=I want Freenet to require a password when it starts.
FirstTimeWizardToadlet.password=Password:
diff --git a/src/freenet/node/Node.java b/src/freenet/node/Node.java
index 70bc8849a70..2266bc9409d 100644
--- a/src/freenet/node/Node.java
+++ b/src/freenet/node/Node.java
@@ -268,10 +268,21 @@ private void migrateOldStore(StoreCallback old, Sto
public void closeOldStore(StoreCallback old) {
FreenetStore store = old.getStore();
+ _closeOldStore(store);
+ }
+
+ private static void _closeOldStore(FreenetStore store) {
if(store instanceof SaltedHashFreenetStore) {
SaltedHashFreenetStore saltstore = (SaltedHashFreenetStore) store;
saltstore.close();
saltstore.destruct();
+ } else if (store instanceof CachingFreenetStore) {
+ store.close();
+ FreenetStore underlyingStore = store.getUnderlyingStore();
+ if (underlyingStore instanceof SaltedHashFreenetStore) {
+ SaltedHashFreenetStore saltStore = (SaltedHashFreenetStore) underlyingStore;
+ saltStore.destruct();
+ }
}
}
@@ -359,12 +370,23 @@ public void set(String val) throws InvalidConfigValueException, NodeNeedRestartE
}
} else {
synchronized(Node.this) {
+ closeAllStores();
storeType = val;
}
throw new NodeNeedRestartException("Store type cannot be changed on the fly");
}
}
+ private void closeAllStores() {
+ // close the old stores so switching back to salt-hash doesn't run into locking trouble
+ closeOldStore(sskDatastore);
+ closeOldStore(pubKeyDatastore);
+ closeOldStore(chkDatastore);
+ closeOldStore(sskDatacache);
+ closeOldStore(pubKeyDatacache);
+ closeOldStore(chkDatacache);
+ }
+
@Override
public String[] getPossibleValues() {
return new String[] { "salt-hash", "ram" };
@@ -416,8 +438,10 @@ public void set(String val) throws InvalidConfigValueException, NodeNeedRestartE
throw new InvalidConfigValueException("Unable to create new store: "+e);
}
} else if(val.equals("ram")) {
+ closeAllClientCaches();
initRAMClientCacheFS();
} else /*if(val.equals("none")) */{
+ closeAllClientCaches();
initNoClientCacheFS();
}
@@ -427,6 +451,13 @@ public void set(String val) throws InvalidConfigValueException, NodeNeedRestartE
}
}
+ private void closeAllClientCaches() {
+ // close the old client caches so switching back to salt-hash doesn't run into locking trouble
+ closeOldStore(sskClientcache);
+ closeOldStore(pubKeyClientcache);
+ closeOldStore(chkClientcache);
+ }
+
@Override
public String[] getPossibleValues() {
return new String[] { "salt-hash", "ram", "none" };
diff --git a/src/freenet/store/saltedhash/SaltedHashFreenetStore.java b/src/freenet/store/saltedhash/SaltedHashFreenetStore.java
index 51a4c8692dd..dc854bf48bb 100644
--- a/src/freenet/store/saltedhash/SaltedHashFreenetStore.java
+++ b/src/freenet/store/saltedhash/SaltedHashFreenetStore.java
@@ -11,6 +11,7 @@
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
+import java.nio.channels.FileLock;
import java.nio.channels.OverlappingFileLockException;
import java.util.Arrays;
import java.util.Deque;
@@ -571,10 +572,13 @@ private boolean onWrite() {
private File metaFile;
private RandomAccessFile metaRAF;
private FileChannel metaFC;
+ private FileLock metaFCLock;
+
// header+data file
private File hdFile;
private RandomAccessFile hdRAF;
private FileChannel hdFC;
+ private FileLock hdFCLock;
private final int hdPadding;
/**
@@ -842,7 +846,7 @@ private boolean openStoreFiles(File baseDir, String name) throws IOException {
metaFC = metaRAF.getChannel();
try {
- metaFC.lock();
+ metaFCLock = metaFC.lock();
} catch(OverlappingFileLockException ex) {
throw new Error("Could not aquire lock for file " + baseDir.toPath().resolve(name + ".metadata"), ex);
}
@@ -850,7 +854,7 @@ private boolean openStoreFiles(File baseDir, String name) throws IOException {
hdRAF = new RandomAccessFile(hdFile, "rw");
hdFC = hdRAF.getChannel();
try {
- hdFC.lock();
+ hdFCLock = hdFC.lock();
} catch(OverlappingFileLockException ex) {
throw new Error("Could not aquire lock for file " + baseDir.toPath().resolve(name + ".hd"), ex);
}
@@ -1040,12 +1044,18 @@ private void flushAndClose(boolean abort) {
Logger.normal(this, "Flush and closing this store: " + name);
try {
metaFC.force(true);
+ if (metaFCLock.isValid()) {
+ metaFCLock.release();
+ }
metaFC.close();
} catch (Exception e) {
Logger.error(this, "error flusing store", e);
}
try {
hdFC.force(true);
+ if (hdFCLock.isValid()) {
+ hdFCLock.release();
+ }
hdFC.close();
} catch (Exception e) {
Logger.error(this, "error flusing store", e);