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") }}

{{ l10n("storage") }} + +

+

{% 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);