├── .editorconfig
├── .gitignore
├── .hound.yml
├── .jshintrc
├── .travis.yml
├── CHANGELOG.md
├── README.md
├── appveyor.yml
├── doc
└── README.hbs
├── lib
├── lock.js
├── storage.js
└── utils.js
├── package-lock.json
├── package.json
├── stress
├── process.js
└── start.sh
└── tests
├── storage.spec.js
└── utils.spec.js
/.editorconfig:
--------------------------------------------------------------------------------
1 | # editorconfig.org
2 | root = true
3 |
4 | [*]
5 | indent_style = space
6 | indent_size = 2
7 | end_of_line = lf
8 | charset = utf-8
9 | trim_trailing_whitespace = true
10 | insert_final_newline = true
11 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 |
6 | # Runtime data
7 | pids
8 | *.pid
9 | *.seed
10 |
11 | # Directory for instrumented libs generated by jscoverage/JSCover
12 | lib-cov
13 |
14 | # Coverage directory used by tools like istanbul
15 | coverage
16 |
17 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
18 | .grunt
19 |
20 | # node-waf configuration
21 | .lock-wscript
22 |
23 | # Compiled binary addons (http://nodejs.org/api/addons.html)
24 | build/Release
25 |
26 | # Dependency directory
27 | # https://docs.npmjs.com/misc/faq#should-i-check-my-node-modules-folder-into-git
28 | node_modules
29 |
--------------------------------------------------------------------------------
/.hound.yml:
--------------------------------------------------------------------------------
1 | javascript:
2 | config_file: .jshintrc
3 |
--------------------------------------------------------------------------------
/.jshintrc:
--------------------------------------------------------------------------------
1 | {
2 | // --------------------------------------------------------------------
3 | // JSHint Configuration, Strict Edition
4 | // --------------------------------------------------------------------
5 | //
6 | // This is a options template for [JSHint][1], using [JSHint example][2]
7 | // and [Ory Band's example][3] as basis and setting config values to
8 | // be most strict:
9 | //
10 | // * set all enforcing options to true
11 | // * set all relaxing options to false
12 | // * set all environment options to false, except the browser value
13 | // * set all JSLint legacy options to false
14 | //
15 | // [1]: http://www.jshint.com/
16 | // [2]: https://github.com/jshint/node-jshint/blob/master/example/config.json
17 | // [3]: https://github.com/oryband/dotfiles/blob/master/jshintrc
18 | //
19 | // @author http://michael.haschke.biz/
20 | // @license http://unlicense.org/
21 |
22 | // == Enforcing Options ===============================================
23 | //
24 | // These options tell JSHint to be more strict towards your code. Use
25 | // them if you want to allow only a safe subset of JavaScript, very
26 | // useful when your codebase is shared with a big number of developers
27 | // with different skill levels.
28 |
29 | "bitwise" : true, // Prohibit bitwise operators (&, |, ^, etc.).
30 | "curly" : true, // Require {} for every new block or scope.
31 | "eqeqeq" : true, // Require triple equals i.e. `===`.
32 | "forin" : true, // Tolerate `for in` loops without `hasOwnPrototype`.
33 | "immed" : true, // Require immediate invocations to be wrapped in parens e.g. `( function(){}() );`
34 | "latedef" : true, // Prohibit variable use before definition.
35 | "newcap" : true, // Require capitalization of all constructor functions e.g. `new F()`.
36 | "noarg" : true, // Prohibit use of `arguments.caller` and `arguments.callee`.
37 | "noempty" : true, // Prohibit use of empty blocks.
38 | "nonew" : true, // Prohibit use of constructors for side-effects.
39 | "plusplus" : true, // Prohibit use of `++` & `--`.
40 | "regexp" : true, // Prohibit `.` and `[^...]` in regular expressions.
41 | "undef" : true, // Require all non-global variables be declared before they are used.
42 | "strict" : true, // Require `use strict` pragma in every file.
43 | "trailing" : true, // Prohibit trailing whitespaces.
44 |
45 | // == Relaxing Options ================================================
46 | //
47 | // These options allow you to suppress certain types of warnings. Use
48 | // them only if you are absolutely positive that you know what you are
49 | // doing.
50 |
51 | "asi" : false, // Tolerate Automatic Semicolon Insertion (no semicolons).
52 | "boss" : false, // Tolerate assignments inside if, for & while. Usually conditions & loops are for comparison, not assignments.
53 | "debug" : false, // Allow debugger statements e.g. browser breakpoints.
54 | "eqnull" : false, // Tolerate use of `== null`.
55 | "es5" : false, // Allow EcmaScript 5 syntax.
56 | "esnext" : true, // Allow ES.next specific features such as `const` and `let`.
57 | "evil" : false, // Tolerate use of `eval`.
58 | "expr" : true, // Tolerate `ExpressionStatement` as Programs.
59 | "funcscope" : false, // Tolerate declarations of variables inside of control structures while accessing them later from the outside.
60 | "globalstrict" : true, // Allow global "use strict" (also enables 'strict').
61 | "iterator" : false, // Allow usage of __iterator__ property.
62 | "lastsemic" : false, // Tolerat missing semicolons when the it is omitted for the last statement in a one-line block.
63 | "laxbreak" : false, // Tolerate unsafe line breaks e.g. `return [\n] x` without semicolons.
64 | "laxcomma" : false, // Suppress warnings about comma-first coding style.
65 | "loopfunc" : false, // Allow functions to be defined within loops.
66 | "multistr" : false, // Tolerate multi-line strings.
67 | "onecase" : false, // Tolerate switches with just one case.
68 | "proto" : false, // Tolerate __proto__ property. This property is deprecated.
69 | "regexdash" : false, // Tolerate unescaped last dash i.e. `[-...]`.
70 | "scripturl" : false, // Tolerate script-targeted URLs.
71 | "smarttabs" : false, // Tolerate mixed tabs and spaces when the latter are used for alignmnent only.
72 | "shadow" : false, // Allows re-define variables later in code e.g. `var x=1; x=2;`.
73 | "sub" : false, // Tolerate all forms of subscript notation besides dot notation e.g. `dict['key']` instead of `dict.key`.
74 | "supernew" : false, // Tolerate `new function () { ... };` and `new Object;`.
75 | "validthis" : false, // Tolerate strict violations when the code is running in strict mode and you use this in a non-constructor function.
76 |
77 | // == Environments ====================================================
78 | //
79 | // These options pre-define global variables that are exposed by
80 | // popular JavaScript libraries and runtime environments—such as
81 | // browser or node.js.
82 |
83 | "browser" : true, // Standard browser globals e.g. `window`, `document`.
84 | "couch" : false, // Enable globals exposed by CouchDB.
85 | "devel" : false, // Allow development statements e.g. `console.log();`.
86 | "dojo" : false, // Enable globals exposed by Dojo Toolkit.
87 | "mocha" : true, // Enable globals exposed by Mocha.
88 | "jquery" : false, // Enable globals exposed by jQuery JavaScript library.
89 | "mootools" : false, // Enable globals exposed by MooTools JavaScript framework.
90 | "node" : true, // Enable globals available when code is running inside of the NodeJS runtime environment.
91 | "nonstandard" : false, // Define non-standard but widely adopted globals such as escape and unescape.
92 | "prototypejs" : false, // Enable globals exposed by Prototype JavaScript framework.
93 | "rhino" : false, // Enable globals available when your code is running inside of the Rhino runtime environment.
94 | "wsh" : false, // Enable globals available when your code is running as a script for the Windows Script Host.
95 |
96 | // == JSLint Legacy ===================================================
97 | //
98 | // These options are legacy from JSLint. Aside from bug fixes they will
99 | // not be improved in any way and might be removed at any point.
100 |
101 | "nomen" : false, // Prohibit use of initial or trailing underbars in names.
102 | "onevar" : false, // Allow only one `var` statement per function.
103 | "passfail" : false, // Stop on first error.
104 | "white" : false, // Check against strict whitespace and indentation rules.
105 |
106 | // == Undocumented Options ============================================
107 | //
108 | // While I've found these options in [example1][2] and [example2][3]
109 | // they are not described in the [JSHint Options documentation][4].
110 | //
111 | // [4]: http://www.jshint.com/options/
112 |
113 | "maxerr" : 100, // Maximum errors before stopping.
114 | "maxlen" : 120, // Maximum line length.
115 | "indent" : 4 // Specify indentation spacing
116 | }
117 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 | dist: xenial
3 | node_js:
4 | - 12
5 | services:
6 | - xvfb
7 | before_install:
8 | - sudo apt-get install -y libgconf-2-4
9 | script:
10 | - npm test
11 | - ./stress/start.sh
12 | notifications:
13 | email: false
14 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Change Log
2 |
3 | All notable changes to this project will be documented in this file.
4 | This project adheres to [Semantic Versioning](http://semver.org/).
5 |
6 | ## [4.6.0] - 2022-10-12
7 |
8 | - Add a `.setSync()` function
9 |
10 | ## [4.5.0] - 2021-04-13
11 |
12 | - Add a `.getSync()` function
13 |
14 | ## [4.4.0] - 2021-02-22
15 |
16 | - Gracefully require the user to call `.setDataPath()` if the `remote` module
17 | is not available when running on a renderer process
18 |
19 | ## [4.3.0] - 2020-11-04
20 |
21 | - Add a `prettyPrinting` option to `.set()`
22 |
23 | ## [4.2.0] - 2020-07-09
24 |
25 | - Support a `validate` boolean option in `.set()` to validate writes by reading
26 | the key back after a short period of time and re-trying the write if the
27 | contents do not match
28 |
29 | ## [4.1.8] - 2019-09-13
30 |
31 | - Don't list non-JSON files in `.keys()`
32 |
33 | ## [4.1.7] - 2019-08-12
34 |
35 | - Don't store data as UTF-8
36 |
37 | ## [4.1.6] - 2019-01-26
38 |
39 | - Implement atomic writes
40 |
41 | ## [4.1.5] - 2018-12-02
42 |
43 | - Retry on `EPERM` when locking and reading
44 |
45 | ## [4.1.4] - 2018-10-09
46 |
47 | ### Changed
48 |
49 | - Set `electron` as a `devDependency`
50 |
51 | ## [4.1.3] - 2018-10-01
52 |
53 | ### Changed
54 |
55 | - Retry lock release if OS reports `EPERM`
56 |
57 | ## [4.1.2] - 2018-08-26
58 |
59 | ### Changed
60 |
61 | - Set `electron` as a `peerDependency`
62 |
63 | ## [4.1.1] - 2018-07-11
64 |
65 | ### Changed
66 |
67 | - Ensure parallel writes from multiple processes don't corrupt data
68 |
69 | ## [4.1.0] - 2018-04-15
70 |
71 | ### Changed
72 |
73 | - Support spaces in keys
74 |
75 | ## [4.0.3] - 2018-04-07
76 |
77 | ### Changed
78 |
79 | - Remove unnecessary ES6 features from the code base to keep it ES5
80 |
81 | ## [4.0.2] - 2017-10-20
82 |
83 | ### Changed
84 |
85 | - Ensure the `options` argument always defaults to an empty object.
86 |
87 | ## [4.0.1] - 2017-10-19
88 |
89 | ### Changed
90 |
91 | - Don't throw if the user doesn't pass a callback function.
92 |
93 | ## [4.0.0] - 2017-10-18
94 |
95 | ### Changed
96 |
97 | - React to external changes to the `userData` path.
98 | - Replace `storage.DEFAULT_DATA_PATH` with `storage.getDefaultDataPath()`.
99 |
100 | ## [3.2.0] - 2017-10-07
101 |
102 | ### Added
103 |
104 | - Add `dataPath` options to every function.
105 |
106 | ## [3.1.1] - 2017-09-27
107 |
108 | ### Changed
109 |
110 | - Replace asterisks with hyphens in file names to avoid Windows path problems.
111 |
112 | ## [3.1.0] - 2017-08-29
113 |
114 | ### Added
115 |
116 | - Support storing values in a custom directory.
117 |
118 | ## [3.0.7] - 2017-07-27
119 |
120 | ### Changed
121 |
122 | - Decode URI encoded file names on `.keys()`
123 |
124 | ## [3.0.6] - 2017-06-29
125 |
126 | ### Changed
127 |
128 | - Ensure parallel writes don't corrupt the data.
129 |
130 | ## [3.0.5] - 2017-04-14
131 |
132 | ### Changed
133 |
134 | - Make the module work on Spectron tests.
135 |
136 | ## [3.0.4] - 2017-03-30
137 |
138 | ### Changed
139 |
140 | - Get rid of `exists-file`, which is known to cause UglifyJS issues.
141 |
142 | ## [3.0.3] - 2017-03-30
143 |
144 | ### Changed
145 |
146 | - Remove ES6 features from the codebase.
147 |
148 | ## [3.0.2] - 2017-03-24
149 |
150 | ### Changed
151 |
152 | - Ignore `.DS_Store` in settings directory
153 | - Include the invalid error object on "invalid data" errors
154 |
155 | ## [3.0.1] - 2017-01-30
156 |
157 | ### Changed
158 |
159 | - Don't throw `ENOENT` on `.getAll()` if the user data path directory doesn't exist.
160 |
161 | ## [3.0.0] - 2017-01-08
162 |
163 | ### Changed
164 |
165 | - Store settings inside a `storage/` directory inside `userPath`.
166 |
167 | ## [2.1.1] - 2017-01-08
168 |
169 | ### Changed
170 |
171 | - Don't throw `ENOENT` on `.set()` if `userPath` doesn't exist.
172 |
173 | ## [2.1.0] - 2016-11-13
174 |
175 | ### Added
176 |
177 | - Implement `.getAll()`.
178 | - Implement `.getMany()`.
179 |
180 | ## [2.0.3] - 2016-10-27
181 |
182 | ### Changed
183 |
184 | - Change `let` to `var` for compatibility purposes.
185 |
186 | ## [2.0.2] - 2016-10-24
187 |
188 | ### Changed
189 |
190 | - Fix "Callback has already been called" error in `.get()`.
191 |
192 | ## [2.0.1] - 2016-10-05
193 |
194 | ### Changed
195 |
196 | - Prevent errors when using reserved characters in keys in Windows.
197 |
198 | ## [2.0.0] - 2016-02-26
199 |
200 | ### Changed
201 |
202 | - Ignore `GPUCache` key, saved by Electron.
203 |
204 | ### Removed
205 |
206 | - Remove promises support.
207 |
208 | ## [1.1.0] - 2016-02-17
209 |
210 | ### Added
211 |
212 | - Implement `.keys() function`.
213 |
214 | ### Changed
215 |
216 | - Fix error when requiring this module from the renderer process.
217 |
218 | [4.6.0]: https://github.com/electron-userland/electron-json-storage/compare/v4.5.0...v4.6.0
219 | [4.5.0]: https://github.com/electron-userland/electron-json-storage/compare/v4.4.0...v4.5.0
220 | [4.4.0]: https://github.com/electron-userland/electron-json-storage/compare/v4.3.0...v4.4.0
221 | [4.3.0]: https://github.com/electron-userland/electron-json-storage/compare/v4.2.0...v4.3.0
222 | [4.2.0]: https://github.com/electron-userland/electron-json-storage/compare/v4.1.8...v4.2.0
223 | [4.1.8]: https://github.com/electron-userland/electron-json-storage/compare/v4.1.7...v4.1.8
224 | [4.1.7]: https://github.com/electron-userland/electron-json-storage/compare/v4.1.6...v4.1.7
225 | [4.1.6]: https://github.com/electron-userland/electron-json-storage/compare/v4.1.5...v4.1.6
226 | [4.1.5]: https://github.com/electron-userland/electron-json-storage/compare/v4.1.4...v4.1.5
227 | [4.1.4]: https://github.com/electron-userland/electron-json-storage/compare/v4.1.3...v4.1.4
228 | [4.1.3]: https://github.com/electron-userland/electron-json-storage/compare/v4.1.2...v4.1.3
229 | [4.1.2]: https://github.com/electron-userland/electron-json-storage/compare/v4.1.1...v4.1.2
230 | [4.1.1]: https://github.com/electron-userland/electron-json-storage/compare/v4.1.0...v4.1.1
231 | [4.1.0]: https://github.com/electron-userland/electron-json-storage/compare/v4.0.3...v4.1.0
232 | [4.0.3]: https://github.com/electron-userland/electron-json-storage/compare/v4.0.2...v4.0.3
233 | [4.0.2]: https://github.com/electron-userland/electron-json-storage/compare/v4.0.1...v4.0.2
234 | [4.0.1]: https://github.com/electron-userland/electron-json-storage/compare/v4.0.0...v4.0.1
235 | [4.0.0]: https://github.com/electron-userland/electron-json-storage/compare/v3.2.0...v4.0.0
236 | [3.2.0]: https://github.com/electron-userland/electron-json-storage/compare/v3.1.1...v3.2.0
237 | [3.1.1]: https://github.com/electron-userland/electron-json-storage/compare/v3.1.0...v3.1.1
238 | [3.1.0]: https://github.com/electron-userland/electron-json-storage/compare/v3.0.7...v3.1.0
239 | [3.0.7]: https://github.com/electron-userland/electron-json-storage/compare/v3.0.6...v3.0.7
240 | [3.0.6]: https://github.com/electron-userland/electron-json-storage/compare/v3.0.5...v3.0.6
241 | [3.0.5]: https://github.com/electron-userland/electron-json-storage/compare/v3.0.4...v3.0.5
242 | [3.0.4]: https://github.com/electron-userland/electron-json-storage/compare/v3.0.3...v3.0.4
243 | [3.0.3]: https://github.com/electron-userland/electron-json-storage/compare/v3.0.2...v3.0.3
244 | [3.0.2]: https://github.com/electron-userland/electron-json-storage/compare/v3.0.1...v3.0.2
245 | [3.0.1]: https://github.com/electron-userland/electron-json-storage/compare/v3.0.0...v3.0.1
246 | [3.0.0]: https://github.com/electron-userland/electron-json-storage/compare/v2.1.1...v3.0.0
247 | [2.1.1]: https://github.com/electron-userland/electron-json-storage/compare/v2.1.0...v2.1.1
248 | [2.1.0]: https://github.com/electron-userland/electron-json-storage/compare/v2.0.3...v2.1.0
249 | [2.0.3]: https://github.com/electron-userland/electron-json-storage/compare/v2.0.2...v2.0.3
250 | [2.0.2]: https://github.com/electron-userland/electron-json-storage/compare/v2.0.1...v2.0.2
251 | [2.0.1]: https://github.com/electron-userland/electron-json-storage/compare/v2.0.0...v2.0.1
252 | [2.0.0]: https://github.com/electron-userland/electron-json-storage/compare/v1.1.0...v2.0.0
253 | [1.1.0]: https://github.com/electron-userland/electron-json-storage/compare/v1.0.0...v1.1.0
254 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | electron-json-storage
2 | =====================
3 |
4 | > Easily write and read user settings in Electron apps
5 |
6 | [](http://badge.fury.io/js/electron-json-storage)
7 | [](https://david-dm.org/jviotti/electron-json-storage.svg)
8 | [](https://travis-ci.org/electron-userland/electron-json-storage)
9 | [](https://ci.appveyor.com/project/electron-userland/electron-json-storage/branch/master)
10 |
11 | [Electron](http://electron.atom.io) lacks an easy way to persist and read user settings for your application. `electron-json-storage` implements an API somewhat similar to [localStorage](https://developer.mozilla.org/en/docs/Web/API/Window/localStorage) to write and read JSON objects to/from the operating system application data directory, as defined by `app.getPath('userData')`.
12 |
13 | Related modules:
14 |
15 | - [electron-settings](https://github.com/nathanbuchar/electron-settings)
16 | - [electron-store](https://github.com/sindresorhus/electron-store)
17 | - [electron-storage](https://github.com/Cocycles/electron-storage)
18 |
19 | Installation
20 | ------------
21 |
22 | Install `electron-json-storage` by running:
23 |
24 | ```sh
25 | $ npm install --save electron-json-storage
26 | ```
27 |
28 | You can require this module from either the **main** or **renderer** process (with and without `remote`).
29 |
30 | Running on Electron >10 renderer processes
31 | ------------------------------------------
32 |
33 | When loaded in renderer processes, this module will try to make use of
34 | `electron.remote` in order to fetch the `userData` path.
35 |
36 | Electron 10 now [defaults `enableRemoteModule` to
37 | false](https://www.electronjs.org/docs/breaking-changes#default-changed-enableremotemodule-defaults-to-false),
38 | which means that `electron-json-storage` will be able to calculate a data path by default.
39 |
40 | The solution is to manually call `storage.setDataPath()` before reading or
41 | writing any values or setting `enableRemoteModule` to `true`.
42 |
43 | Documentation
44 | -------------
45 |
46 |
47 | * [storage](#module_storage)
48 | * [.getDefaultDataPath()](#module_storage.getDefaultDataPath) ⇒ String
\| Null
49 | * [.setDataPath(directory)](#module_storage.setDataPath)
50 | * [.getDataPath()](#module_storage.getDataPath) ⇒ String
51 | * [.get(key, [options], callback)](#module_storage.get)
52 | * [.getSync(key, [options])](#module_storage.getSync)
53 | * [.getMany(keys, [options], callback)](#module_storage.getMany)
54 | * [.getAll([options], callback)](#module_storage.getAll)
55 | * [.set(key, json, [options], callback)](#module_storage.set)
56 | * [.has(key, [options], callback)](#module_storage.has)
57 | * [.keys([options], callback)](#module_storage.keys)
58 | * [.remove(key, [options], callback)](#module_storage.remove)
59 | * [.clear([options], callback)](#module_storage.clear)
60 |
61 |
62 |
63 | ### storage.getDefaultDataPath() ⇒ String
\| Null
64 | This function will return `null` when running in the
65 | renderer process without support for the `remote` IPC
66 | mechanism. You have to explicitly set a data path using
67 | `.setDataPath()` in these cases.
68 |
69 | **Kind**: static method of [storage
](#module_storage)
70 | **Summary**: Get the default data path
71 | **Returns**: String
\| Null
- default data path
72 | **Access**: public
73 | **Example**
74 | ```js
75 | const defaultDataPath = storage.getDefaultDataPath()
76 | ```
77 |
78 |
79 | ### storage.setDataPath(directory)
80 | The default value will be used if the directory is undefined.
81 |
82 | **Kind**: static method of [storage
](#module_storage)
83 | **Summary**: Set current data path
84 | **Access**: public
85 |
86 | | Param | Type | Description |
87 | | --- | --- | --- |
88 | | directory | String
\| Undefined
| directory |
89 |
90 | **Example**
91 | ```js
92 | const os = require('os');
93 | const storage = require('electron-json-storage');
94 |
95 | storage.setDataPath(os.tmpdir());
96 | ```
97 |
98 |
99 | ### storage.getDataPath() ⇒ String
100 | Returns the current data path. It defaults to a directory called
101 | "storage" inside Electron's `userData` path.
102 |
103 | **Kind**: static method of [storage
](#module_storage)
104 | **Summary**: Get current user data path
105 | **Returns**: String
- the user data path
106 | **Access**: public
107 | **Example**
108 | ```js
109 | const storage = require('electron-json-storage');
110 |
111 | const dataPath = storage.getDataPath();
112 | console.log(dataPath);
113 | ```
114 |
115 |
116 | ### storage.get(key, [options], callback)
117 | If the key doesn't exist in the user data, an empty object is returned.
118 | Also notice that the `.json` extension is added automatically, but it's
119 | ignored if you pass it yourself.
120 |
121 | Passing an extension other than `.json` will result in a file created
122 | with both extensions. For example, the key `foo.data` will result in a file
123 | called `foo.data.json`.
124 |
125 | **Kind**: static method of [storage
](#module_storage)
126 | **Summary**: Read user data
127 | **Access**: public
128 |
129 | | Param | Type | Description |
130 | | --- | --- | --- |
131 | | key | String
| key |
132 | | [options] | Object
| options |
133 | | [options.dataPath] | String
| data path |
134 | | callback | function
| callback (error, data) |
135 |
136 | **Example**
137 | ```js
138 | const storage = require('electron-json-storage');
139 |
140 | storage.get('foobar', function(error, data) {
141 | if (error) throw error;
142 |
143 | console.log(data);
144 | });
145 | ```
146 |
147 |
148 | ### storage.getSync(key, [options])
149 | See `.get()`.
150 |
151 | **Kind**: static method of [storage
](#module_storage)
152 | **Summary**: Read user data (sync)
153 | **Access**: public
154 |
155 | | Param | Type | Description |
156 | | --- | --- | --- |
157 | | key | String
| key |
158 | | [options] | Object
| options |
159 | | [options.dataPath] | String
| data path |
160 |
161 | **Example**
162 | ```js
163 | const storage = require('electron-json-storage');
164 |
165 | var data = storage.getSync('foobar');
166 | console.log(data);
167 | ```
168 |
169 |
170 | ### storage.getMany(keys, [options], callback)
171 | This function returns an object with the data of all the passed keys.
172 | If one of the keys doesn't exist, an empty object is returned for it.
173 |
174 | **Kind**: static method of [storage
](#module_storage)
175 | **Summary**: Read many user data keys
176 | **Access**: public
177 |
178 | | Param | Type | Description |
179 | | --- | --- | --- |
180 | | keys | Array.<String>
| keys |
181 | | [options] | Object
| options |
182 | | [options.dataPath] | String
| data path |
183 | | callback | function
| callback (error, data) |
184 |
185 | **Example**
186 | ```js
187 | const storage = require('electron-json-storage');
188 |
189 | storage.getMany([ 'foobar', 'barbaz' ], function(error, data) {
190 | if (error) throw error;
191 |
192 | console.log(data.foobar);
193 | console.log(data.barbaz);
194 | });
195 | ```
196 |
197 |
198 | ### storage.getAll([options], callback)
199 | This function returns an empty object if there is no data to be read.
200 |
201 | **Kind**: static method of [storage
](#module_storage)
202 | **Summary**: Read all user data
203 | **Access**: public
204 |
205 | | Param | Type | Description |
206 | | --- | --- | --- |
207 | | [options] | Object
| options |
208 | | [options.dataPath] | String
| data path |
209 | | callback | function
| callback (error, data) |
210 |
211 | **Example**
212 | ```js
213 | const storage = require('electron-json-storage');
214 |
215 | storage.getAll(function(error, data) {
216 | if (error) throw error;
217 |
218 | console.log(data);
219 | });
220 | ```
221 |
222 |
223 | ### storage.set(key, json, [options], callback)
224 | **Kind**: static method of [storage
](#module_storage)
225 | **Summary**: Write user data
226 | **Access**: public
227 |
228 | | Param | Type | Description |
229 | | --- | --- | --- |
230 | | key | String
| key |
231 | | json | Object
| json object |
232 | | [options] | Object
| options |
233 | | [options.dataPath] | String
| data path |
234 | | [options.validate] | String
| validate writes by reading the data back |
235 | | [options.prettyPrinting] | boolean
| adds line breaks and spacing to the written data |
236 | | callback | function
| callback (error) |
237 |
238 | **Example**
239 | ```js
240 | const storage = require('electron-json-storage');
241 |
242 | storage.set('foobar', { foo: 'bar' }, function(error) {
243 | if (error) throw error;
244 | });
245 | ```
246 |
247 |
248 | ### storage.has(key, [options], callback)
249 | **Kind**: static method of [storage
](#module_storage)
250 | **Summary**: Check if a key exists
251 | **Access**: public
252 |
253 | | Param | Type | Description |
254 | | --- | --- | --- |
255 | | key | String
| key |
256 | | [options] | Object
| options |
257 | | [options.dataPath] | String
| data path |
258 | | callback | function
| callback (error, hasKey) |
259 |
260 | **Example**
261 | ```js
262 | const storage = require('electron-json-storage');
263 |
264 | storage.has('foobar', function(error, hasKey) {
265 | if (error) throw error;
266 |
267 | if (hasKey) {
268 | console.log('There is data stored as `foobar`');
269 | }
270 | });
271 | ```
272 |
273 |
274 | ### storage.keys([options], callback)
275 | **Kind**: static method of [storage
](#module_storage)
276 | **Summary**: Get the list of saved keys
277 | **Access**: public
278 |
279 | | Param | Type | Description |
280 | | --- | --- | --- |
281 | | [options] | Object
| options |
282 | | [options.dataPath] | String
| data path |
283 | | callback | function
| callback (error, keys) |
284 |
285 | **Example**
286 | ```js
287 | const storage = require('electron-json-storage');
288 |
289 | storage.keys(function(error, keys) {
290 | if (error) throw error;
291 |
292 | for (var key of keys) {
293 | console.log('There is a key called: ' + key);
294 | }
295 | });
296 | ```
297 |
298 |
299 | ### storage.remove(key, [options], callback)
300 | Notice this function does nothing, nor throws any error
301 | if the key doesn't exist.
302 |
303 | **Kind**: static method of [storage
](#module_storage)
304 | **Summary**: Remove a key
305 | **Access**: public
306 |
307 | | Param | Type | Description |
308 | | --- | --- | --- |
309 | | key | String
| key |
310 | | [options] | Object
| options |
311 | | [options.dataPath] | String
| data path |
312 | | callback | function
| callback (error) |
313 |
314 | **Example**
315 | ```js
316 | const storage = require('electron-json-storage');
317 |
318 | storage.remove('foobar', function(error) {
319 | if (error) throw error;
320 | });
321 | ```
322 |
323 |
324 | ### storage.clear([options], callback)
325 | **Kind**: static method of [storage
](#module_storage)
326 | **Summary**: Clear all stored data in the current user data path
327 | **Access**: public
328 |
329 | | Param | Type | Description |
330 | | --- | --- | --- |
331 | | [options] | Object
| options |
332 | | [options.dataPath] | String
| data path |
333 | | callback | function
| callback (error) |
334 |
335 | **Example**
336 | ```js
337 | const storage = require('electron-json-storage');
338 |
339 | storage.clear(function(error) {
340 | if (error) throw error;
341 | });
342 | ```
343 |
344 | Support
345 | -------
346 |
347 | If you're having any problem, please [raise an issue](https://github.com/electron-userland/electron-json-storage/issues/new) on GitHub and we'll be happy to help.
348 |
349 | Tests
350 | -----
351 |
352 | Run the test suite by doing:
353 |
354 | ```sh
355 | $ npm test
356 | ```
357 |
358 | Contribute
359 | ----------
360 |
361 | - Issue Tracker: [github.com/electron-userland/electron-json-storage/issues](https://github.com/electron-userland/electron-json-storage/issues)
362 | - Source Code: [github.com/electron-userland/electron-json-storage](https://github.com/electron-userland/electron-json-storage)
363 |
364 | Before submitting a PR, please make sure that you include tests, and that [jshint](http://jshint.com) runs without any warning:
365 |
366 | ```sh
367 | $ npm run-script lint
368 | ```
369 |
370 | License
371 | -------
372 |
373 | The project is licensed under the MIT license.
374 |
--------------------------------------------------------------------------------
/appveyor.yml:
--------------------------------------------------------------------------------
1 | # appveyor file
2 | # http://www.appveyor.com/docs/appveyor-yml
3 |
4 | init:
5 | - git config --global core.autocrlf input
6 |
7 | cache:
8 | - C:\Users\appveyor\.node-gyp
9 | - '%AppData%\npm-cache'
10 |
11 | # what combinations to test
12 | environment:
13 | global:
14 | ELECTRON_NO_ATTACH_CONSOLE: true
15 | matrix:
16 | - nodejs_version: 12
17 |
18 | branches:
19 | only:
20 | - master
21 |
22 | install:
23 | - ps: Install-Product node $env:nodejs_version x64
24 | - set PATH=%APPDATA%\npm;%PATH%
25 | - npm install
26 |
27 | build: off
28 |
29 | test_script:
30 | - node --version
31 | - npm --version
32 | - cmd: npm test
33 |
--------------------------------------------------------------------------------
/doc/README.hbs:
--------------------------------------------------------------------------------
1 | electron-json-storage
2 | =====================
3 |
4 | > Easily write and read user settings in Electron apps
5 |
6 | [](http://badge.fury.io/js/electron-json-storage)
7 | [](https://david-dm.org/jviotti/electron-json-storage.svg)
8 | [](https://travis-ci.org/electron-userland/electron-json-storage)
9 | [](https://ci.appveyor.com/project/electron-userland/electron-json-storage/branch/master)
10 |
11 | [Electron](http://electron.atom.io) lacks an easy way to persist and read user settings for your application. `electron-json-storage` implements an API somewhat similar to [localStorage](https://developer.mozilla.org/en/docs/Web/API/Window/localStorage) to write and read JSON objects to/from the operating system application data directory, as defined by `app.getPath('userData')`.
12 |
13 | Related modules:
14 |
15 | - [electron-settings](https://github.com/nathanbuchar/electron-settings)
16 | - [electron-store](https://github.com/sindresorhus/electron-store)
17 | - [electron-storage](https://github.com/Cocycles/electron-storage)
18 |
19 | Installation
20 | ------------
21 |
22 | Install `electron-json-storage` by running:
23 |
24 | ```sh
25 | $ npm install --save electron-json-storage
26 | ```
27 |
28 | You can require this module from either the **main** or **renderer** process (with and without `remote`).
29 |
30 | Running on Electron >10 renderer processes
31 | ------------------------------------------
32 |
33 | When loaded in renderer processes, this module will try to make use of
34 | `electron.remote` in order to fetch the `userData` path.
35 |
36 | Electron 10 now [defaults `enableRemoteModule` to
37 | false](https://www.electronjs.org/docs/breaking-changes#default-changed-enableremotemodule-defaults-to-false),
38 | which means that `electron-json-storage` will be able to calculate a data path by default.
39 |
40 | The solution is to manually call `storage.setDataPath()` before reading or
41 | writing any values or setting `enableRemoteModule` to `true`.
42 |
43 | Documentation
44 | -------------
45 |
46 | {{#module name="storage"}}
47 | {{>body~}}
48 | {{>member-index~}}
49 | {{>separator~}}
50 | {{>members~}}
51 | {{/module}}
52 |
53 | Support
54 | -------
55 |
56 | If you're having any problem, please [raise an issue](https://github.com/electron-userland/electron-json-storage/issues/new) on GitHub and we'll be happy to help.
57 |
58 | Tests
59 | -----
60 |
61 | Run the test suite by doing:
62 |
63 | ```sh
64 | $ npm test
65 | ```
66 |
67 | Contribute
68 | ----------
69 |
70 | - Issue Tracker: [github.com/electron-userland/electron-json-storage/issues](https://github.com/electron-userland/electron-json-storage/issues)
71 | - Source Code: [github.com/electron-userland/electron-json-storage](https://github.com/electron-userland/electron-json-storage)
72 |
73 | Before submitting a PR, please make sure that you include tests, and that [jshint](http://jshint.com) runs without any warning:
74 |
75 | ```sh
76 | $ npm run-script lint
77 | ```
78 |
79 | License
80 | -------
81 |
82 | The project is licensed under the MIT license.
83 |
--------------------------------------------------------------------------------
/lib/lock.js:
--------------------------------------------------------------------------------
1 | /*
2 | * The MIT License
3 | *
4 | * Copyright (c) 2018 Juan Cruz Viotti. https://github.com/jviotti
5 | *
6 | * Permission is hereby granted, free of charge, to any person obtaining a copy
7 | * of this software and associated documentation files (the "Software"), to deal
8 | * in the Software without restriction, including without limitation the rights
9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | * copies of the Software, and to permit persons to whom the Software is
11 | * furnished to do so, subject to the following conditions:
12 | *
13 | * The above copyright notice and this permission notice shall be included in
14 | * all copies or substantial portions of the Software.
15 | *
16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 | * THE SOFTWARE.
23 | */
24 |
25 | 'use strict';
26 |
27 | const lockFile = require('lockfile');
28 |
29 | /**
30 | * @summary Lock options
31 | * @type {Object}
32 | * @private
33 | */
34 | const lockOptions = {
35 | stale: 10000,
36 | retries: 1000,
37 | retryWait: 50
38 | };
39 |
40 | /**
41 | * @summary Create a lock file
42 | * @function
43 | * @public
44 | *
45 | * @param {String} file - lock file
46 | * @param {Function} callback - callback (error)
47 | *
48 | * @example
49 | * lock.lock('foo.lock', function(error) {
50 | * if (error) {
51 | * throw error;
52 | * }
53 | * })
54 | */
55 | exports.lock = function(file, callback, times) {
56 | times = times || 0;
57 |
58 | lockFile.lock(file, lockOptions, function(error) {
59 | if (error && error.code === 'EPERM' && times < 10) {
60 | setTimeout(function() {
61 | exports.lock(file, callback, times + 1);
62 | }, 1000);
63 | return;
64 | }
65 |
66 | return callback(error);
67 | });
68 | };
69 |
70 | /**
71 | * @summary Create a lock file (sync)
72 | * @function
73 | * @public
74 | *
75 | * @param {String} file - lock file
76 | *
77 | * @example
78 | * lock.lockSync('foo.lock');
79 | */
80 | exports.lockSync = function(file, times) {
81 | times = times || 0;
82 |
83 | try {
84 | lockFile.lockSync(file, {
85 | stale: lockOptions.stale,
86 | retries: lockOptions.retries
87 | });
88 | } catch (error) {
89 | if (error && error.code === 'EPERM' && times < 10) {
90 | return exports.lockSync(file, times + 1);
91 | }
92 |
93 | throw error;
94 | }
95 | };
96 |
97 | /**
98 | * @summary Unlock a locked file
99 | * @function
100 | * @public
101 | *
102 | * @param {String} file - lock file
103 | * @param {Function} callback - callback (error)
104 | *
105 | * @example
106 | * lock.unlock('foo.lock', function(error) {
107 | * if (error) {
108 | * throw error;
109 | * }
110 | * })
111 | */
112 | exports.unlock = function(file, callback, times) {
113 | times = times || 0;
114 |
115 | lockFile.unlock(file, function(error) {
116 | if (error && error.code === 'EPERM' && times < 10) {
117 | setTimeout(function() {
118 | exports.unlock(file, callback, times + 1);
119 | }, 1000);
120 | return;
121 | }
122 |
123 | return callback(error);
124 | });
125 | };
126 |
127 | /**
128 | * @summary Unlock a locked file (sync)
129 | * @function
130 | * @public
131 | *
132 | * @param {String} file - lock file
133 | *
134 | * @example
135 | * lock.unlockSync('foo.lock');
136 | */
137 | exports.unlockSync = function(file, times) {
138 | times = times || 0;
139 |
140 | try {
141 | lockFile.unlockSync(file);
142 | } catch (error) {
143 | if (error && error.code === 'EPERM' && times < 10) {
144 | return exports.unlockSync(file, times + 1);
145 | }
146 |
147 | throw error;
148 | }
149 | };
150 |
--------------------------------------------------------------------------------
/lib/storage.js:
--------------------------------------------------------------------------------
1 | /*
2 | * The MIT License
3 | *
4 | * Copyright (c) 2016 Juan Cruz Viotti. https://github.com/jviotti
5 | *
6 | * Permission is hereby granted, free of charge, to any person obtaining a copy
7 | * of this software and associated documentation files (the "Software"), to deal
8 | * in the Software without restriction, including without limitation the rights
9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | * copies of the Software, and to permit persons to whom the Software is
11 | * furnished to do so, subject to the following conditions:
12 | *
13 | * The above copyright notice and this permission notice shall be included in
14 | * all copies or substantial portions of the Software.
15 | *
16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 | * THE SOFTWARE.
23 | */
24 |
25 | 'use strict';
26 |
27 | /**
28 | * @module storage
29 | */
30 |
31 | const _ = require('lodash');
32 | const async = require('async');
33 | const fs = require('fs');
34 | const rimraf = require('rimraf');
35 | const mkdirp = require('mkdirp');
36 | const path = require('path');
37 | const writeFileAtomic = require('write-file-atomic');
38 | const utils = require('./utils');
39 | const lock = require('./lock');
40 |
41 | const readFile = function (fileName, callback, times) {
42 | times = times || 0;
43 |
44 | fs.readFile(fileName, function (error, object) {
45 | if (!error) {
46 | return callback(null, object);
47 | }
48 |
49 | if (error.code === 'ENOENT') {
50 | return callback(null, JSON.stringify({}));
51 | }
52 |
53 | if (error.code === 'EPERM' && times < 10) {
54 | setTimeout(function () {
55 | readFile(fileName, callback, times + 1);
56 | }, 1000);
57 | return;
58 | }
59 |
60 | return callback(error);
61 | });
62 | };
63 |
64 | const readFileSync = function (fileName, times) {
65 | times = times || 0;
66 | try {
67 | return fs.readFileSync(fileName);
68 | } catch (error) {
69 | if (error.code === 'ENOENT') {
70 | return JSON.stringify({});
71 | }
72 |
73 | if (error.code === 'EPERM' && times < 10) {
74 | return readFileSync(fileName, times + 1);
75 | }
76 |
77 | throw error;
78 | }
79 | };
80 |
81 | /**
82 | * @summary Get the default data path
83 | * @function
84 | * @public
85 | *
86 | * @description
87 | * This function will return `null` when running in the
88 | * renderer process without support for the `remote` IPC
89 | * mechanism. You have to explicitly set a data path using
90 | * `.setDataPath()` in these cases.
91 | *
92 | * @returns {(String|Null)} default data path
93 | *
94 | * @example
95 | * const defaultDataPath = storage.getDefaultDataPath()
96 | */
97 | exports.getDefaultDataPath = utils.getDefaultDataPath;
98 |
99 | /**
100 | * @summary Set current data path
101 | * @function
102 | * @public
103 | *
104 | * @description
105 | * The default value will be used if the directory is undefined.
106 | *
107 | * @param {(String|Undefined)} directory - directory
108 | *
109 | * @example
110 | * const os = require('os');
111 | * const storage = require('electron-json-storage');
112 | *
113 | * storage.setDataPath(os.tmpdir());
114 | */
115 | exports.setDataPath = utils.setDataPath;
116 |
117 | /**
118 | * @summary Get current user data path
119 | * @function
120 | * @public
121 | *
122 | * @description
123 | * Returns the current data path. It defaults to a directory called
124 | * "storage" inside Electron's `userData` path.
125 | *
126 | * @returns {String} the user data path
127 | *
128 | * @example
129 | * const storage = require('electron-json-storage');
130 | *
131 | * const dataPath = storage.getDataPath();
132 | * console.log(dataPath);
133 | */
134 | exports.getDataPath = utils.getDataPath;
135 |
136 | /**
137 | * @summary Read user data
138 | * @function
139 | * @public
140 | *
141 | * @description
142 | * If the key doesn't exist in the user data, an empty object is returned.
143 | * Also notice that the `.json` extension is added automatically, but it's
144 | * ignored if you pass it yourself.
145 | *
146 | * Passing an extension other than `.json` will result in a file created
147 | * with both extensions. For example, the key `foo.data` will result in a file
148 | * called `foo.data.json`.
149 | *
150 | * @param {String} key - key
151 | * @param {Object} [options] - options
152 | * @param {String} [options.dataPath] - data path
153 | * @param {Function} callback - callback (error, data)
154 | *
155 | * @example
156 | * const storage = require('electron-json-storage');
157 | *
158 | * storage.get('foobar', function(error, data) {
159 | * if (error) throw error;
160 | *
161 | * console.log(data);
162 | * });
163 | */
164 | exports.get = function (key, options, callback) {
165 | if (_.isFunction(options)) {
166 | callback = options;
167 | }
168 |
169 | options = options || {};
170 | callback = callback || _.noop;
171 | var fileName = null;
172 |
173 | async.waterfall([
174 | async.asyncify(_.partial(utils.getFileName, key, {
175 | dataPath: options.dataPath
176 | })),
177 | function (result, callback) {
178 | fileName = result;
179 | mkdirp(path.dirname(fileName), callback);
180 | },
181 | function (made, next) {
182 | lock.lock(utils.getLockFileName(fileName), function (error) {
183 | if (error && error.code === 'EEXIST') {
184 | return exports.get(key, options, callback);
185 | }
186 |
187 | return next(error);
188 | });
189 | },
190 | function (callback) {
191 | readFile(fileName, callback);
192 | },
193 | function (object, callback) {
194 | var objectJSON = {};
195 | try {
196 | objectJSON = JSON.parse(object);
197 | } catch (error) {
198 | return callback(new Error('Invalid data: ' + object));
199 | }
200 | return callback(null, objectJSON);
201 | }
202 | ], function (error, result) {
203 | lock.unlock(utils.getLockFileName(fileName), function (lockError) {
204 | if (error) {
205 | return callback(error);
206 | }
207 |
208 | return callback(lockError, result);
209 | });
210 | });
211 | };
212 |
213 | /**
214 | * @summary Read user data (sync)
215 | * @function
216 | * @public
217 | *
218 | * @description
219 | * See `.get()`.
220 | *
221 | * @param {String} key - key
222 | * @param {Object} [options] - options
223 | * @param {String} [options.dataPath] - data path
224 | *
225 | * @example
226 | * const storage = require('electron-json-storage');
227 | *
228 | * var data = storage.getSync('foobar');
229 | * console.log(data);
230 | */
231 | exports.getSync = function (key, options) {
232 | options = options || {};
233 | var fileName = utils.getFileName(key, {
234 | dataPath: options.dataPath
235 | });
236 |
237 | mkdirp.sync(path.dirname(fileName));
238 |
239 | try {
240 | lock.lockSync(utils.getLockFileName(fileName));
241 | } catch (error) {
242 | if (error && error.code === 'EEXIST') {
243 | return exports.getSync(key, options);
244 | }
245 |
246 | throw error;
247 | }
248 |
249 | var object = readFileSync(fileName);
250 | lock.unlockSync(utils.getLockFileName(fileName));
251 |
252 | try {
253 | return JSON.parse(object);
254 | } catch (error) {
255 | throw new Error('Invalid data: ' + object);
256 | }
257 | };
258 |
259 | /**
260 | * @summary Read many user data keys
261 | * @function
262 | * @public
263 | *
264 | * @description
265 | * This function returns an object with the data of all the passed keys.
266 | * If one of the keys doesn't exist, an empty object is returned for it.
267 | *
268 | * @param {String[]} keys - keys
269 | * @param {Object} [options] - options
270 | * @param {String} [options.dataPath] - data path
271 | * @param {Function} callback - callback (error, data)
272 | *
273 | * @example
274 | * const storage = require('electron-json-storage');
275 | *
276 | * storage.getMany([ 'foobar', 'barbaz' ], function(error, data) {
277 | * if (error) throw error;
278 | *
279 | * console.log(data.foobar);
280 | * console.log(data.barbaz);
281 | * });
282 | */
283 | exports.getMany = function (keys, options, callback) {
284 | if (_.isFunction(options)) {
285 | callback = options;
286 | options = {};
287 | }
288 |
289 | options = options || {};
290 | callback = callback || _.noop;
291 |
292 | async.reduce(keys, {}, function (reducer, key, callback) {
293 | exports.get(key, options, function (error, data) {
294 | if (error) {
295 | return callback(error);
296 | }
297 | return callback(null, _.set(reducer, key, data));
298 | });
299 | }, callback);
300 | };
301 |
302 | /**
303 | * @summary Read all user data
304 | * @function
305 | * @public
306 | *
307 | * @description
308 | * This function returns an empty object if there is no data to be read.
309 | *
310 | * @param {Object} [options] - options
311 | * @param {String} [options.dataPath] - data path
312 | * @param {Function} callback - callback (error, data)
313 | *
314 | * @example
315 | * const storage = require('electron-json-storage');
316 | *
317 | * storage.getAll(function(error, data) {
318 | * if (error) throw error;
319 | *
320 | * console.log(data);
321 | * });
322 | */
323 | exports.getAll = function (options, callback) {
324 | if (_.isFunction(options)) {
325 | callback = options;
326 | options = {};
327 | }
328 |
329 | options = options || {};
330 | callback = callback || _.noop;
331 |
332 | async.waterfall([
333 | _.partial(exports.keys, options),
334 | function (keys, callback) {
335 | async.reduce(keys, {}, function (reducer, key, callback) {
336 | async.waterfall([
337 | _.partial(exports.get, key, options),
338 | function (contents, callback) {
339 | return callback(null, _.set(reducer, key, contents));
340 | }
341 | ], callback);
342 | }, callback);
343 | }
344 | ], callback);
345 | };
346 |
347 | /**
348 | * @summary Write user data
349 | * @function
350 | * @public
351 | *
352 | * @param {String} key - key
353 | * @param {Object} json - json object
354 | * @param {Object} [options] - options
355 | * @param {String} [options.dataPath] - data path
356 | * @param {String} [options.validate] - validate writes by reading the data back
357 | * @param {boolean} [options.prettyPrinting] - adds line breaks and spacing to the written data
358 | * @param {Function} callback - callback (error)
359 | *
360 | * @example
361 | * const storage = require('electron-json-storage');
362 | *
363 | * storage.set('foobar', { foo: 'bar' }, function(error) {
364 | * if (error) throw error;
365 | * });
366 | */
367 | exports.set = function (key, json, options, callback, retries) {
368 | if (!_.isNumber(retries)) {
369 | retries = 10;
370 | }
371 |
372 | if (_.isFunction(options)) {
373 | callback = options;
374 | }
375 |
376 | options = options || {};
377 | callback = callback || _.noop;
378 | var fileName = null;
379 |
380 | async.waterfall([
381 | async.asyncify(_.partial(utils.getFileName, key, {
382 | dataPath: options.dataPath
383 | })),
384 | function (result, callback) {
385 | fileName = result;
386 | const data = JSON.stringify(json, null, (options.prettyPrinting ? 2 : 0));
387 |
388 | if (!data) {
389 | return callback(new Error('Invalid JSON data'));
390 | }
391 |
392 | // Create the directory in case it doesn't exist yet
393 | mkdirp(path.dirname(fileName), function (error) {
394 | return callback(error, data);
395 | });
396 | },
397 | function (data, next) {
398 | lock.lock(utils.getLockFileName(fileName), function (error) {
399 | if (error && error.code === 'EEXIST') {
400 | return exports.set(key, json, options, callback);
401 | }
402 |
403 | return next(error, fileName, data);
404 | });
405 | },
406 | function (fileName, data, callback) {
407 | writeFileAtomic(fileName, data, callback);
408 | }
409 | ], function (error) {
410 | lock.unlock(utils.getLockFileName(fileName), function (lockError) {
411 | if (error) {
412 | return callback(error);
413 | }
414 |
415 | if (!options.validate) {
416 | return callback(lockError);
417 | }
418 |
419 | // Check that the writes were actually successful
420 | // after a little bit
421 | setTimeout(function () {
422 | exports.get(key, {
423 | dataPath: options.dataPath
424 | }, function (getError, data) {
425 | if (getError) {
426 | return callback(getError);
427 | }
428 |
429 | if (!_.isEqual(data, json)) {
430 | if (retries <= 0) {
431 | throw new Error('Couldn\'t ensure data was written correctly');
432 | }
433 |
434 | return exports.set(key, json, options, callback, retries - 1);
435 | }
436 |
437 | return callback();
438 | });
439 | }, 100);
440 | });
441 | });
442 | };
443 |
444 | /**
445 | * @summary Write user data sync
446 | * @function
447 | * @public
448 | *
449 | * @param {String} key - key
450 | * @param {Object} json - json object
451 | * @param {Object} [options] - options
452 | * @param {String} [options.dataPath] - data path
453 | * @param {boolean} [options.prettyPrinting] - adds line breaks and spacing to the written data
454 | *
455 | * @example
456 | * const storage = require('electron-json-storage');
457 | *
458 | * storage.setSync('foobar', { foo: 'bar' });
459 | */
460 | exports.setSync = function (key, json, options = {}) {
461 | const fileName = utils.getFileName(key, {
462 | dataPath: options.dataPath
463 | });
464 | const data = JSON.stringify(json, null, (options.prettyPrinting ? 2 : 0));
465 |
466 | if (!data) {
467 | throw new Error('Invalid JSON data');
468 | }
469 |
470 | mkdirp.sync(path.dirname(fileName));
471 |
472 | try {
473 | lock.lockSync(utils.getLockFileName(fileName));
474 | } catch (error) {
475 | if (error && error.code === 'EEXIST') {
476 | return exports.setSync(key, json, options);
477 | }
478 | throw error;
479 | }
480 |
481 | try {
482 | fs.writeFileSync(fileName, data);
483 | lock.unlockSync(utils.getLockFileName(fileName));
484 | return;
485 | }
486 | catch (error) {
487 | throw error;
488 | }
489 | };
490 |
491 | /**
492 | * @summary Check if a key exists
493 | * @function
494 | * @public
495 | *
496 | * @param {String} key - key
497 | * @param {Object} [options] - options
498 | * @param {String} [options.dataPath] - data path
499 | * @param {Function} callback - callback (error, hasKey)
500 | *
501 | * @example
502 | * const storage = require('electron-json-storage');
503 | *
504 | * storage.has('foobar', function(error, hasKey) {
505 | * if (error) throw error;
506 | *
507 | * if (hasKey) {
508 | * console.log('There is data stored as `foobar`');
509 | * }
510 | * });
511 | */
512 | exports.has = function (key, options, callback) {
513 | if (_.isFunction(options)) {
514 | callback = options;
515 | }
516 |
517 | options = options || {};
518 | callback = callback || _.noop;
519 |
520 | async.waterfall([
521 | async.asyncify(_.partial(utils.getFileName, key, {
522 | dataPath: options.dataPath
523 | })),
524 | function (filename, done) {
525 | fs.stat(filename, function (error) {
526 | if (error) {
527 | if (error.code === 'ENOENT') {
528 | return done(null, false);
529 | }
530 |
531 | return done(error);
532 | }
533 |
534 | return done(null, true);
535 | });
536 | }
537 | ], callback);
538 | };
539 |
540 | /**
541 | * @summary Get the list of saved keys
542 | * @function
543 | * @public
544 | *
545 | * @param {Object} [options] - options
546 | * @param {String} [options.dataPath] - data path
547 | * @param {Function} callback - callback (error, keys)
548 | *
549 | * @example
550 | * const storage = require('electron-json-storage');
551 | *
552 | * storage.keys(function(error, keys) {
553 | * if (error) throw error;
554 | *
555 | * for (var key of keys) {
556 | * console.log('There is a key called: ' + key);
557 | * }
558 | * });
559 | */
560 | exports.keys = function (options, callback) {
561 | if (_.isFunction(options)) {
562 | callback = options;
563 | options = {};
564 | }
565 |
566 | options = options || {};
567 | callback = callback || _.noop;
568 |
569 | async.waterfall([
570 | function (callback) {
571 | callback(null, options.dataPath || exports.getDataPath());
572 | },
573 | function (userDataPath, callback) {
574 | mkdirp(userDataPath, function (error) {
575 | return callback(error, userDataPath);
576 | });
577 | },
578 | fs.readdir,
579 | function (keys, callback) {
580 | callback(null, _.map(_.reject(keys, function (key) {
581 | return path.extname(key) !== '.json';
582 | }), function (key) {
583 | return path.basename(decodeURIComponent(key), '.json');
584 | }));
585 | }
586 | ], callback);
587 | };
588 |
589 | /**
590 | * @summary Remove a key
591 | * @function
592 | * @public
593 | *
594 | * @description
595 | * Notice this function does nothing, nor throws any error
596 | * if the key doesn't exist.
597 | *
598 | * @param {String} key - key
599 | * @param {Object} [options] - options
600 | * @param {String} [options.dataPath] - data path
601 | * @param {Function} callback - callback (error)
602 | *
603 | * @example
604 | * const storage = require('electron-json-storage');
605 | *
606 | * storage.remove('foobar', function(error) {
607 | * if (error) throw error;
608 | * });
609 | */
610 | exports.remove = function (key, options, callback) {
611 | if (_.isFunction(options)) {
612 | callback = options;
613 | }
614 |
615 | options = options || {};
616 | callback = callback || _.noop;
617 |
618 | async.waterfall([
619 | async.asyncify(_.partial(utils.getFileName, key, {
620 | dataPath: options.dataPath
621 | })),
622 | rimraf
623 | ], callback);
624 | };
625 |
626 | /**
627 | * @summary Clear all stored data in the current user data path
628 | * @function
629 | * @public
630 | *
631 | * @param {Object} [options] - options
632 | * @param {String} [options.dataPath] - data path
633 | * @param {Function} callback - callback (error)
634 | *
635 | * @example
636 | * const storage = require('electron-json-storage');
637 | *
638 | * storage.clear(function(error) {
639 | * if (error) throw error;
640 | * });
641 | */
642 | exports.clear = function (options, callback) {
643 | if (_.isFunction(options)) {
644 | callback = options;
645 | }
646 |
647 | options = options || {};
648 | callback = callback || _.noop;
649 |
650 | const userData = options.dataPath || exports.getDataPath();
651 | const jsonFiles = path.join(userData, '*.json');
652 | rimraf(jsonFiles, callback);
653 | };
654 |
--------------------------------------------------------------------------------
/lib/utils.js:
--------------------------------------------------------------------------------
1 | /*
2 | * The MIT License
3 | *
4 | * Copyright (c) 2016 Juan Cruz Viotti. https://github.com/jviotti
5 | *
6 | * Permission is hereby granted, free of charge, to any person obtaining a copy
7 | * of this software and associated documentation files (the "Software"), to deal
8 | * in the Software without restriction, including without limitation the rights
9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | * copies of the Software, and to permit persons to whom the Software is
11 | * furnished to do so, subject to the following conditions:
12 | *
13 | * The above copyright notice and this permission notice shall be included in
14 | * all copies or substantial portions of the Software.
15 | *
16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 | * THE SOFTWARE.
23 | */
24 |
25 | 'use strict';
26 |
27 | const _ = require('lodash');
28 | const path = require('path');
29 | const electron = require('electron');
30 | const app = electron.app || (electron.remote && electron.remote.app) || null;
31 |
32 | /**
33 | * @summary Get the default data path
34 | * @function
35 | * @public
36 | *
37 | * @returns {String} default data path
38 | *
39 | * @example
40 | * const defaultDataPath = utils.getDefaultDataPath()
41 | */
42 | exports.getDefaultDataPath = function() {
43 | if (!app) {
44 | return null;
45 | }
46 |
47 | return path.join(app.getPath('userData'), 'storage');
48 | };
49 |
50 | /**
51 | * @summary The current data path
52 | * @type {String}
53 | */
54 | var currentDataPath;
55 |
56 | /**
57 | * @summary Set default data path
58 | * @function
59 | * @public
60 | *
61 | * @param {String} directory - directory
62 | *
63 | * @example
64 | * const os = require('os');
65 | * utils.setDataPath(os.tmpdir());
66 | */
67 | exports.setDataPath = function(directory) {
68 | if (_.isNil(directory)) {
69 | currentDataPath = undefined;
70 | return;
71 | }
72 |
73 | if (!path.isAbsolute(directory)) {
74 | throw new Error('The user data path should be an absolute directory');
75 | }
76 |
77 | currentDataPath = path.normalize(directory);
78 | };
79 |
80 | /**
81 | * @summary Get data path
82 | * @function
83 | * @public
84 | *
85 | * @returns {Strings} data path
86 | *
87 | * @example
88 | * const dataPath = utils.getDataPath();
89 | * console.log(dataPath);
90 | */
91 | exports.getDataPath = function() {
92 | return currentDataPath || exports.getDefaultDataPath();
93 | };
94 |
95 | /**
96 | * @summary Get storage file name for a key
97 | * @function
98 | * @public
99 | *
100 | * @param {String} key - key
101 | * @param {Object} [options] - options
102 | * @param {String} [options.dataPath] - custom data path
103 | * @returns {String} file name
104 | *
105 | * @example
106 | * let fileName = utils.getFileName('foo');
107 | * console.log(fileName);
108 | */
109 | exports.getFileName = function(key, options) {
110 | options = options || {};
111 |
112 | if (!key) {
113 | throw new Error('Missing key');
114 | }
115 |
116 | if (!_.isString(key) || key.trim().length === 0) {
117 | throw new Error('Invalid key');
118 | }
119 |
120 | // Trick to prevent adding the `.json` twice
121 | // if the key already contains it.
122 | const keyFileName = path.basename(key, '.json') + '.json';
123 |
124 | // Prevent ENOENT and other similar errors when using
125 | // reserved characters in Windows filenames.
126 | // See: https://en.wikipedia.org/wiki/Filename#Reserved%5Fcharacters%5Fand%5Fwords
127 | const escapedFileName = encodeURIComponent(keyFileName)
128 | .replace(/\*/g, '-').replace(/%20/g, ' ');
129 |
130 | const dataPath = options.dataPath || exports.getDataPath();
131 | if (!dataPath) {
132 | throw new Error('You must explicitly set a data path');
133 | }
134 |
135 | return path.join(dataPath, escapedFileName);
136 | };
137 |
138 | /**
139 | * @summary Get the lock file out of a file name
140 | * @function
141 | * @public
142 | *
143 | * @param {String} fileName - file name
144 | * @returns {String} lock file name
145 | *
146 | * @example
147 | * let lockFileName = utils.getLockFileName('foo');
148 | * console.log(lockFileName);
149 | */
150 | exports.getLockFileName = function(fileName) {
151 | return fileName + '.lock';
152 | };
153 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "electron-json-storage",
3 | "version": "4.6.0",
4 | "description": "Easily write and read user settings in Electron apps",
5 | "main": "lib/storage.js",
6 | "homepage": "https://github.com/electron-userland/electron-json-storage",
7 | "repository": {
8 | "type": "git",
9 | "url": "git://github.com/electron-userland/electron-json-storage.git"
10 | },
11 | "directories": {
12 | "test": "tests"
13 | },
14 | "scripts": {
15 | "test": "npm run lint && electron-mocha --recursive tests -R spec && electron-mocha --renderer --recursive tests -R spec",
16 | "lint": "jshint --config .jshintrc --reporter unix lib tests stress",
17 | "readme": "jsdoc2md --template doc/README.hbs lib/storage.js > README.md"
18 | },
19 | "keywords": [
20 | "electron",
21 | "json",
22 | "storage",
23 | "user",
24 | "app",
25 | "data"
26 | ],
27 | "author": "Juan Cruz Viotti ",
28 | "license": "MIT",
29 | "devDependencies": {
30 | "chai": "^4.2.0",
31 | "electron": "^10.1.3",
32 | "electron-mocha": "^9.2.0",
33 | "jsdoc-to-markdown": "^6.0.1",
34 | "jshint": "^2.9.1",
35 | "tmp": "0.0.31"
36 | },
37 | "dependencies": {
38 | "async": "^2.0.0",
39 | "lockfile": "^1.0.4",
40 | "lodash": "^4.0.1",
41 | "mkdirp": "^0.5.1",
42 | "rimraf": "^2.5.1",
43 | "write-file-atomic": "^2.4.2"
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/stress/process.js:
--------------------------------------------------------------------------------
1 | /*
2 | * The MIT License
3 | *
4 | * Copyright (c) 2016 Juan Cruz Viotti. https://github.com/jviotti
5 | *
6 | * Permission is hereby granted, free of charge, to any person obtaining a copy
7 | * of this software and associated documentation files (the "Software"), to deal
8 | * in the Software without restriction, including without limitation the rights
9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | * copies of the Software, and to permit persons to whom the Software is
11 | * furnished to do so, subject to the following conditions:
12 | *
13 | * The above copyright notice and this permission notice shall be included in
14 | * all copies or substantial portions of the Software.
15 | *
16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 | * THE SOFTWARE.
23 | */
24 |
25 | 'use strict';
26 |
27 | const async = require('async');
28 | const storage = require('..');
29 | const value = process.argv[2];
30 |
31 | const retry = function(callback, times) {
32 | if (times === 0) {
33 | return callback();
34 | }
35 |
36 | async.waterfall([
37 | function(next) {
38 | storage.set('foo', {
39 | value: value
40 | }, next);
41 | },
42 | function(next) {
43 | storage.get('foo', next);
44 | }
45 | ], function(error, result) {
46 | if (error) {
47 | return callback(error);
48 | }
49 |
50 | console.log(process.pid, times, result);
51 | retry(callback, times - 1);
52 | });
53 | };
54 |
55 | retry(function(error) {
56 | if (error) {
57 | console.error(error);
58 | process.exit(1);
59 | } else {
60 | process.exit(0);
61 | }
62 | }, 2000);
63 |
--------------------------------------------------------------------------------
/stress/start.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | set -e
4 |
5 | ./node_modules/.bin/electron stress/process.js xxx & PID1=$!
6 | ./node_modules/.bin/electron stress/process.js xxxxxx & PID2=$!
7 | wait $PID1
8 | wait $PID2
9 |
--------------------------------------------------------------------------------
/tests/storage.spec.js:
--------------------------------------------------------------------------------
1 | /*
2 | * The MIT License
3 | *
4 | * Copyright (c) 2016 Juan Cruz Viotti. https://github.com/jviotti
5 | *
6 | * Permission is hereby granted, free of charge, to any person obtaining a copy
7 | * of this software and associated documentation files (the "Software"), to deal
8 | * in the Software without restriction, including without limitation the rights
9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | * copies of the Software, and to permit persons to whom the Software is
11 | * furnished to do so, subject to the following conditions:
12 | *
13 | * The above copyright notice and this permission notice shall be included in
14 | * all copies or substantial portions of the Software.
15 | *
16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 | * THE SOFTWARE.
23 | */
24 |
25 | 'use strict';
26 |
27 | const electron = require('electron');
28 | const _ = require('lodash');
29 | const async = require('async');
30 | const fs = require('fs');
31 | const path = require('path');
32 | const os = require('os');
33 | const tmp = require('tmp');
34 | const rimraf = require('rimraf');
35 | const mkdirp = require('mkdirp');
36 | const chai = require('chai');
37 | const storage = require('../lib/storage');
38 | const utils = require('../lib/utils');
39 | const app = electron.app || electron.remote.app;
40 |
41 | describe('Electron JSON Storage', function() {
42 |
43 | this.timeout(100000);
44 |
45 | // Ensure each test case is always ran in a clean state
46 | beforeEach(function(done) {
47 | storage.setDataPath(utils.getDefaultDataPath());
48 | storage.clear(done);
49 | });
50 |
51 | describe('stress testing', function() {
52 |
53 | const cases = _.times(1000, () => {
54 | return Math.floor(Math.random() * 100000);
55 | });
56 |
57 | it('should survive serial stress testing', function(done) {
58 | async.eachSeries(cases, function(number, callback) {
59 | async.waterfall([
60 | _.partial(storage.set, 'foo', { value: number }),
61 | _.partial(storage.get, 'foo'),
62 | function(data, next) {
63 | chai.expect(data.value).to.equal(number);
64 | next();
65 | }
66 | ], callback);
67 | }, done);
68 | });
69 |
70 | it('should survive serial stress testing with validate=true', function(done) {
71 | async.eachSeries(_.times(100, () => {
72 | return Math.floor(Math.random() * 100000);
73 | }), function(number, callback) {
74 | async.waterfall([
75 | _.partial(storage.set, 'foo', { value: number }, { validate: true }),
76 | _.partial(storage.get, 'foo'),
77 | function(data, next) {
78 | chai.expect(data.value).to.equal(number);
79 | next();
80 | }
81 | ], callback);
82 | }, done);
83 | });
84 |
85 | it('should survive parallel stress testing', function(done) {
86 | async.eachSeries(cases, function(number, callback) {
87 | async.parallel([
88 | _.partial(storage.set, 'foo', { value: [number] }),
89 | _.partial(storage.set, 'foo', { value: [number, number] })
90 | ], function() {
91 | storage.get('foo', function(error, data) {
92 | chai.expect(error).to.not.exist;
93 | callback();
94 | });
95 | });
96 | }, done);
97 | });
98 |
99 | });
100 |
101 | describe('.getDefaultDataPath()', function() {
102 |
103 | it('should be a string', function() {
104 | chai.expect(_.isString(storage.getDefaultDataPath())).to.be.true;
105 | });
106 |
107 | it('should be an absolute path', function() {
108 | chai.expect(path.isAbsolute(storage.getDefaultDataPath())).to.be.true;
109 | });
110 |
111 | });
112 |
113 | describe('.setDataPath()', function() {
114 |
115 | it('should be able to set a custom data path', function() {
116 | const newDataPath = os.tmpdir();
117 | storage.setDataPath(newDataPath);
118 | const dataPath = storage.getDataPath();
119 | chai.expect(dataPath).to.equal(newDataPath);
120 | });
121 |
122 | it('should set the default path if no argument', function() {
123 | storage.setDataPath();
124 | const dataPath = app.getPath('userData');
125 | chai.expect(storage.getDataPath().indexOf(dataPath)).to.equal(0);
126 | });
127 |
128 | it('should throw given a relative path', function() {
129 | chai.expect(function() {
130 | storage.setDataPath('foo');
131 | }).to.throw('The user data path should be an absolute directory');
132 | });
133 |
134 | });
135 |
136 | describe('.getDataPath()', function() {
137 |
138 | it('should initially return the default data path', function() {
139 | const dataPath = storage.getDataPath();
140 | chai.expect(dataPath).to.equal(utils.getDefaultDataPath());
141 | });
142 |
143 | it('should be able to return new data paths', function() {
144 | const newDataPath = os.tmpdir();
145 | storage.setDataPath(newDataPath);
146 | const dataPath = storage.getDataPath();
147 | chai.expect(dataPath).to.equal(newDataPath);
148 | });
149 |
150 | });
151 |
152 | describe('.get()', function() {
153 |
154 | it('should yield an error if no key', function(done) {
155 | storage.get(null, function(error, data) {
156 | chai.expect(error).to.be.an.instanceof(Error);
157 | chai.expect(error.message).to.equal('Missing key');
158 | chai.expect(data).to.not.exist;
159 | done();
160 | });
161 | });
162 |
163 | it('should yield an error if no key (sync)', function(done) {
164 | chai.expect(() => {
165 | storage.getSync(null);
166 | }).to.throw('Missing key');
167 | done();
168 | });
169 |
170 | it('should yield an error if key is not a string', function(done) {
171 | storage.get(123, function(error, data) {
172 | chai.expect(error).to.be.an.instanceof(Error);
173 | chai.expect(error.message).to.equal('Invalid key');
174 | chai.expect(data).to.not.exist;
175 | done();
176 | });
177 | });
178 |
179 | it('should yield an error if key is not a string (sync)', function(done) {
180 | chai.expect(() => {
181 | storage.getSync(123);
182 | }).to.throw('Invalid key');
183 | done();
184 | });
185 |
186 | it('should yield an error if key is a blank string', function(done) {
187 | storage.get(' ', function(error, data) {
188 | chai.expect(error).to.be.an.instanceof(Error);
189 | chai.expect(error.message).to.equal('Invalid key');
190 | chai.expect(data).to.not.exist;
191 | done();
192 | });
193 | });
194 |
195 | it('should yield an error if key is a blank string (sync)', function(done) {
196 | chai.expect(() => {
197 | storage.getSync(' ');
198 | }).to.throw('Invalid key');
199 | done();
200 | });
201 |
202 | describe('given the user data path does not exist', function() {
203 |
204 | beforeEach(function(done) {
205 | rimraf(storage.getDataPath(), done);
206 | });
207 |
208 | afterEach(function(done) {
209 | mkdirp(storage.getDataPath(), done);
210 | });
211 |
212 | it('should return an empty object for any key', function(done) {
213 | storage.get('foobarbaz', function(error, data) {
214 | chai.expect(error).to.not.exist;
215 | chai.expect(data).to.deep.equal({});
216 | done();
217 | });
218 | });
219 |
220 | it('should return an empty object for any key (sync)', function(done) {
221 | var data = storage.getSync('foobarbaz');
222 | chai.expect(data).to.deep.equal({});
223 | done();
224 | });
225 |
226 | });
227 |
228 | describe('given the same key stored in multiple data paths', function(done) {
229 |
230 | beforeEach(function(done) {
231 | this.newDataPath = path.join(os.tmpdir(), 'electron-json-storage');
232 | const self = this;
233 |
234 | async.waterfall([
235 | function(callback) {
236 | storage.setDataPath(self.newDataPath);
237 | callback();
238 | },
239 | function(callback) {
240 | storage.set('foo', { location: 'new' }, callback);
241 | },
242 | function(callback) {
243 | storage.setDataPath(utils.getDefaultDataPath());
244 | callback();
245 | },
246 | function(callback) {
247 | storage.set('foo', { location: 'default' }, callback);
248 | }
249 | ], done);
250 | });
251 |
252 | it('should initially return the key in the default location', function(done) {
253 | storage.get('foo', function(error, data) {
254 | chai.expect(error).to.not.exist;
255 | chai.expect(data).to.deep.equal({
256 | location: 'default'
257 | });
258 |
259 | done();
260 | });
261 | });
262 |
263 | it('should initially return the key in the default location (sync)', function(done) {
264 | var data = storage.getSync('foo');
265 | chai.expect(data).to.deep.equal({
266 | location: 'default'
267 | });
268 |
269 | done();
270 | });
271 |
272 | it('should return the new value given the right data path', function(done) {
273 | storage.setDataPath(this.newDataPath);
274 | storage.get('foo', function(error, data) {
275 | chai.expect(error).to.not.exist;
276 | chai.expect(data).to.deep.equal({
277 | location: 'new'
278 | });
279 |
280 | done();
281 | });
282 | });
283 |
284 | it('should return the new value given the right data path (sync)', function(done) {
285 | storage.setDataPath(this.newDataPath);
286 | var data = storage.getSync('foo');
287 | chai.expect(data).to.deep.equal({
288 | location: 'new'
289 | });
290 |
291 | done();
292 | });
293 |
294 | it('should return nothing given the wrong data path', function(done) {
295 | if (os.platform() === 'win32') {
296 | storage.setDataPath('C:\\tmp\\electron-json-storage');
297 | } else {
298 | storage.setDataPath('/tmp/electron-json-storage');
299 | }
300 |
301 | async.waterfall([
302 | storage.clear,
303 | function(callback) {
304 | storage.get('foo', callback);
305 | }
306 | ], function(error, data) {
307 | chai.expect(error).to.not.exist;
308 | chai.expect(data).to.deep.equal({});
309 | done();
310 | });
311 | });
312 |
313 | it('should return nothing given the wrong data path (sync)', function(done) {
314 | if (os.platform() === 'win32') {
315 | storage.setDataPath('C:\\tmp\\electron-json-storage');
316 | } else {
317 | storage.setDataPath('/tmp/electron-json-storage');
318 | }
319 |
320 | async.waterfall([
321 | storage.clear,
322 | function(callback) {
323 | callback(null, storage.getSync('foo'));
324 | }
325 | ], function(error, data) {
326 | chai.expect(error).to.not.exist;
327 | chai.expect(data).to.deep.equal({});
328 | done();
329 | });
330 | });
331 | });
332 |
333 | describe('given stored keys with a colon', function() {
334 |
335 | beforeEach(function(done) {
336 | async.parallel([
337 | _.partial(storage.set, 'foo', { name: 'foo' }),
338 | _.partial(storage.set, 'bar:colon', { name: 'bar' })
339 | ], done);
340 | });
341 |
342 | it('should return all stored keys', function(done) {
343 | storage.getAll(function(error, data) {
344 | chai.expect(error).to.not.exist;
345 | chai.expect(data).to.deep.equal({
346 | foo: { name: 'foo' },
347 | 'bar:colon': { name: 'bar' }
348 | });
349 | done();
350 | });
351 | });
352 |
353 | });
354 |
355 | describe('given stored settings', function() {
356 |
357 | beforeEach(function(done) {
358 | storage.set('foo', { data: 'hello world' }, done);
359 | });
360 |
361 | it('should yield the data', function(done) {
362 | storage.get('foo', function(error, data) {
363 | chai.expect(error).to.not.exist;
364 | chai.expect(data).to.deep.equal({ data: 'hello world' });
365 | done();
366 | });
367 | });
368 |
369 | it('should yield the data (sync)', function(done) {
370 | var data = storage.getSync('foo');
371 | chai.expect(data).to.deep.equal({ data: 'hello world' });
372 | done();
373 | });
374 |
375 | it('should yield the data if explicitly passing the extension', function(done) {
376 | storage.get('foo.json', function(error, data) {
377 | chai.expect(error).to.not.exist;
378 | chai.expect(data).to.deep.equal({ data: 'hello world' });
379 | done();
380 | });
381 | });
382 |
383 | it('should yield the data if explicitly passing the extension (sync)', function(done) {
384 | var data = storage.getSync('foo.json');
385 | chai.expect(data).to.deep.equal({ data: 'hello world' });
386 | done();
387 | });
388 |
389 | it('should yield an empty object given an incorrect key', function(done) {
390 | storage.get('foobarbaz', function(error, data) {
391 | chai.expect(error).to.not.exist;
392 | chai.expect(data).to.deep.equal({});
393 | done();
394 | });
395 | });
396 |
397 | it('should yield an empty object given an incorrect key (sync)', function(done) {
398 | var data = storage.getSync('foobarbaz');
399 | chai.expect(data).to.deep.equal({});
400 | done();
401 | });
402 |
403 | });
404 |
405 | describe('given invalid stored JSON', function() {
406 |
407 | beforeEach(function(done) {
408 | const fileName = utils.getFileName('foo');
409 |
410 | // Using fs directly since storage.set()
411 | // contains logic to prevent invalid JSON
412 | // from being written at all
413 | return fs.writeFile(fileName, 'Foo{bar}123', done);
414 |
415 | });
416 |
417 | it('should yield an error', function(done) {
418 | storage.get('foo', function(error, data) {
419 | chai.expect(error).to.be.an.instanceof(Error);
420 | chai.expect(error.message).to.equal('Invalid data: Foo{bar}123');
421 | chai.expect(data).to.not.exist;
422 | done();
423 | });
424 | });
425 |
426 | it('should yield an error (sync)', function(done) {
427 | chai.expect(() => {
428 | return storage.getSync('foo');
429 | }).to.throw('Invalid data: Foo{bar}123');
430 | done();
431 | });
432 |
433 | });
434 |
435 | describe('given a non-existent user data path', function() {
436 |
437 | beforeEach(function() {
438 | this.oldUserData = app.getPath('userData');
439 | app.setPath('userData', tmp.tmpNameSync());
440 | });
441 |
442 | afterEach(function() {
443 | app.setPath('userData', this.oldUserData);
444 | });
445 |
446 | it('should return an empty object for any key', function(done) {
447 | async.waterfall([
448 | function(callback) {
449 | storage.get('foo', callback);
450 | },
451 | ], function(error, result) {
452 | chai.expect(error).to.not.exist;
453 | chai.expect(result).to.deep.equal({});
454 | done();
455 | });
456 | });
457 |
458 | it('should return an empty object for any key (sync)', function(done) {
459 | var result = storage.getSync('foo');
460 | chai.expect(result).to.deep.equal({});
461 | done();
462 | });
463 |
464 | });
465 |
466 | });
467 |
468 | describe('.getMany()', function() {
469 |
470 | describe('given many stored keys in a custom data path', function() {
471 |
472 | beforeEach(function(done) {
473 | this.dataPath = os.tmpdir();
474 | async.parallel([
475 | _.partial(storage.set, 'foo', { name: 'foo' }, { dataPath: this.dataPath }),
476 | _.partial(storage.set, 'bar', { name: 'bar' }, { dataPath: this.dataPath }),
477 | _.partial(storage.set, 'baz', { name: 'baz' }, { dataPath: this.dataPath })
478 | ], done);
479 | });
480 |
481 | it('should return nothing given the wrong data path', function(done) {
482 | storage.getMany([ 'foo', 'baz' ], function(error, data) {
483 | chai.expect(error).to.not.exist;
484 | chai.expect(data).to.deep.equal({
485 | foo: {},
486 | baz: {}
487 | });
488 | done();
489 | });
490 | });
491 |
492 | it('should return the values given the correct data path', function(done) {
493 | storage.getMany([ 'foo', 'baz' ], {
494 | dataPath: this.dataPath
495 | }, function(error, data) {
496 | chai.expect(error).to.not.exist;
497 | chai.expect(data).to.deep.equal({
498 | foo: { name: 'foo' },
499 | baz: { name: 'baz' }
500 | });
501 | done();
502 | });
503 | });
504 |
505 | });
506 |
507 | describe('given many stored keys', function() {
508 |
509 | beforeEach(function(done) {
510 | async.parallel([
511 | _.partial(storage.set, 'foo', { name: 'foo' }),
512 | _.partial(storage.set, 'bar', { name: 'bar' }),
513 | _.partial(storage.set, 'baz', { name: 'baz' })
514 | ], done);
515 | });
516 |
517 | it('should return an empty object if no passed keys', function(done) {
518 | storage.getMany([], function(error, data) {
519 | chai.expect(error).to.not.exist;
520 | chai.expect(data).to.deep.equal({});
521 | done();
522 | });
523 | });
524 |
525 | it('should read the passed keys', function(done) {
526 | storage.getMany([ 'foo', 'baz' ], function(error, data) {
527 | chai.expect(error).to.not.exist;
528 | chai.expect(data).to.deep.equal({
529 | foo: { name: 'foo' },
530 | baz: { name: 'baz' }
531 | });
532 | done();
533 | });
534 | });
535 |
536 | it('should be able to read a single key', function(done) {
537 | storage.getMany([ 'foo' ], function(error, data) {
538 | chai.expect(error).to.not.exist;
539 | chai.expect(data).to.deep.equal({
540 | foo: { name: 'foo' }
541 | });
542 | done();
543 | });
544 | });
545 |
546 | it('should return empty objects for missing keys', function(done) {
547 | storage.getMany([ 'foo', 'hello' ], function(error, data) {
548 | chai.expect(error).to.not.exist;
549 | chai.expect(data).to.deep.equal({
550 | foo: { name: 'foo' },
551 | hello: {}
552 | });
553 | done();
554 | });
555 | });
556 |
557 | });
558 |
559 | });
560 |
561 | describe('.getAll()', function() {
562 |
563 | describe('given the user data path does not exist', function() {
564 |
565 | beforeEach(function(done) {
566 | rimraf(storage.getDataPath(), done);
567 | });
568 |
569 | afterEach(function(done) {
570 | mkdirp(storage.getDataPath(), done);
571 | });
572 |
573 | it('should return an empty object', function(done) {
574 | storage.getAll(function(error, data) {
575 | chai.expect(error).to.not.exist;
576 | chai.expect(data).to.deep.equal({});
577 | done();
578 | });
579 | });
580 |
581 | });
582 |
583 | describe('given no stored keys', function() {
584 |
585 | beforeEach(storage.clear);
586 |
587 | it('should return an empty object', function(done) {
588 | storage.getAll(function(error, data) {
589 | chai.expect(error).to.not.exist;
590 | chai.expect(data).to.deep.equal({});
591 | done();
592 | });
593 | });
594 |
595 | });
596 |
597 | describe('given many stored keys', function() {
598 |
599 | beforeEach(function(done) {
600 | async.parallel([
601 | _.partial(storage.set, 'foo', { name: 'foo' }),
602 | _.partial(storage.set, 'bar', { name: 'bar' }),
603 | _.partial(storage.set, 'baz', { name: 'baz' })
604 | ], done);
605 | });
606 |
607 | it('should return all stored keys', function(done) {
608 | storage.getAll(function(error, data) {
609 | chai.expect(error).to.not.exist;
610 | chai.expect(data).to.deep.equal({
611 | foo: { name: 'foo' },
612 | bar: { name: 'bar' },
613 | baz: { name: 'baz' }
614 | });
615 | done();
616 | });
617 | });
618 |
619 | });
620 |
621 | describe('given many stored keys in different locations', function() {
622 |
623 | beforeEach(function(done) {
624 | this.dataPath = path.join(os.tmpdir(), 'hello');
625 |
626 | async.parallel([
627 | _.partial(storage.set, 'foo', { name: 'foo' }),
628 | _.partial(storage.set, 'bar', { name: 'bar' }, {
629 | dataPath: this.dataPath
630 | }),
631 | _.partial(storage.set, 'baz', { name: 'baz' })
632 | ], done);
633 | });
634 |
635 | it('should return all stored keys in the default location', function(done) {
636 | storage.getAll(function(error, data) {
637 | chai.expect(error).to.not.exist;
638 | chai.expect(data).to.deep.equal({
639 | foo: { name: 'foo' },
640 | baz: { name: 'baz' }
641 | });
642 | done();
643 | });
644 | });
645 |
646 | it('should return all stored keys in a custom location', function(done) {
647 | storage.getAll({
648 | dataPath: this.dataPath
649 | }, function(error, data) {
650 | chai.expect(error).to.not.exist;
651 | chai.expect(data).to.deep.equal({
652 | bar: { name: 'bar' }
653 | });
654 | done();
655 | });
656 | });
657 |
658 | });
659 |
660 | describe('given many stored keys in different data directories', function() {
661 |
662 | beforeEach(function(done) {
663 | this.newDataPath = path.join(os.tmpdir(), 'electron-json-storage');
664 | const self = this;
665 |
666 | async.parallel([
667 | function(callback) {
668 | storage.setDataPath(self.newDataPath);
669 | callback();
670 | },
671 | function(callback) {
672 | storage.set('foo', { name: 'foo' }, callback);
673 | },
674 | function(callback) {
675 | storage.set('bar', { name: 'bar' }, callback);
676 | },
677 | function(callback) {
678 | storage.setDataPath(utils.getDefaultDataPath());
679 | callback();
680 | },
681 | function(callback) {
682 | storage.set('baz', { name: 'baz' }, callback);
683 | }
684 | ], done);
685 | });
686 |
687 | it('should return all stored keys depending on the data path', function(done) {
688 | storage.setDataPath(this.newDataPath);
689 |
690 | async.waterfall([
691 | storage.getAll,
692 | function(keys, callback) {
693 | chai.expect(keys).to.deep.equal({
694 | foo: { name: 'foo' },
695 | bar: { name: 'bar' }
696 | });
697 |
698 | callback();
699 | },
700 | function(callback) {
701 | storage.setDataPath(utils.getDefaultDataPath());
702 | callback();
703 | },
704 | storage.getAll,
705 | function(keys, callback) {
706 | chai.expect(keys).to.deep.equal({
707 | baz: { name: 'baz' }
708 | });
709 |
710 | callback();
711 | },
712 | ], done);
713 | });
714 |
715 | });
716 |
717 | });
718 |
719 | describe('.set()', function() {
720 |
721 | it('should yield an error if no key', function(done) {
722 | storage.set(null, { foo: 'bar' }, function(error) {
723 | chai.expect(error).to.be.an.instanceof(Error);
724 | chai.expect(error.message).to.equal('Missing key');
725 | done();
726 | });
727 | });
728 |
729 | it('should yield an error if no key (sync)', function(done) {
730 | chai.expect(() => {
731 | storage.setSync(null, { foo: 'bar' });
732 | }).to.throw('Missing key');
733 | done();
734 | });
735 |
736 | it('should yield an error if key is not a string', function(done) {
737 | storage.set(123, { foo: 'bar' }, function(error) {
738 | chai.expect(error).to.be.an.instanceof(Error);
739 | chai.expect(error.message).to.equal('Invalid key');
740 | done();
741 | });
742 | });
743 |
744 | it('should yield an error if key is not a string (sync)', function(done) {
745 | chai.expect(() => {
746 | storage.setSync(123, { foo: 'bar' });
747 | }).to.throw('Invalid key');
748 | done();
749 | });
750 |
751 | it('should yield an error if key is a blank string', function(done) {
752 | storage.set(' ', { foo: 'bar' }, function(error) {
753 | chai.expect(error).to.be.an.instanceof(Error);
754 | chai.expect(error.message).to.equal('Invalid key');
755 | done();
756 | });
757 | });
758 |
759 | it('should yield an error if key is a blank string (sync)', function(done) {
760 | chai.expect(() => {
761 | storage.setSync(' ', { foo: 'bar' });
762 | }).to.throw('Invalid key');
763 | done();
764 | });
765 |
766 | it('should yield an error if data is not a valid JSON object', function(done) {
767 | storage.set('foo', _.noop, function(error) {
768 | chai.expect(error).to.be.an.instanceof(Error);
769 | chai.expect(error.message).to.equal('Invalid JSON data');
770 | done();
771 | });
772 | });
773 |
774 | it('should yield an error if data is not a valid JSON object (sync)', function(done) {
775 | chai.expect(() => {
776 | storage.setSync('foo', _.noop);
777 | }).to.throw('Invalid JSON data');
778 | done();
779 | });
780 |
781 | it('should be able to store a valid JSON object in a file with a colon', function(done) {
782 | async.waterfall([
783 | function(callback) {
784 | storage.set('test:value', { foo: 'bar' }, callback);
785 | },
786 | function(callback) {
787 | storage.get('test:value', callback);
788 | }
789 | ], function(error, data) {
790 | chai.expect(error).to.not.exist;
791 | chai.expect(data).to.deep.equal({ foo: 'bar' });
792 | done();
793 | });
794 | });
795 |
796 | it('should be able to store a valid JSON object in a file with a colon (sync)', function(done) {
797 | async.waterfall([
798 | function(callback) {
799 | storage.setSync('test:value', { foo: 'bar' });
800 | callback();
801 | },
802 | function(callback) {
803 | callback(null, storage.getSync('test:value'));
804 | }
805 | ], function(error, data) {
806 | chai.expect(error).to.not.exist;
807 | chai.expect(data).to.deep.equal({ foo: 'bar' });
808 | done();
809 | });
810 | });
811 |
812 | it('should be able to store a valid JSON object', function(done) {
813 | async.waterfall([
814 | function(callback) {
815 | storage.set('foo', { foo: 'baz' }, callback);
816 | },
817 | function(callback) {
818 | storage.get('foo', callback);
819 | }
820 | ], function(error, data) {
821 | chai.expect(error).to.not.exist;
822 | chai.expect(data).to.deep.equal({ foo: 'baz' });
823 | done();
824 | });
825 | });
826 |
827 | it('should be able to store a valid JSON object (sync)', function(done) {
828 | async.waterfall([
829 | function(callback) {
830 | storage.setSync('foo', { foo: 'baz' });
831 | callback();
832 | },
833 | function(callback) {
834 | callback(null, storage.getSync('foo'));
835 | }
836 | ], function(error, data) {
837 | chai.expect(error).to.not.exist;
838 | chai.expect(data).to.deep.equal({ foo: 'baz' });
839 | done();
840 | });
841 | });
842 |
843 | it('should minify JSON documents by default', function(done) {
844 | async.waterfall([
845 | function(callback) {
846 | storage.set('foo', { foo: 'baz' }, callback);
847 | },
848 | function(callback) {
849 | fs.readFile(utils.getFileName('foo'), {
850 | encoding: 'utf8'
851 | }, callback);
852 | }
853 | ], function(error, data) {
854 | chai.expect(error).to.not.exist;
855 | chai.expect(data).to.deep.equal("{\"foo\":\"baz\"}");
856 | done();
857 | });
858 | });
859 |
860 | it('should minify JSON documents by default (sync)', function(done) {
861 | async.waterfall([
862 | function(callback) {
863 | storage.setSync('foo', { foo: 'baz' });
864 | callback();
865 | },
866 | function(callback) {
867 | callback(null, fs.readFileSync(utils.getFileName('foo'), {
868 | encoding: 'utf8'
869 | }));
870 | }
871 | ], function(error, data) {
872 | chai.expect(error).to.not.exist;
873 | chai.expect(data).to.deep.equal("{\"foo\":\"baz\"}");
874 | done();
875 | });
876 | });
877 |
878 | it('should minify JSON documents when setting prettyPrinting=false', function(done) {
879 | async.waterfall([
880 | function(callback) {
881 | storage.set('foo', { foo: 'baz' }, { prettyPrinting: false }, callback);
882 | },
883 | function(callback) {
884 | fs.readFile(utils.getFileName('foo'), {
885 | encoding: 'utf8'
886 | }, callback);
887 | }
888 | ], function(error, data) {
889 | chai.expect(error).to.not.exist;
890 | chai.expect(data).to.deep.equal("{\"foo\":\"baz\"}");
891 | done();
892 | });
893 | });
894 |
895 | it('should minify JSON documents when setting prettyPrinting=false (sync)', function(done) {
896 | async.waterfall([
897 | function(callback) {
898 | storage.setSync('foo', { foo: 'baz' }, { prettyPrinting: false });
899 | callback();
900 | },
901 | function(callback) {
902 | callback(null, fs.readFileSync(utils.getFileName('foo'), {
903 | encoding: 'utf8'
904 | }));
905 | }
906 | ], function(error, data) {
907 | chai.expect(error).to.not.exist;
908 | chai.expect(data).to.deep.equal("{\"foo\":\"baz\"}");
909 | done();
910 | });
911 | });
912 |
913 | it('should not minify JSON documents when setting prettyPrinting=true', function(done) {
914 | async.waterfall([
915 | function(callback) {
916 | storage.set('foo', { foo: 'baz' }, { prettyPrinting: true }, callback);
917 | },
918 | function(callback) {
919 | fs.readFile(utils.getFileName('foo'), {
920 | encoding: 'utf8'
921 | }, callback);
922 | }
923 | ], function(error, data) {
924 | chai.expect(error).to.not.exist;
925 | chai.expect(data).to.deep.equal("{\n \"foo\": \"baz\"\n}");
926 | done();
927 | });
928 | });
929 |
930 | it('should not minify JSON documents when setting prettyPrinting=true (sync)', function(done) {
931 | async.waterfall([
932 | function(callback) {
933 | storage.setSync('foo', { foo: 'baz' }, { prettyPrinting: true });
934 | callback();
935 | },
936 | function(callback) {
937 | callback(null, fs.readFileSync(utils.getFileName('foo'), {
938 | encoding: 'utf8'
939 | }));
940 | }
941 | ], function(error, data) {
942 | chai.expect(error).to.not.exist;
943 | chai.expect(data).to.deep.equal("{\n \"foo\": \"baz\"\n}");
944 | done();
945 | });
946 | });
947 |
948 | it('should be able to store a valid JSON pretty-printed object', function(done) {
949 | async.waterfall([
950 | function(callback) {
951 | storage.set('foo', { foo: 'baz' }, { prettyPrinting: true }, callback);
952 | },
953 | function(callback) {
954 | storage.get('foo', callback);
955 | }
956 | ], function(error, data) {
957 | chai.expect(error).to.not.exist;
958 | chai.expect(data).to.deep.equal({ foo: 'baz' });
959 | done();
960 | });
961 | });
962 |
963 | it('should be able to store a valid JSON pretty-printed object (sync)', function(done) {
964 | async.waterfall([
965 | function(callback) {
966 | storage.setSync('foo', { foo: 'baz' }, { prettyPrinting: true });
967 | callback();
968 | },
969 | function(callback) {
970 | callback(null, storage.getSync('foo'));
971 | }
972 | ], function(error, data) {
973 | chai.expect(error).to.not.exist;
974 | chai.expect(data).to.deep.equal({ foo: 'baz' });
975 | done();
976 | });
977 | });
978 |
979 | it('should be able to store a valid JSON object using validate=true', function(done) {
980 | async.waterfall([
981 | function(callback) {
982 | storage.set('foo', { foo: 'baz' }, { validate: true }, callback);
983 | },
984 | function(callback) {
985 | storage.get('foo', callback);
986 | }
987 | ], function(error, data) {
988 | chai.expect(error).to.not.exist;
989 | chai.expect(data).to.deep.equal({ foo: 'baz' });
990 | done();
991 | });
992 | });
993 |
994 | it('should be able to store a valid JSON object using validate=true (sync)', function(done) {
995 | async.waterfall([
996 | function(callback) {
997 | storage.setSync('foo', { foo: 'baz' }, { validate: true });
998 | callback();
999 | },
1000 | function(callback) {
1001 | callback(null, storage.getSync('foo'));
1002 | }
1003 | ], function(error, data) {
1004 | chai.expect(error).to.not.exist;
1005 | chai.expect(data).to.deep.equal({ foo: 'baz' });
1006 | done();
1007 | });
1008 | });
1009 |
1010 | it('should be able to store an object to a custom location', function(done) {
1011 | const newDataPath = os.tmpdir();
1012 |
1013 | async.waterfall([
1014 | function(callback) {
1015 | storage.set('foo', { foo: 'baz' }, {
1016 | dataPath: newDataPath
1017 | }, callback);
1018 | },
1019 | function(callback) {
1020 | async.parallel({
1021 | newDataPath: function(callback) {
1022 | storage.get('foo', {
1023 | dataPath: newDataPath
1024 | }, callback);
1025 | },
1026 | oldDataPath: function(callback) {
1027 | storage.get('foo', callback);
1028 | }
1029 | }, callback);
1030 | }
1031 | ], function(error, results) {
1032 | chai.expect(error).to.not.exist;
1033 | chai.expect(results.newDataPath).to.deep.equal({ foo: 'baz' });
1034 | chai.expect(results.oldDataPath).to.deep.equal({});
1035 | done();
1036 | });
1037 | });
1038 |
1039 | it('should be able to store an object to a custom location (sync)', function(done) {
1040 | const newDataPath = os.tmpdir();
1041 |
1042 | async.waterfall([
1043 | function(callback) {
1044 | storage.setSync('foo', { foo: 'baz' }, {
1045 | dataPath: newDataPath
1046 | });
1047 | callback();
1048 | },
1049 | function(callback) {
1050 | async.parallel({
1051 | newDataPath: function(callback) {
1052 | callback(null, storage.getSync('foo', {
1053 | dataPath: newDataPath
1054 | }));
1055 | },
1056 | oldDataPath: function(callback) {
1057 | callback(null, storage.getSync('foo'));
1058 | }
1059 | }, callback);
1060 | }
1061 | ], function(error, results) {
1062 | chai.expect(error).to.not.exist;
1063 | chai.expect(results.newDataPath).to.deep.equal({ foo: 'baz' });
1064 | chai.expect(results.oldDataPath).to.deep.equal({});
1065 | done();
1066 | });
1067 | });
1068 |
1069 | it('should ignore an explicit json extension', function(done) {
1070 | async.waterfall([
1071 | function(callback) {
1072 | storage.set('foo.json', { foo: 'baz' }, callback);
1073 | },
1074 | function(callback) {
1075 | storage.get('foo', callback);
1076 | }
1077 | ], function(error, data) {
1078 | chai.expect(error).to.not.exist;
1079 | chai.expect(data).to.deep.equal({ foo: 'baz' });
1080 | done();
1081 | });
1082 | });
1083 |
1084 | it('should ignore an explicit json extension (sync)', function(done) {
1085 | async.waterfall([
1086 | function(callback) {
1087 | storage.setSync('foo.json', { foo: 'baz' });
1088 | callback();
1089 | },
1090 | function(callback) {
1091 | callback(null, storage.getSync('foo'));
1092 | }
1093 | ], function(error, data) {
1094 | chai.expect(error).to.not.exist;
1095 | chai.expect(data).to.deep.equal({ foo: 'baz' });
1096 | done();
1097 | });
1098 | });
1099 |
1100 | it('should accept special characters as the key name', function(done) {
1101 | const key = 'foo?bar:baz';
1102 | async.waterfall([
1103 | function(callback) {
1104 | storage.set(key, { foo: 'baz' }, callback);
1105 | },
1106 | function(callback) {
1107 | storage.get(key, callback);
1108 | }
1109 | ], function(error, data) {
1110 | chai.expect(error).to.not.exist;
1111 | chai.expect(data).to.deep.equal({ foo: 'baz' });
1112 | done();
1113 | });
1114 | });
1115 |
1116 | it('should accept special characters as the key name (sync)', function(done) {
1117 | const key = 'foo?bar:baz';
1118 | async.waterfall([
1119 | function(callback) {
1120 | storage.setSync(key, { foo: 'baz' });
1121 | callback();
1122 | },
1123 | function(callback) {
1124 | callback(null, storage.getSync(key));
1125 | }
1126 | ], function(error, data) {
1127 | chai.expect(error).to.not.exist;
1128 | chai.expect(data).to.deep.equal({ foo: 'baz' });
1129 | done();
1130 | });
1131 | });
1132 |
1133 | it('should accept spaces in the key name', function(done){
1134 | const key = 'foo bar baz';
1135 | async.waterfall([
1136 | function(callback) {
1137 | storage.set(key, { foo: 'baz' }, callback);
1138 | },
1139 | function(callback) {
1140 | storage.get(key, callback);
1141 | }
1142 | ], function(error, data) {
1143 | chai.expect(error).to.not.exist;
1144 | chai.expect(data).to.deep.equal({ foo: 'baz' });
1145 | done();
1146 | });
1147 | });
1148 |
1149 | it('should accept spaces in the key name (sync)', function(done){
1150 | const key = 'foo bar baz';
1151 | async.waterfall([
1152 | function(callback) {
1153 | storage.setSync(key, { foo: 'baz' });
1154 | callback();
1155 | },
1156 | function(callback) {
1157 | callback(null, storage.getSync(key));
1158 | }
1159 | ], function(error, data) {
1160 | chai.expect(error).to.not.exist;
1161 | chai.expect(data).to.deep.equal({ foo: 'baz' });
1162 | done();
1163 | });
1164 | });
1165 |
1166 |
1167 | describe('given an existing stored key', function() {
1168 |
1169 | beforeEach(function(done) {
1170 | storage.set('foo', { foo: 'bar' }, done);
1171 | });
1172 |
1173 | it('should be able to override the stored key', function(done) {
1174 | async.waterfall([
1175 | function(callback) {
1176 | storage.get('foo', callback);
1177 | },
1178 | function(data, callback) {
1179 | chai.expect(data).to.deep.equal({ foo: 'bar' });
1180 | storage.set('foo', { foo: 'baz' }, callback);
1181 | },
1182 | function(callback) {
1183 | storage.get('foo', callback);
1184 | }
1185 | ], function(error, data) {
1186 | chai.expect(error).to.not.exist;
1187 | chai.expect(data).to.deep.equal({ foo: 'baz' });
1188 | done();
1189 | });
1190 | });
1191 |
1192 | it('should not override the stored key if the passed data is invalid', function(done) {
1193 | storage.set('foo', _.noop, function(error) {
1194 | chai.expect(error).to.be.an.instanceof(Error);
1195 |
1196 | storage.get('foo', function(error, data) {
1197 | chai.expect(error).to.not.exist;
1198 | chai.expect(data).to.deep.equal({ foo: 'bar' });
1199 | done();
1200 | });
1201 | });
1202 | });
1203 |
1204 | });
1205 |
1206 | describe('given a non-existent user data path', function() {
1207 |
1208 | beforeEach(function() {
1209 | this.oldUserData = app.getPath('userData');
1210 | app.setPath('userData', tmp.tmpNameSync());
1211 | });
1212 |
1213 | afterEach(function() {
1214 | app.setPath('userData', this.oldUserData);
1215 | });
1216 |
1217 | it('should be able to set data', function(done) {
1218 | async.waterfall([
1219 | function(callback) {
1220 | storage.set('foo', { foo: 'bar' }, callback);
1221 | },
1222 | function(callback) {
1223 | storage.get('foo', callback);
1224 | },
1225 | ], function(error, result) {
1226 | chai.expect(error).to.not.exist;
1227 | chai.expect(result).to.deep.equal({ foo: 'bar' });
1228 | done();
1229 | });
1230 | });
1231 |
1232 | });
1233 |
1234 | });
1235 |
1236 | describe('.has()', function() {
1237 |
1238 | it('should yield an error if no key', function(done) {
1239 | storage.has(null, function(error, hasKey) {
1240 | chai.expect(error).to.be.an.instanceof(Error);
1241 | chai.expect(error.message).to.equal('Missing key');
1242 | chai.expect(hasKey).to.not.exist;
1243 | done();
1244 | });
1245 | });
1246 |
1247 | it('should yield an error if key is not a string', function(done) {
1248 | storage.has(123, function(error, hasKey) {
1249 | chai.expect(error).to.be.an.instanceof(Error);
1250 | chai.expect(error.message).to.equal('Invalid key');
1251 | chai.expect(hasKey).to.not.exist;
1252 | done();
1253 | });
1254 | });
1255 |
1256 | it('should yield an error if key is a blank string', function(done) {
1257 | storage.has(' ', function(error, hasKey) {
1258 | chai.expect(error).to.be.an.instanceof(Error);
1259 | chai.expect(error.message).to.equal('Invalid key');
1260 | chai.expect(hasKey).to.not.exist;
1261 | done();
1262 | });
1263 | });
1264 |
1265 | describe('given a stored key in a custom location', function() {
1266 |
1267 | beforeEach(function(done) {
1268 | this.dataPath = os.tmpdir();
1269 | storage.set('foo', { foo: 'bar' }, {
1270 | dataPath: this.dataPath
1271 | }, done);
1272 | });
1273 |
1274 | it('should yield false given the default data path', function(done) {
1275 | storage.has('foo', function(error, hasKey) {
1276 | chai.expect(error).to.not.exist;
1277 | chai.expect(hasKey).to.equal(false);
1278 | done();
1279 | });
1280 | });
1281 |
1282 | it('should yield true given the custom data path', function(done) {
1283 | storage.has('foo', {
1284 | dataPath: this.dataPath
1285 | }, function(error, hasKey) {
1286 | chai.expect(error).to.not.exist;
1287 | chai.expect(hasKey).to.equal(true);
1288 | done();
1289 | });
1290 | });
1291 |
1292 | });
1293 |
1294 | describe('given a stored key', function() {
1295 |
1296 | beforeEach(function(done) {
1297 | storage.set('foo', { foo: 'bar' }, done);
1298 | });
1299 |
1300 | it('should yield true if the key exists', function(done) {
1301 | storage.has('foo', function(error, hasKey) {
1302 | chai.expect(error).to.not.exist;
1303 | chai.expect(hasKey).to.equal(true);
1304 | done();
1305 | });
1306 | });
1307 |
1308 | it('should yield true if the key has a json extension', function(done) {
1309 | storage.has('foo.json', function(error, hasKey) {
1310 | chai.expect(error).to.not.exist;
1311 | chai.expect(hasKey).to.equal(true);
1312 | done();
1313 | });
1314 | });
1315 |
1316 | it('should yield false if the key does not exist', function(done) {
1317 | storage.has('hello', function(error, hasKey) {
1318 | chai.expect(error).to.not.exist;
1319 | chai.expect(hasKey).to.equal(false);
1320 | done();
1321 | });
1322 | });
1323 |
1324 | });
1325 |
1326 | });
1327 |
1328 | describe('.keys()', function() {
1329 |
1330 | describe('given a file name with colons', function() {
1331 |
1332 | beforeEach(function(done) {
1333 | async.waterfall([
1334 | _.partial(storage.set, 'one', 'foo'),
1335 | _.partial(storage.set, 'two', 'bar'),
1336 | _.partial(storage.set, 'three:colon', 'baz')
1337 | ], done);
1338 | });
1339 |
1340 | afterEach(function(done) {
1341 | rimraf(storage.getDataPath(), done);
1342 | });
1343 |
1344 | it('should correctly decode the file names', function(done) {
1345 | storage.keys(function(error, keys) {
1346 | chai.expect(error).to.not.exist;
1347 | chai.expect(keys).to.deep.equal([
1348 | 'one',
1349 | 'three:colon',
1350 | 'two'
1351 | ]);
1352 |
1353 | done();
1354 | });
1355 |
1356 | });
1357 |
1358 | });
1359 |
1360 | describe('given keys in a custom location', function() {
1361 |
1362 | beforeEach(function(done) {
1363 | this.dataPath = path.join(os.tmpdir(), 'custom-data-path');
1364 | async.waterfall([
1365 | _.partial(storage.set, 'one', 'foo', { dataPath: this.dataPath }),
1366 | _.partial(storage.set, 'two', 'bar', { dataPath: this.dataPath }),
1367 | _.partial(storage.set, 'three', 'baz', { dataPath: this.dataPath })
1368 | ], done);
1369 | });
1370 |
1371 | it('should return nothing given the default data path', function(done) {
1372 | storage.keys(function(error, keys) {
1373 | chai.expect(error).to.not.exist;
1374 | chai.expect(keys.length).to.equal(0);
1375 | done();
1376 | });
1377 | });
1378 |
1379 | it('should return the keys given the custom location', function(done) {
1380 | storage.keys({
1381 | dataPath: this.dataPath
1382 | }, function(error, keys) {
1383 | chai.expect(error).to.not.exist;
1384 | chai.expect(keys.length).to.equal(3);
1385 | chai.expect(_.includes(keys, 'one')).to.be.true;
1386 | chai.expect(_.includes(keys, 'two')).to.be.true;
1387 | chai.expect(_.includes(keys, 'three')).to.be.true;
1388 | done();
1389 | });
1390 | });
1391 |
1392 | });
1393 |
1394 | describe('given invalid files in the settings directory', function() {
1395 |
1396 | beforeEach(function(done) {
1397 | async.waterfall([
1398 | _.partial(storage.set, 'one', 'foo'),
1399 | _.partial(storage.set, 'two', 'bar'),
1400 | _.partial(storage.set, 'three', 'baz'),
1401 | _.partial(fs.writeFile, path.join(storage.getDataPath(), '.DS_Store'), 'foo'),
1402 | _.partial(fs.writeFile, path.join(storage.getDataPath(), 'one.json.lock.STALE'), 'foo'),
1403 | _.partial(fs.writeFile, path.join(storage.getDataPath(), 'one.json.lock'), 'foo')
1404 | ], done);
1405 | });
1406 |
1407 | afterEach(function(done) {
1408 | rimraf(storage.getDataPath(), done);
1409 | });
1410 |
1411 | it('should only include the json files', function(done) {
1412 | storage.keys(function(error, keys) {
1413 | chai.expect(error).to.not.exist;
1414 | chai.expect(keys.length).to.equal(3);
1415 | chai.expect(_.includes(keys, 'one')).to.be.true;
1416 | chai.expect(_.includes(keys, 'two')).to.be.true;
1417 | chai.expect(_.includes(keys, 'three')).to.be.true;
1418 | done();
1419 | });
1420 | });
1421 |
1422 | });
1423 |
1424 | it('should yield an empty array if no keys', function(done) {
1425 | storage.keys(function(error, keys) {
1426 | chai.expect(error).to.not.exist;
1427 | chai.expect(keys).to.deep.equal([]);
1428 | done();
1429 | });
1430 | });
1431 |
1432 | it('should yield a single key if there is one saved setting', function(done) {
1433 | async.waterfall([
1434 | function(callback) {
1435 | storage.set('foo', 'bar', callback);
1436 | },
1437 | storage.keys,
1438 | ], function(error, keys) {
1439 | chai.expect(error).to.not.exist;
1440 | chai.expect(keys).to.deep.equal([ 'foo' ]);
1441 | done();
1442 | });
1443 | });
1444 |
1445 | it('should ignore the .json extension', function(done) {
1446 | async.waterfall([
1447 | function(callback) {
1448 | storage.set('foo.json', 'bar', callback);
1449 | },
1450 | storage.keys,
1451 | ], function(error, keys) {
1452 | chai.expect(error).to.not.exist;
1453 | chai.expect(keys).to.deep.equal([ 'foo' ]);
1454 | done();
1455 | });
1456 | });
1457 |
1458 | it('should only remove the .json extension', function(done) {
1459 | async.waterfall([
1460 | function(callback) {
1461 | storage.set('foo.data', 'bar', callback);
1462 | },
1463 | storage.keys,
1464 | ], function(error, keys) {
1465 | chai.expect(error).to.not.exist;
1466 | chai.expect(keys).to.deep.equal([ 'foo.data' ]);
1467 | done();
1468 | });
1469 | });
1470 |
1471 | it('should detect multiple saved settings', function(done) {
1472 | async.waterfall([
1473 | function(callback) {
1474 | async.parallel([
1475 | _.partial(storage.set, 'one', 'foo'),
1476 | _.partial(storage.set, 'two', 'bar'),
1477 | _.partial(storage.set, 'three', 'baz')
1478 | ], callback);
1479 | },
1480 | function(result, callback) {
1481 | storage.keys(callback);
1482 | }
1483 | ], function(error, keys) {
1484 | chai.expect(error).to.not.exist;
1485 | chai.expect(keys).to.deep.equal([
1486 | 'one',
1487 | 'three',
1488 | 'two'
1489 | ]);
1490 | done();
1491 | });
1492 | });
1493 |
1494 | });
1495 |
1496 | describe('.remove()', function() {
1497 |
1498 | it('should yield an error if no key', function(done) {
1499 | storage.remove(null, function(error) {
1500 | chai.expect(error).to.be.an.instanceof(Error);
1501 | chai.expect(error.message).to.equal('Missing key');
1502 | done();
1503 | });
1504 | });
1505 |
1506 | it('should yield an error if key is not a string', function(done) {
1507 | storage.remove(123, function(error) {
1508 | chai.expect(error).to.be.an.instanceof(Error);
1509 | chai.expect(error.message).to.equal('Invalid key');
1510 | done();
1511 | });
1512 | });
1513 |
1514 | it('should yield an error if key is a blank string', function(done) {
1515 | storage.remove(' ', function(error) {
1516 | chai.expect(error).to.be.an.instanceof(Error);
1517 | chai.expect(error.message).to.equal('Invalid key');
1518 | done();
1519 | });
1520 | });
1521 |
1522 | describe('given a stored key in a custom location', function() {
1523 |
1524 | beforeEach(function(done) {
1525 | this.dataPath = os.tmpdir();
1526 | storage.set('foo', { foo: 'bar' }, { dataPath: this.dataPath }, done);
1527 | });
1528 |
1529 | it('should be able to remove the key', function(done) {
1530 | var options = {
1531 | dataPath: this.dataPath
1532 | };
1533 |
1534 | async.waterfall([
1535 | function(callback) {
1536 | storage.has('foo', options, callback);
1537 | },
1538 | function(hasKey, callback) {
1539 | chai.expect(hasKey).to.be.true;
1540 | storage.remove('foo', options, callback);
1541 | },
1542 | function(callback) {
1543 | storage.has('foo', options, callback);
1544 | }
1545 | ], function(error, hasKey) {
1546 | chai.expect(error).to.not.exist;
1547 | chai.expect(hasKey).to.be.false;
1548 | done();
1549 | });
1550 | });
1551 |
1552 | });
1553 |
1554 | describe('given a stored key', function() {
1555 |
1556 | beforeEach(function(done) {
1557 | storage.set('foo', { foo: 'bar' }, done);
1558 | });
1559 |
1560 | it('should be able to remove the key', function(done) {
1561 | async.waterfall([
1562 | function(callback) {
1563 | storage.has('foo', callback);
1564 | },
1565 | function(hasKey, callback) {
1566 | chai.expect(hasKey).to.be.true;
1567 | storage.remove('foo', callback);
1568 | },
1569 | function(callback) {
1570 | storage.has('foo', callback);
1571 | }
1572 | ], function(error, hasKey) {
1573 | chai.expect(error).to.not.exist;
1574 | chai.expect(hasKey).to.be.false;
1575 | done();
1576 | });
1577 | });
1578 |
1579 | it('should do nothing if the key does not exist', function(done) {
1580 | async.waterfall([
1581 | function(callback) {
1582 | storage.has('bar', callback);
1583 | },
1584 | function(hasKey, callback) {
1585 | chai.expect(hasKey).to.be.false;
1586 | storage.remove('bar', callback);
1587 | },
1588 | function(callback) {
1589 | storage.has('bar', callback);
1590 | }
1591 | ], function(error, hasKey) {
1592 | chai.expect(error).to.not.exist;
1593 | chai.expect(hasKey).to.be.false;
1594 | done();
1595 | });
1596 | });
1597 |
1598 | });
1599 |
1600 | });
1601 |
1602 | describe('.clear()', function() {
1603 |
1604 | it('should not yield an error if no keys', function(done) {
1605 | storage.clear(function(error) {
1606 | chai.expect(error).to.not.exist;
1607 | done();
1608 | });
1609 | });
1610 |
1611 | describe('given a stored key in a custom location', function() {
1612 |
1613 | beforeEach(function(done) {
1614 | this.dataPath = os.tmpdir();
1615 | storage.set('foo', { foo: 'bar' }, { dataPath: this.dataPath }, done);
1616 | });
1617 |
1618 | it('should clear the key', function(done) {
1619 | var options = {
1620 | dataPath: this.dataPath
1621 | };
1622 |
1623 | async.waterfall([
1624 | function(callback) {
1625 | storage.has('foo', options, callback);
1626 | },
1627 | function(hasKey, callback) {
1628 | chai.expect(hasKey).to.be.true;
1629 | storage.clear(options, callback);
1630 | },
1631 | function(callback) {
1632 | storage.has('foo', options, callback);
1633 | }
1634 | ], function(error, hasKey) {
1635 | chai.expect(error).to.not.exist;
1636 | chai.expect(hasKey).to.be.false;
1637 | done();
1638 | });
1639 | });
1640 |
1641 | });
1642 |
1643 | describe('given a stored key', function() {
1644 |
1645 | beforeEach(function(done) {
1646 | storage.set('foo', { foo: 'bar' }, done);
1647 | });
1648 |
1649 | it('should clear the key', function(done) {
1650 | async.waterfall([
1651 | function(callback) {
1652 | storage.has('foo', callback);
1653 | },
1654 | function(hasKey, callback) {
1655 | chai.expect(hasKey).to.be.true;
1656 | storage.clear(callback);
1657 | },
1658 | function(callback) {
1659 | storage.has('foo', callback);
1660 | }
1661 | ], function(error, hasKey) {
1662 | chai.expect(error).to.not.exist;
1663 | chai.expect(hasKey).to.be.false;
1664 | done();
1665 | });
1666 | });
1667 |
1668 | it('should not delete the user data storage directory', function(done) {
1669 | const isDirectory = function(dir, callback) {
1670 | fs.stat(dir, function(error, stat) {
1671 | if (error) {
1672 | if (error.code === 'ENOENT') {
1673 | return callback(null, false);
1674 | }
1675 |
1676 | return callback(error);
1677 | }
1678 |
1679 | return callback(null, stat.isDirectory());
1680 | });
1681 | };
1682 |
1683 | const userDataPath = storage.getDataPath();
1684 |
1685 | async.waterfall([
1686 | _.partial(isDirectory, userDataPath),
1687 | function(directory, callback) {
1688 | chai.expect(directory).to.be.true;
1689 | storage.clear(callback);
1690 | },
1691 | _.partial(isDirectory, userDataPath)
1692 | ], function(error, directory) {
1693 | chai.expect(error).to.not.exist;
1694 | chai.expect(directory).to.be.true;
1695 | done();
1696 | });
1697 | });
1698 |
1699 | it('should not delete other files inside the user data directory', function(done) {
1700 | const userDataPath = app.getPath('userData');
1701 |
1702 | async.waterfall([
1703 | function(callback) {
1704 | async.parallel([
1705 | _.partial(fs.writeFile, path.join(userDataPath, 'foo'), 'foo'),
1706 | _.partial(fs.writeFile, path.join(userDataPath, 'bar'), 'bar.json')
1707 | ], callback);
1708 | },
1709 | function(results, callback) {
1710 | storage.clear(callback);
1711 | },
1712 | function(callback) {
1713 | async.parallel([
1714 | _.partial(fs.readFile, path.join(userDataPath, 'foo'), { encoding: 'utf8' }),
1715 | _.partial(fs.readFile, path.join(userDataPath, 'bar'), { encoding: 'utf8' })
1716 | ], callback);
1717 | }
1718 | ], function(error, results) {
1719 | chai.expect(error).to.not.exist;
1720 | chai.expect(results).to.deep.equal([ 'foo', 'bar.json' ]);
1721 | done();
1722 | });
1723 | });
1724 |
1725 | });
1726 |
1727 | describe('given many stored keys', function() {
1728 |
1729 | beforeEach(function(done) {
1730 | async.parallel([
1731 | _.partial(storage.set, 'foo', { name: 'foo' }),
1732 | _.partial(storage.set, 'bar', { name: 'bar' }),
1733 | _.partial(storage.set, 'baz', { name: 'baz' })
1734 | ], done);
1735 | });
1736 |
1737 | it('should clear all stored keys', function(done) {
1738 | async.waterfall([
1739 | function(callback) {
1740 | async.parallel({
1741 | foo: _.partial(storage.has, 'foo'),
1742 | bar: _.partial(storage.has, 'bar'),
1743 | baz: _.partial(storage.has, 'baz')
1744 | }, callback);
1745 | },
1746 | function(results, callback) {
1747 | chai.expect(results.foo).to.be.true;
1748 | chai.expect(results.bar).to.be.true;
1749 | chai.expect(results.baz).to.be.true;
1750 |
1751 | storage.clear(callback);
1752 | },
1753 | function(callback) {
1754 | async.parallel({
1755 | foo: _.partial(storage.has, 'foo'),
1756 | bar: _.partial(storage.has, 'bar'),
1757 | baz: _.partial(storage.has, 'baz')
1758 | }, callback);
1759 | },
1760 | ], function(error, results) {
1761 | chai.expect(error).to.not.exist;
1762 | chai.expect(results.foo).to.be.false;
1763 | chai.expect(results.bar).to.be.false;
1764 | chai.expect(results.baz).to.be.false;
1765 | done();
1766 | });
1767 | });
1768 |
1769 | });
1770 |
1771 | });
1772 |
1773 | });
1774 |
--------------------------------------------------------------------------------
/tests/utils.spec.js:
--------------------------------------------------------------------------------
1 | /*
2 | * The MIT License
3 | *
4 | * Copyright (c) 2016 Juan Cruz Viotti. https://github.com/jviotti
5 | *
6 | * Permission is hereby granted, free of charge, to any person obtaining a copy
7 | * of this software and associated documentation files (the "Software"), to deal
8 | * in the Software without restriction, including without limitation the rights
9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | * copies of the Software, and to permit persons to whom the Software is
11 | * furnished to do so, subject to the following conditions:
12 | *
13 | * The above copyright notice and this permission notice shall be included in
14 | * all copies or substantial portions of the Software.
15 | *
16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 | * THE SOFTWARE.
23 | */
24 |
25 | 'use strict';
26 |
27 | const chai = require('chai');
28 | const path = require('path');
29 | const utils = require('../lib/utils');
30 | const electron = require('electron');
31 | const os = require('os');
32 | const app = electron.app || electron.remote.app;
33 |
34 | describe('Utils', function() {
35 |
36 | this.timeout(20000);
37 |
38 | describe('.getDataPath()', function() {
39 |
40 | it('should return an absolute path', function() {
41 | chai.expect(path.isAbsolute(utils.getDataPath())).to.be.true;
42 | });
43 |
44 | it('should equal the dirname of the path returned by getFileName()', function() {
45 | const fileName = utils.getFileName('foo');
46 | const userDataPath = utils.getDataPath();
47 | chai.expect(path.dirname(fileName)).to.equal(userDataPath);
48 | });
49 |
50 | it('should pick up external changes to the userData path', function() {
51 | utils.setDataPath(undefined);
52 | const oldDataPath = app.getPath('userData');
53 | chai.expect(utils.getDataPath().indexOf(oldDataPath)).to.equal(0);
54 | const newPath = os.platform() === 'win32' ? 'C:\\foo' : '/foo';
55 | app.setPath('userData', newPath);
56 | chai.expect(utils.getDataPath().indexOf(newPath)).to.equal(0);
57 | app.setPath('userData', oldDataPath);
58 | });
59 |
60 | });
61 |
62 | describe('.setDataPath()', function() {
63 |
64 | beforeEach(function() {
65 | utils.setDataPath(utils.getDefaultDataPath());
66 | });
67 |
68 | it('should be able to go back to the default', function() {
69 | utils.setDataPath(path.join(os.tmpdir(), 'foo'));
70 | chai.expect(utils.getDataPath()).to.not.equal(utils.getDefaultDataPath());
71 | utils.setDataPath(utils.getDefaultDataPath());
72 | chai.expect(utils.getDataPath()).to.equal(utils.getDefaultDataPath());
73 | });
74 |
75 | it('should change the user data path', function() {
76 | const newUserDataPath = path.join(utils.getDataPath(), 'foo' , 'bar');
77 | utils.setDataPath(newUserDataPath);
78 | chai.expect(utils.getDataPath()).to.equal(newUserDataPath);
79 | });
80 |
81 | it('should throw if path is not absolute', function() {
82 | chai.expect(function() {
83 | utils.setDataPath('testpath/storage');
84 | }).to.throw('The user data path should be an absolute directory');
85 | });
86 |
87 | });
88 |
89 | describe('.getFileName()', function() {
90 |
91 | it('should throw if no key', function() {
92 | chai.expect(function() {
93 | utils.getFileName(null);
94 | }).to.throw('Missing key');
95 | });
96 |
97 | it('should throw if key is not a string', function() {
98 | chai.expect(function() {
99 | utils.getFileName(123);
100 | }).to.throw('Invalid key');
101 | });
102 |
103 | it('should throw if key is a blank string', function() {
104 | chai.expect(function() {
105 | utils.getFileName(' ');
106 | }).to.throw('Invalid key');
107 | });
108 |
109 | it('should append the .json extension automatically', function() {
110 | const fileName = utils.getFileName('foo');
111 | chai.expect(path.basename(fileName)).to.equal('foo.json');
112 | });
113 |
114 | it('should not add .json twice', function() {
115 | const fileName = utils.getFileName('foo.json');
116 | chai.expect(path.basename(fileName)).to.equal('foo.json');
117 | });
118 |
119 | it('should preserve an extension other than .json', function() {
120 | const fileName = utils.getFileName('foo.data');
121 | chai.expect(path.basename(fileName)).to.equal('foo.data.json');
122 | });
123 |
124 | it('should return an absolute path', function() {
125 | const fileName = utils.getFileName('foo.data');
126 | chai.expect(path.isAbsolute(fileName)).to.be.true;
127 | });
128 |
129 | it('should encode special characters', function() {
130 | const fileName = utils.getFileName('foo?bar:baz');
131 | chai.expect(path.basename(fileName)).to.equal('foo%3Fbar%3Abaz.json');
132 | });
133 |
134 | // HTTP encoding doesn't help us here
135 | it('should replace asterisks with hyphens', function() {
136 | const fileName = utils.getFileName('john6638@gmail*dot*com');
137 | chai.expect(path.basename(fileName)).to.equal('john6638%40gmail-dot-com.json');
138 | });
139 |
140 | it('should allow spaces in file names', function() {
141 | const fileName = utils.getFileName('foo bar');
142 | chai.expect(path.basename(fileName)).to.equal('foo bar.json');
143 | });
144 |
145 | it('should react to user data path changes', function() {
146 | const newUserDataPath = path.join(utils.getDataPath(), 'foo' , 'bar');
147 | utils.setDataPath(newUserDataPath);
148 | const fileName = utils.getFileName('foo');
149 | chai.expect(path.dirname(fileName)).to.equal(newUserDataPath);
150 | });
151 |
152 | it('should accept a custom data path', function() {
153 | const dataPath = path.join('my', 'custom', 'data', 'path');
154 | const fileName = utils.getFileName('foo', {
155 | dataPath: dataPath
156 | });
157 |
158 | chai.expect(fileName).to.equal(path.join(dataPath, 'foo.json'));
159 | });
160 |
161 | });
162 |
163 | });
164 |
--------------------------------------------------------------------------------