Persist settings in ${XDG_CONFIG_HOME}/flipper/settings
Summary: Moves the settings state from electron local storage into a json file in the users configured config location. Unless modified by the user, this will usually be `~/.config/flipper/settings.json` Settings will now persist across re-installs, and can now be easily inspected and backed up. Reviewed By: passy Differential Revision: D17712687 fbshipit-source-id: 1e778063e41d0a1a86145817b9797bf0458121da
This commit is contained in:
committed by
Facebook Github Bot
parent
7775e82851
commit
85c0ec0d13
@@ -163,6 +163,6 @@ class SignInSheet extends Component<Props, State> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default connect<StateFromProps, DispatchFromProps, OwnProps, Store>(
|
export default connect<StateFromProps, DispatchFromProps, OwnProps, Store>(
|
||||||
({settingsState}) => ({settings: settingsState.settings}),
|
({settingsState}) => ({settings: settingsState}),
|
||||||
{updateSettings},
|
{updateSettings},
|
||||||
)(SignInSheet);
|
)(SignInSheet);
|
||||||
|
|||||||
@@ -27,11 +27,14 @@ import plugins, {
|
|||||||
Action as PluginsAction,
|
Action as PluginsAction,
|
||||||
} from './plugins';
|
} from './plugins';
|
||||||
import settings, {
|
import settings, {
|
||||||
State as SettingsState,
|
Settings as SettingsState,
|
||||||
Action as SettingsAction,
|
Action as SettingsAction,
|
||||||
} from './settings';
|
} from './settings';
|
||||||
import user, {State as UserState, Action as UserAction} from './user';
|
import user, {State as UserState, Action as UserAction} from './user';
|
||||||
|
import JsonFileStorage from '../utils/jsonFileReduxPersistStorage';
|
||||||
|
import os from 'os';
|
||||||
|
import {resolve} from 'path';
|
||||||
|
import xdg from 'xdg-basedir';
|
||||||
import {persistReducer, PersistPartial} from 'redux-persist';
|
import {persistReducer, PersistPartial} from 'redux-persist';
|
||||||
|
|
||||||
import {Store as ReduxStore, MiddlewareAPI as ReduxMiddlewareAPI} from 'redux';
|
import {Store as ReduxStore, MiddlewareAPI as ReduxMiddlewareAPI} from 'redux';
|
||||||
@@ -61,6 +64,14 @@ export type State = {
|
|||||||
export type Store = ReduxStore<State, Actions>;
|
export type Store = ReduxStore<State, Actions>;
|
||||||
export type MiddlewareAPI = ReduxMiddlewareAPI<Dispatch<Actions>, State>;
|
export type MiddlewareAPI = ReduxMiddlewareAPI<Dispatch<Actions>, State>;
|
||||||
|
|
||||||
|
const settingsStorage = new JsonFileStorage(
|
||||||
|
resolve(
|
||||||
|
...(xdg.config ? [xdg.config] : [os.homedir(), '.config']),
|
||||||
|
'flipper',
|
||||||
|
'settings.json',
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
export default combineReducers<State, Actions>({
|
export default combineReducers<State, Actions>({
|
||||||
application: persistReducer<ApplicationState, Actions>(
|
application: persistReducer<ApplicationState, Actions>(
|
||||||
{
|
{
|
||||||
@@ -101,7 +112,7 @@ export default combineReducers<State, Actions>({
|
|||||||
user,
|
user,
|
||||||
),
|
),
|
||||||
settingsState: persistReducer(
|
settingsState: persistReducer(
|
||||||
{key: 'settings', storage, whitelist: ['settings']},
|
{key: 'settings', storage: settingsStorage},
|
||||||
settings,
|
settings,
|
||||||
),
|
),
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -11,10 +11,6 @@ export type Settings = {
|
|||||||
androidHome: string;
|
androidHome: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type State = {
|
|
||||||
settings: Settings;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type Action =
|
export type Action =
|
||||||
| {type: 'INIT'}
|
| {type: 'INIT'}
|
||||||
| {
|
| {
|
||||||
@@ -22,21 +18,16 @@ export type Action =
|
|||||||
payload: Settings;
|
payload: Settings;
|
||||||
};
|
};
|
||||||
|
|
||||||
const initialState: State = {
|
const initialState: Settings = {
|
||||||
settings: {
|
|
||||||
androidHome: '/opt/android_sdk',
|
androidHome: '/opt/android_sdk',
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function reducer(
|
export default function reducer(
|
||||||
state: State = initialState,
|
state: Settings = initialState,
|
||||||
action: Actions,
|
action: Actions,
|
||||||
): State {
|
): Settings {
|
||||||
if (action.type === 'UPDATE_SETTINGS') {
|
if (action.type === 'UPDATE_SETTINGS') {
|
||||||
return {
|
return action.payload;
|
||||||
...state,
|
|
||||||
settings: action.payload,
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,9 @@
|
|||||||
* @format
|
* @format
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {promises} from 'fs';
|
import {promises, exists} from 'fs';
|
||||||
|
import path from 'path';
|
||||||
|
import {promisify} from 'util';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Redux-persist storage engine for storing state in a human readable JSON file.
|
* Redux-persist storage engine for storing state in a human readable JSON file.
|
||||||
@@ -28,14 +30,12 @@ export default class JsonFileStorage {
|
|||||||
.then(buffer => buffer.toString())
|
.then(buffer => buffer.toString())
|
||||||
.then(this.deserializeValue)
|
.then(this.deserializeValue)
|
||||||
.catch(e => {
|
.catch(e => {
|
||||||
console.error(
|
console.warn(
|
||||||
`Failed to read settings file: "${
|
`Failed to read settings file: "${
|
||||||
this.filepath
|
this.filepath
|
||||||
}". ${e}. Replacing file with default settings.`,
|
}". ${e}. Replacing file with default settings.`,
|
||||||
);
|
);
|
||||||
return promises
|
return this.writeContents(JSON.stringify({})).then(() => ({}));
|
||||||
.writeFile(this.filepath, JSON.stringify({}))
|
|
||||||
.then(() => ({}));
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -51,7 +51,7 @@ export default class JsonFileStorage {
|
|||||||
setItem(_key: string, value: any, callback?: (_: any) => any): Promise<any> {
|
setItem(_key: string, value: any, callback?: (_: any) => any): Promise<any> {
|
||||||
const originalValue = this.parseFile();
|
const originalValue = this.parseFile();
|
||||||
const writePromise = originalValue.then(_ =>
|
const writePromise = originalValue.then(_ =>
|
||||||
promises.writeFile(this.filepath, this.serializeValue(value)),
|
this.writeContents(this.serializeValue(value)),
|
||||||
);
|
);
|
||||||
|
|
||||||
return Promise.all([originalValue, writePromise]).then(([o, _]) => {
|
return Promise.all([originalValue, writePromise]).then(([o, _]) => {
|
||||||
@@ -61,8 +61,7 @@ export default class JsonFileStorage {
|
|||||||
}
|
}
|
||||||
|
|
||||||
removeItem(_key: string, callback?: () => any): Promise<void> {
|
removeItem(_key: string, callback?: () => any): Promise<void> {
|
||||||
return promises
|
return this.writeContents(JSON.stringify({}))
|
||||||
.writeFile(this.filepath, JSON.stringify({}))
|
|
||||||
.then(_ => callback && callback())
|
.then(_ => callback && callback())
|
||||||
.then(() => {});
|
.then(() => {});
|
||||||
}
|
}
|
||||||
@@ -86,4 +85,13 @@ export default class JsonFileStorage {
|
|||||||
}, {});
|
}, {});
|
||||||
return JSON.stringify(reconstructedObject);
|
return JSON.stringify(reconstructedObject);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
writeContents(content: string): Promise<void> {
|
||||||
|
const dir = path.dirname(this.filepath);
|
||||||
|
return promisify(exists)(dir)
|
||||||
|
.then(dirExists =>
|
||||||
|
dirExists ? Promise.resolve() : promises.mkdir(dir, {recursive: true}),
|
||||||
|
)
|
||||||
|
.then(() => promises.writeFile(this.filepath, content));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user