fix: Handle storage correctly on software upgrade.

On 2.0.0 the persistent data was inconsistently stored with electron-settings.
It is now saved separately for each version and an upgrade path is defined for each upgrade.
This commit is contained in:
Quentin Decaunes 2020-03-17 15:48:42 +01:00
parent 8dd94fd126
commit e178e86062
10 changed files with 157 additions and 33 deletions

View File

@ -4,6 +4,7 @@ const electron = require("electron");
const path = require("path"); const path = require("path");
const isDev = require("electron-is-dev"); const isDev = require("electron-is-dev");
const electronSettings = require("electron-settings"); const electronSettings = require("electron-settings");
const upgrader = require('./upgrader.js');
const app = electron.app; const app = electron.app;
const Tray = electron.Tray; const Tray = electron.Tray;
@ -13,10 +14,12 @@ const app_version_as_string = app.getVersion().replace(/\./g, "_") + (isDev ? "-
let mainWindow; let mainWindow;
let tray; let tray;
upgrader();
const currentSettings = () => { const currentSettings = () => {
const localStorage = electronSettings.get(app_version_as_string); const localStorage = electronSettings.get(app_version_as_string);
if (localStorage) { if (localStorage) {
return localStorage.settings; return localStorage.appContext.settings;
} }
return false; return false;
}; };

101
public/upgrader.js Normal file
View File

@ -0,0 +1,101 @@
const electronSettings = require("electron-settings");
const electron = require("electron");
const isDev = require("electron-is-dev");
const app = electron.app;
const app_version_as_string = app.getVersion().replace(/\./g, "_") + (isDev ? "-dev" : "");
function storageHaveCurrentVersion() {
return typeof electronSettings.get(app_version_as_string) === "object";
}
function getAllAvailableVersion() {
var keys = Object.keys(electronSettings.getAll());
var versionAsRegex = isDev
? /^\d*_\d*_\d*(-dev)$/
: /^\d*_\d*_\d*$/;
var validKeys = keys.filter((val, index) => {
return versionAsRegex.test(val);
});
validKeys.push(app_version_as_string);
validKeys.sort((a, b) => {
return a.localeCompare(b);
});
return validKeys;
}
function storageHasPreviousVersion() {
var validKeys = getAllAvailableVersion();
return validKeys.indexOf(app_version_as_string) > 0;
}
function getPreviousVersion() {
var validKeys = getAllAvailableVersion();
const index = validKeys.indexOf(app_version_as_string);
return validKeys[index-1];
}
function from_2_0_0_MoveAppContext(previousVersion) {
electronSettings.set(
`${app_version_as_string}.appContext`,
electronSettings.get(previousVersion)
);
}
function from_2_0_0_MoveLightMode() {
electronSettings.set(
`${app_version_as_string}.lightMode`,
electronSettings.get("lightMode")
);
}
function from_2_0_0_MoveLocale() {
electronSettings.set(
`${app_version_as_string}.locale`,
electronSettings.get("locale")
);
}
function from_2_0_0_MoveReApplyPeriodically() {
electronSettings.set(
`${app_version_as_string}.reApplyPeriodically`,
electronSettings.get("reApplyPeriodically")
);
}
function from_2_0_0_MoveVotedPresets() {
electronSettings.set(
`${app_version_as_string}.votedPresets`,
electronSettings.get("votedPresets")
);
}
function upgradeFromPreviousVersion() {
isDev && console.log('Upgrading storage...');
const previousVersion = getPreviousVersion();
switch (previousVersion) {
case "2_0_0":
case "2_0_0-dev":
from_2_0_0_MoveAppContext(previousVersion);
from_2_0_0_MoveLightMode(previousVersion);
from_2_0_0_MoveLocale(previousVersion);
from_2_0_0_MoveReApplyPeriodically(previousVersion);
from_2_0_0_MoveVotedPresets(previousVersion);
isDev && console.log('... from 2.0.0');
break;
default:
break;
}
}
const upgrader = () => {
if (storageHaveCurrentVersion()) {
// No need to upgrade.
return;
}
if (!storageHasPreviousVersion()) {
// No need to upgrade.
return;
}
upgradeFromPreviousVersion();
};
module.exports = upgrader;

View File

@ -8,11 +8,13 @@ import SysInfoContext, {
SysInfoState, SysInfoState,
createPermissiveMachineSignature, createPermissiveMachineSignature,
} from "./contexts/SysInfoContext"; } from "./contexts/SysInfoContext";
import LightModeContext from "./contexts/LightModeContext"; import LightModeContext, { lightModeSettingsKey } from "./contexts/LightModeContext";
import { checkNewVersion } from "./contexts/RyzenControllerAppContext"; import { checkNewVersion } from "./contexts/RyzenControllerAppContext";
import LocaleContext, { getTranslation } from "./contexts/LocaleContext"; import LocaleContext, { getTranslation, localeSettingsKey } from "./contexts/LocaleContext";
import LocaleSelectorModal from "./components/LocaleSelectorModal"; import LocaleSelectorModal from "./components/LocaleSelectorModal";
const si = window.require("systeminformation"); const si = window.require("systeminformation");
const electronSettings = window.require("electron-settings");
type AppState = { type AppState = {
sysinfo: SysInfoState; sysinfo: SysInfoState;
@ -55,9 +57,9 @@ class App extends React.Component<{}, AppState> {
}; };
switchLightMode(): void { switchLightMode(): void {
let lightMode = window.require("electron-settings").get("lightMode") || "light"; let lightMode = electronSettings.get(lightModeSettingsKey) || "light";
lightMode = lightMode === "light" ? "dark" : "light"; lightMode = lightMode === "light" ? "dark" : "light";
window.require("electron-settings").set("lightMode", lightMode); electronSettings.set(lightModeSettingsKey, lightMode);
this.setState({ this.setState({
lightMode: { lightMode: {
mode: lightMode, mode: lightMode,
@ -67,16 +69,16 @@ class App extends React.Component<{}, AppState> {
} }
changeLocale(to: AvailableLanguages): void { changeLocale(to: AvailableLanguages): void {
window.require("electron-settings").set("locale", to); electronSettings.set(localeSettingsKey, to);
window.location.reload(); window.location.reload();
} }
getLatestLightMode(): "light" | "dark" { getLatestLightMode(): "light" | "dark" {
return window.require("electron-settings").get("lightMode") || "light"; return electronSettings.get(lightModeSettingsKey) || "light";
} }
getLatestLocale(): AvailableLanguages { getLatestLocale(): AvailableLanguages {
return window.require("electron-settings").get("locale") || "en"; return electronSettings.get(localeSettingsKey) || "en";
} }
componentDidMount() { componentDidMount() {

View File

@ -3,6 +3,7 @@ import logo from "../assets/icon.png";
import Badge from "./Badge"; import Badge from "./Badge";
import LightModeContext from "../contexts/LightModeContext"; import LightModeContext from "../contexts/LightModeContext";
import { getTranslation } from "../contexts/LocaleContext"; import { getTranslation } from "../contexts/LocaleContext";
import AppVersion from "../contexts/AppVersion";
function TopBar() { function TopBar() {
return ( return (
@ -18,7 +19,7 @@ function TopBar() {
</div> </div>
<Badge <Badge
className="uk-margin-left" className="uk-margin-left"
value={process.env.REACT_APP_VERSION || "dev"} value={AppVersion.semver}
onClick={openExternal("https://gitlab.com/ryzen-controller-team/ryzen-controller/releases")} onClick={openExternal("https://gitlab.com/ryzen-controller-team/ryzen-controller/releases")}
background="#EE0000" background="#EE0000"
/> />

View File

@ -0,0 +1,6 @@
export default {
string: process.env?.REACT_APP_VERSION?.replace(/\./g, "_") || "dev",
semver: process.env?.REACT_APP_VERSION || "dev",
isDev: process.env?.REACT_APP_VERSION?.indexOf("dev") !== -1,
};

View File

@ -1,4 +1,7 @@
import { createContext } from "react"; import { createContext } from "react";
import AppVersion from "./AppVersion";
const lightModeSettingsKey = `${AppVersion.string}.lightMode`;
const LightModeContext = createContext({ const LightModeContext = createContext({
mode: "light", mode: "light",
@ -6,4 +9,5 @@ const LightModeContext = createContext({
}); });
LightModeContext.displayName = "LightModeContext"; LightModeContext.displayName = "LightModeContext";
export { lightModeSettingsKey };
export default LightModeContext; export default LightModeContext;

View File

@ -1,8 +1,10 @@
import { createContext } from "react"; import { createContext } from "react";
import LocaleTranslations from "../locales/LocaleTranslations"; import LocaleTranslations from "../locales/LocaleTranslations";
import AppVersion from "./AppVersion";
const fs = window.require("fs"); const fs = window.require("fs");
const electronSettings = window.require("electron-settings"); const electronSettings = window.require("electron-settings");
const localeSettingsKey = `${AppVersion.string}.locale`;
const LocaleContext = createContext({ const LocaleContext = createContext({
is: "en", is: "en",
@ -26,12 +28,12 @@ function addKeyToLocale(_id: string, _currentLocale: string, _fallback: string |
let localeFile = `src/locales/${_currentLocale}.json`; let localeFile = `src/locales/${_currentLocale}.json`;
let inter = setInterval(() => { let inter = setInterval(() => {
let lock = window.require("electron-settings").get("lock"); let lock = electronSettings.get("lock");
if (lock) { if (lock) {
return; return;
} }
clearInterval(inter); clearInterval(inter);
window.require("electron-settings").set("lock", true); electronSettings.set("lock", true);
console.log(`Writting key ${id} to locale ${currentLocale}...`); console.log(`Writting key ${id} to locale ${currentLocale}...`);
fs.readFile(localeFile, (err: string | null, data: string) => { fs.readFile(localeFile, (err: string | null, data: string) => {
if (err) { if (err) {
@ -44,7 +46,7 @@ function addKeyToLocale(_id: string, _currentLocale: string, _fallback: string |
localeTranslation[id] = fallback; localeTranslation[id] = fallback;
} }
fs.writeFile(localeFile, JSON.stringify(localeTranslation, null, 4), function(err: string | null) { fs.writeFile(localeFile, JSON.stringify(localeTranslation, null, 4), function(err: string | null) {
window.require("electron-settings").delete("lock"); electronSettings.delete("lock");
if (err) { if (err) {
console.log("error", err); console.log("error", err);
return; return;
@ -73,13 +75,13 @@ function addKeyToLocale(_id: string, _currentLocale: string, _fallback: string |
* @param variables Variables to replace in the sentence * @param variables Variables to replace in the sentence
*/ */
function getTranslation(id: string, fallback?: string, variables?: Record<string, string>): string { function getTranslation(id: string, fallback?: string, variables?: Record<string, string>): string {
const currentLocale = electronSettings.get("locale") ? (electronSettings.get("locale") as AvailableLanguages) : "en"; const currentLocale = electronSettings.get(localeSettingsKey) ? (electronSettings.get(localeSettingsKey) as AvailableLanguages) : "en";
var sentence: string | undefined = LocaleTranslations[currentLocale][id]; var sentence: string | undefined = LocaleTranslations[currentLocale][id];
if (!sentence && sentence !== "") { if (!sentence && sentence !== "") {
console.warn(`Missing translation for ${id} in locale ${currentLocale}.`); console.warn(`Missing translation for ${id} in locale ${currentLocale}.`);
if (process.env.REACT_APP_VERSION?.indexOf("-dev") !== -1) { if (AppVersion.isDev) {
addKeyToLocale(id, currentLocale, fallback); addKeyToLocale(id, currentLocale, fallback);
} }
} }
@ -100,5 +102,5 @@ function getTranslation(id: string, fallback?: string, variables?: Record<string
return sentence; return sentence;
} }
export { getTranslation }; export { localeSettingsKey, getTranslation };
export default LocaleContext; export default LocaleContext;

View File

@ -4,10 +4,13 @@ import { isNumber } from "util";
import compareVersions from "compare-versions"; import compareVersions from "compare-versions";
import NotificationContext from "./NotificationContext"; import NotificationContext from "./NotificationContext";
import { getTranslation } from "./LocaleContext"; import { getTranslation } from "./LocaleContext";
import AppVersion from './AppVersion';
const isDev = window.require("electron-is-dev"); const isDev = window.require("electron-is-dev");
const electronSettings = window.require("electron-settings"); const electronSettings = window.require("electron-settings");
const reApplyPeriodicallySettingsKey = `${AppVersion.string}.reApplyPeriodically`;
const appContextSettingsKey = `${AppVersion.string}.appContext`;
const fileSystem = window.require("fs"); const fileSystem = window.require("fs");
const app_version_as_string = process.env?.REACT_APP_VERSION?.replace(/\./g, "_") || "dev";
const defaultPreset = { const defaultPreset = {
"--slow-time=": { enabled: false, value: getOptionDefinition("--slow-time=").default }, "--slow-time=": { enabled: false, value: getOptionDefinition("--slow-time=").default },
@ -26,7 +29,7 @@ const defaultPreset = {
const getRyzenAdjExecutablePath = function(): string { const getRyzenAdjExecutablePath = function(): string {
const cwd = window.require("electron").remote.app.getAppPath(); const cwd = window.require("electron").remote.app.getAppPath();
let path: string | undefined = electronSettings.get(app_version_as_string)?.settings?.ryzenAdjPath; let path: string | undefined = electronSettings.get(appContextSettingsKey)?.settings?.ryzenAdjPath;
if (path) { if (path) {
return path; return path;
@ -162,10 +165,10 @@ const RyzenControllerSettingsDefinitions: RyzenControllerSettingDefinitionList =
} }
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
try { try {
let intervalId = electronSettings.get("reApplyPeriodically"); let intervalId = electronSettings.get(reApplyPeriodicallySettingsKey);
clearInterval(intervalId); clearInterval(intervalId);
intervalId = false; intervalId = false;
electronSettings.set("reApplyPeriodically", false); electronSettings.set(reApplyPeriodicallySettingsKey, false);
if (parsedSeconds <= 0) { if (parsedSeconds <= 0) {
resolve(false); resolve(false);
@ -173,9 +176,9 @@ const RyzenControllerSettingsDefinitions: RyzenControllerSettingDefinitionList =
} }
electronSettings.set( electronSettings.set(
"reApplyPeriodically", reApplyPeriodicallySettingsKey,
setInterval(() => { setInterval(() => {
let preset = electronSettings.get(app_version_as_string).currentSettings; let preset = electronSettings.get(appContextSettingsKey).currentSettings;
if (!isPresetValid(preset)) { if (!isPresetValid(preset)) {
return; return;
} }
@ -336,7 +339,7 @@ const isPresetValid = function(preset: PartialRyzenAdjOptionListType): boolean {
return true; return true;
}; };
let loadedContext = window.require("electron-settings").get(app_version_as_string); let loadedContext = electronSettings.get(appContextSettingsKey);
let context = defaultRyzenControllerAppContext; let context = defaultRyzenControllerAppContext;
if (loadedContext) { if (loadedContext) {
context = { context = {
@ -353,11 +356,11 @@ const persistentSave = function(context: RyzenControllerAppContextType) {
...context, ...context,
currentSettings: context.latestSettings, currentSettings: context.latestSettings,
}; };
window.require("electron-settings").set(app_version_as_string, savedContext); electronSettings.set(appContextSettingsKey, savedContext);
}; };
const executeRyzenAdjUsingPreset = function(presetName: string): boolean { const executeRyzenAdjUsingPreset = function(presetName: string): boolean {
const presets = electronSettings.get(app_version_as_string)?.presets; const presets = electronSettings.get(appContextSettingsKey)?.presets;
if (!presets.hasOwnProperty(presetName)) { if (!presets.hasOwnProperty(presetName)) {
return false; return false;
} }
@ -407,7 +410,6 @@ export {
RyzenControllerSettingsDefinitions, RyzenControllerSettingsDefinitions,
getSettingDefinition, getSettingDefinition,
getRyzenAdjExecutablePath, getRyzenAdjExecutablePath,
app_version_as_string,
executeRyzenAdjUsingPreset, executeRyzenAdjUsingPreset,
checkIfNewerReleaseExist as checkNewVersion, checkIfNewerReleaseExist as checkNewVersion,
isPresetValid, isPresetValid,

View File

@ -8,7 +8,10 @@ import PresetOnline from "../components/PresetOnline";
import NotificationContext from "../contexts/NotificationContext"; import NotificationContext from "../contexts/NotificationContext";
import PresetsOnlineContext from "../contexts/PresetsOnline"; import PresetsOnlineContext from "../contexts/PresetsOnline";
import { getTranslation } from "../contexts/LocaleContext"; import { getTranslation } from "../contexts/LocaleContext";
import AppVersion from "../contexts/AppVersion";
const uikit = window.require("uikit"); const uikit = window.require("uikit");
const electronSettings = window.require("electron-settings");
const votedPresetsSettingsKey = `${AppVersion.string}.votedPresets`;
class PresetsScene extends React.Component<{}, PresetsOnlineContextType> { class PresetsScene extends React.Component<{}, PresetsOnlineContextType> {
_isMounted = false; _isMounted = false;
@ -165,7 +168,7 @@ class PresetsScene extends React.Component<{}, PresetsOnlineContextType> {
} }
isUserAlreadyVotedForThisPreset(presetId: number): boolean { isUserAlreadyVotedForThisPreset(presetId: number): boolean {
const votedPresets: Array<number> = window.require("electron-settings").get("votedPresets"); const votedPresets: Array<number> = electronSettings.get(votedPresetsSettingsKey);
if (!votedPresets) { if (!votedPresets) {
return false; return false;
} }
@ -173,12 +176,12 @@ class PresetsScene extends React.Component<{}, PresetsOnlineContextType> {
} }
retainVotedPreset(presetId: number): void { retainVotedPreset(presetId: number): void {
let votedPresets = window.require("electron-settings").get("votedPresets"); let votedPresets = electronSettings.get(votedPresetsSettingsKey);
if (!votedPresets) { if (!votedPresets) {
votedPresets = []; votedPresets = [];
} }
votedPresets.push(presetId); votedPresets.push(presetId);
window.require("electron-settings").set("votedPresets", votedPresets); electronSettings.set(votedPresetsSettingsKey, votedPresets);
} }
componentDidMount() { componentDidMount() {

View File

@ -5,7 +5,6 @@ import RyzenControllerAppContext, {
defaultPreset, defaultPreset,
persistentSave, persistentSave,
getSettingDefinition, getSettingDefinition,
app_version_as_string,
executeRyzenAdjUsingPreset, executeRyzenAdjUsingPreset,
} from "../contexts/RyzenControllerAppContext"; } from "../contexts/RyzenControllerAppContext";
import RyzenAdjScene from "../scenes/RyzenAdjScene"; import RyzenAdjScene from "../scenes/RyzenAdjScene";
@ -13,6 +12,7 @@ import PresetsScene from "../scenes/PresetsScene";
import SettingsScene from "../scenes/SettingsScene"; import SettingsScene from "../scenes/SettingsScene";
import NotificationContext from "../contexts/NotificationContext"; import NotificationContext from "../contexts/NotificationContext";
import { getTranslation } from "../contexts/LocaleContext"; import { getTranslation } from "../contexts/LocaleContext";
import AppVersion from "../contexts/AppVersion";
const electronSettings = window.require("electron-settings"); const electronSettings = window.require("electron-settings");
const powerMonitor = window.require("electron").remote.powerMonitor; const powerMonitor = window.require("electron").remote.powerMonitor;
@ -27,7 +27,7 @@ class Scene extends React.Component<{}, RyzenControllerAppContextType> {
}; };
componentDidMount() { componentDidMount() {
let settings = electronSettings.get(app_version_as_string); let settings = electronSettings.get(AppVersion.string);
if (settings) { if (settings) {
settings = settings.settings; settings = settings.settings;
} else { } else {
@ -58,7 +58,7 @@ class Scene extends React.Component<{}, RyzenControllerAppContextType> {
} }
powerMonitor.on("unlock-screen", () => { powerMonitor.on("unlock-screen", () => {
const presetName = electronSettings.get(app_version_as_string)?.settings?.onSessionResume; const presetName = electronSettings.get(AppVersion.string)?.settings?.onSessionResume;
if (presetName) { if (presetName) {
executeRyzenAdjUsingPreset(presetName); executeRyzenAdjUsingPreset(presetName);
} }
@ -75,14 +75,14 @@ class Scene extends React.Component<{}, RyzenControllerAppContextType> {
handleBatteryStatusChange() { handleBatteryStatusChange() {
powerMonitor.on("on-ac", () => { powerMonitor.on("on-ac", () => {
const presetName = electronSettings.get(app_version_as_string)?.settings?.onLaptopPluggedIn; const presetName = electronSettings.get(AppVersion.string)?.settings?.onLaptopPluggedIn;
if (presetName !== false && presetName !== "") { if (presetName !== false && presetName !== "") {
executeRyzenAdjUsingPreset(presetName); executeRyzenAdjUsingPreset(presetName);
} }
}); });
powerMonitor.on("on-battery", () => { powerMonitor.on("on-battery", () => {
const presetName = electronSettings.get(app_version_as_string)?.settings?.onLaptopPluggedOut; const presetName = electronSettings.get(AppVersion.string)?.settings?.onLaptopPluggedOut;
if (presetName !== false && presetName !== "") { if (presetName !== false && presetName !== "") {
executeRyzenAdjUsingPreset(presetName); executeRyzenAdjUsingPreset(presetName);
} }