소스 검색

Throttle calls to Settings.save()

Sebastian Stenzel 9 년 전
부모
커밋
08f664e3df

+ 1 - 0
main/ui/src/main/java/org/cryptomator/ui/ExitUtil.java

@@ -136,6 +136,7 @@ class ExitUtil {
 			return;
 		} else {
 			settings.setNumTrayNotifications(settings.getNumTrayNotifications() - 1);
+			settings.save();
 		}
 		final Runnable notificationCmd;
 		if (SystemUtils.IS_OS_MAC_OSX) {

+ 4 - 0
main/ui/src/main/java/org/cryptomator/ui/controllers/MainController.java

@@ -40,6 +40,7 @@ import javafx.beans.binding.BooleanBinding;
 import javafx.beans.property.ObjectProperty;
 import javafx.beans.property.SimpleObjectProperty;
 import javafx.collections.FXCollections;
+import javafx.collections.ListChangeListener.Change;
 import javafx.collections.ObservableList;
 import javafx.event.ActionEvent;
 import javafx.fxml.FXML;
@@ -94,6 +95,9 @@ public class MainController extends LocalizedFXMLViewController {
 		this.changePasswordController = changePasswordController;
 		this.settingsController = settingsController;
 		this.vaults = FXCollections.observableList(settings.getDirectories());
+		this.vaults.addListener((Change<? extends Vault> c) -> {
+			settings.save();
+		});
 
 		// derived bindings:
 		this.isShowingSettings = activeController.isEqualTo(settingsController.get());

+ 15 - 6
main/ui/src/main/java/org/cryptomator/ui/controllers/SettingsController.java

@@ -59,9 +59,9 @@ public class SettingsController extends LocalizedFXMLViewController {
 		useIpv6Checkbox.setSelected(SystemUtils.IS_OS_WINDOWS && settings.shouldUseIpv6());
 		versionLabel.setText(String.format(localization.getString("settings.version.label"), applicationVersion().orElse("SNAPSHOT")));
 
-		EasyBind.subscribe(checkForUpdatesCheckbox.selectedProperty(), settings::setCheckForUpdatesEnabled);
+		EasyBind.subscribe(checkForUpdatesCheckbox.selectedProperty(), this::checkForUpdateDidChange);
 		EasyBind.subscribe(portField.textProperty(), this::portDidChange);
-		EasyBind.subscribe(useIpv6Checkbox.selectedProperty(), settings::setUseIpv6);
+		EasyBind.subscribe(useIpv6Checkbox.selectedProperty(), this::useIpv6DidChange);
 	}
 
 	@Override
@@ -73,21 +73,30 @@ public class SettingsController extends LocalizedFXMLViewController {
 		return Optional.ofNullable(getClass().getPackage().getImplementationVersion());
 	}
 
+	private void checkForUpdateDidChange(Boolean newValue) {
+		settings.setCheckForUpdatesEnabled(newValue);
+		settings.save();
+	}
+
 	private void portDidChange(String newValue) {
 		try {
 			int port = Integer.parseInt(newValue);
-			if (port < Settings.MIN_PORT) {
+			if (port < Settings.MIN_PORT || port > Settings.MAX_PORT) {
 				settings.setPort(Settings.DEFAULT_PORT);
-			} else if (port < Settings.MAX_PORT) {
-				settings.setPort(port);
 			} else {
-				portField.setText(String.valueOf(Settings.MAX_PORT));
+				settings.setPort(port);
+				settings.save();
 			}
 		} catch (NumberFormatException e) {
 			portField.setText(String.valueOf(Settings.DEFAULT_PORT));
 		}
 	}
 
+	private void useIpv6DidChange(Boolean newValue) {
+		settings.setUseIpv6(newValue);
+		settings.save();
+	}
+
 	private void filterNumericKeyEvents(KeyEvent t) {
 		if (t.getCharacter() == null || t.getCharacter().length() == 0) {
 			return;

+ 2 - 22
main/ui/src/main/java/org/cryptomator/ui/settings/Settings.java

@@ -48,26 +48,11 @@ public class Settings implements Serializable {
 	/**
 	 * Package-private constructor; use {@link SettingsProvider}.
 	 */
-	Settings() {
-		this.saveCmd = s -> {
-		};
-	}
-
-	private Settings(Consumer<Settings> saveCmd) {
+	Settings(Consumer<Settings> saveCmd) {
 		this.saveCmd = saveCmd;
 	}
 
-	Settings withSaveCmd(Consumer<Settings> saveCmd) {
-		final Settings result = new Settings(saveCmd);
-		result.directories = this.directories;
-		result.checkForUpdatesEnabled = this.checkForUpdatesEnabled;
-		result.port = this.port;
-		result.useIpv6 = this.useIpv6;
-		result.numTrayNotifications = this.numTrayNotifications;
-		return result;
-	}
-
-	private void save() {
+	public void save() {
 		saveCmd.accept(this);
 	}
 
@@ -82,7 +67,6 @@ public class Settings implements Serializable {
 
 	public void setDirectories(List<Vault> directories) {
 		this.directories = directories;
-		save();
 	}
 
 	public boolean isCheckForUpdatesEnabled() {
@@ -92,7 +76,6 @@ public class Settings implements Serializable {
 
 	public void setCheckForUpdatesEnabled(boolean checkForUpdatesEnabled) {
 		this.checkForUpdatesEnabled = checkForUpdatesEnabled;
-		save();
 	}
 
 	public void setPort(int port) {
@@ -100,7 +83,6 @@ public class Settings implements Serializable {
 			throw new IllegalArgumentException("Invalid port");
 		}
 		this.port = port;
-		save();
 	}
 
 	public int getPort() {
@@ -121,7 +103,6 @@ public class Settings implements Serializable {
 
 	public void setUseIpv6(boolean useIpv6) {
 		this.useIpv6 = useIpv6;
-		save();
 	}
 
 	public Integer getNumTrayNotifications() {
@@ -130,7 +111,6 @@ public class Settings implements Serializable {
 
 	public void setNumTrayNotifications(Integer numTrayNotifications) {
 		this.numTrayNotifications = numTrayNotifications;
-		save();
 	}
 
 }

+ 24 - 5
main/ui/src/main/java/org/cryptomator/ui/settings/SettingsProvider.java

@@ -16,6 +16,12 @@ import java.nio.file.FileSystems;
 import java.nio.file.Files;
 import java.nio.file.Path;
 import java.nio.file.StandardOpenOption;
+import java.util.Objects;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.ScheduledFuture;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicReference;
 
 import javax.inject.Inject;
 import javax.inject.Named;
@@ -34,6 +40,7 @@ public class SettingsProvider implements Provider<Settings> {
 	private static final Logger LOG = LoggerFactory.getLogger(SettingsProvider.class);
 	private static final Path SETTINGS_DIR;
 	private static final String SETTINGS_FILE = "settings.json";
+	private static final long SAVE_DELAY_MS = 1000;
 
 	static {
 		final String appdata = System.getenv("APPDATA");
@@ -52,6 +59,8 @@ public class SettingsProvider implements Provider<Settings> {
 	}
 
 	private final ObjectMapper objectMapper;
+	private final ScheduledExecutorService saveScheduler = Executors.newSingleThreadScheduledExecutor();
+	private final AtomicReference<ScheduledFuture<?>> scheduledSaveCmd = new AtomicReference<>();
 
 	@Inject
 	public SettingsProvider(@Named("VaultJsonMapper") ObjectMapper objectMapper) {
@@ -69,23 +78,33 @@ public class SettingsProvider implements Provider<Settings> {
 
 	@Override
 	public Settings get() {
-		Settings settings = null;
+		final Settings settings = new Settings(this::scheduleSave);
 		try {
 			final Path settingsPath = getSettingsPath();
 			final InputStream in = Files.newInputStream(settingsPath, StandardOpenOption.READ);
-			settings = objectMapper.readValue(in, Settings.class);
+			objectMapper.readerForUpdating(settings).readValue(in);
 			LOG.info("Settings loaded from " + settingsPath);
 		} catch (IOException e) {
 			LOG.info("Failed to load settings, creating new one.");
-			settings = new Settings();
 		}
-		return settings.withSaveCmd(this::save);
+		return settings;
 	}
 
-	private void save(Settings settings) {
+	private void scheduleSave(Settings settings) {
 		if (settings == null) {
 			return;
 		}
+		ScheduledFuture<?> saveCmd = saveScheduler.schedule(() -> {
+			this.save(settings);
+		} , SAVE_DELAY_MS, TimeUnit.MILLISECONDS);
+		ScheduledFuture<?> previousSaveCmd = scheduledSaveCmd.getAndSet(saveCmd);
+		if (previousSaveCmd != null) {
+			previousSaveCmd.cancel(false);
+		}
+	}
+
+	private void save(Settings settings) {
+		Objects.requireNonNull(settings);
 		try {
 			final Path settingsPath = getSettingsPath();
 			Files.createDirectories(settingsPath.getParent());