1
0
mirror of https://github.com/laurent22/joplin.git synced 2024-11-27 08:21:03 +02:00

Removed cache package dependency and implemented one more suitable for React Native

This commit is contained in:
Laurent Cozic 2020-10-15 18:40:13 +01:00
parent efa346fea4
commit eec32cf70a
18 changed files with 172 additions and 40 deletions

View File

@ -64,6 +64,7 @@ CliClient/build/
# AUTO-GENERATED - EXCLUDED TYPESCRIPT BUILD # AUTO-GENERATED - EXCLUDED TYPESCRIPT BUILD
CliClient/app/LinkSelector.js CliClient/app/LinkSelector.js
CliClient/app/services/plugins/PluginRunner.js CliClient/app/services/plugins/PluginRunner.js
CliClient/tests/InMemoryCache.js
CliClient/tests/models_Setting.js CliClient/tests/models_Setting.js
CliClient/tests/services_CommandService.js CliClient/tests/services_CommandService.js
CliClient/tests/services_InteropService.js CliClient/tests/services_InteropService.js
@ -210,6 +211,7 @@ ReactNativeClient/lib/hooks/useEffectDebugger.js
ReactNativeClient/lib/hooks/useImperativeHandlerDebugger.js ReactNativeClient/lib/hooks/useImperativeHandlerDebugger.js
ReactNativeClient/lib/hooks/usePrevious.js ReactNativeClient/lib/hooks/usePrevious.js
ReactNativeClient/lib/hooks/usePropsDebugger.js ReactNativeClient/lib/hooks/usePropsDebugger.js
ReactNativeClient/lib/InMemoryCache.js
ReactNativeClient/lib/joplin-renderer/MdToHtml/rules/checkbox.js ReactNativeClient/lib/joplin-renderer/MdToHtml/rules/checkbox.js
ReactNativeClient/lib/joplin-renderer/MdToHtml/rules/fence.js ReactNativeClient/lib/joplin-renderer/MdToHtml/rules/fence.js
ReactNativeClient/lib/joplin-renderer/MdToHtml/rules/mermaid.js ReactNativeClient/lib/joplin-renderer/MdToHtml/rules/mermaid.js

2
.gitignore vendored
View File

@ -58,6 +58,7 @@ plugin_types/
# AUTO-GENERATED - EXCLUDED TYPESCRIPT BUILD # AUTO-GENERATED - EXCLUDED TYPESCRIPT BUILD
CliClient/app/LinkSelector.js CliClient/app/LinkSelector.js
CliClient/app/services/plugins/PluginRunner.js CliClient/app/services/plugins/PluginRunner.js
CliClient/tests/InMemoryCache.js
CliClient/tests/models_Setting.js CliClient/tests/models_Setting.js
CliClient/tests/services_CommandService.js CliClient/tests/services_CommandService.js
CliClient/tests/services_InteropService.js CliClient/tests/services_InteropService.js
@ -204,6 +205,7 @@ ReactNativeClient/lib/hooks/useEffectDebugger.js
ReactNativeClient/lib/hooks/useImperativeHandlerDebugger.js ReactNativeClient/lib/hooks/useImperativeHandlerDebugger.js
ReactNativeClient/lib/hooks/usePrevious.js ReactNativeClient/lib/hooks/usePrevious.js
ReactNativeClient/lib/hooks/usePropsDebugger.js ReactNativeClient/lib/hooks/usePropsDebugger.js
ReactNativeClient/lib/InMemoryCache.js
ReactNativeClient/lib/joplin-renderer/MdToHtml/rules/checkbox.js ReactNativeClient/lib/joplin-renderer/MdToHtml/rules/checkbox.js
ReactNativeClient/lib/joplin-renderer/MdToHtml/rules/fence.js ReactNativeClient/lib/joplin-renderer/MdToHtml/rules/fence.js
ReactNativeClient/lib/joplin-renderer/MdToHtml/rules/mermaid.js ReactNativeClient/lib/joplin-renderer/MdToHtml/rules/mermaid.js

View File

@ -4199,11 +4199,6 @@
"resolved": "https://registry.npmjs.org/mdurl/-/mdurl-1.0.1.tgz", "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-1.0.1.tgz",
"integrity": "sha1-/oWy7HWlkDfyrf7BAP1sYBdhFS4=" "integrity": "sha1-/oWy7HWlkDfyrf7BAP1sYBdhFS4="
}, },
"memory-cache": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/memory-cache/-/memory-cache-0.2.0.tgz",
"integrity": "sha1-eJCwHVLADI68nVM+H46xfjA0hxo="
},
"micromatch": { "micromatch": {
"version": "3.1.10", "version": "3.1.10",
"resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz",

View File

@ -80,7 +80,6 @@
"markdown-it-toc-done-right": "^4.1.0", "markdown-it-toc-done-right": "^4.1.0",
"md5": "^2.2.1", "md5": "^2.2.1",
"md5-file": "^4.0.0", "md5-file": "^4.0.0",
"memory-cache": "^0.2.0",
"mime": "^2.0.3", "mime": "^2.0.3",
"moment": "^2.24.0", "moment": "^2.24.0",
"multiparty": "^4.2.1", "multiparty": "^4.2.1",

View File

@ -0,0 +1,59 @@
import InMemoryCache from 'lib/InMemoryCache';
const { time } = require('lib/time-utils.js');
describe('InMemoryCache', function() {
it('should get and set values', () => {
const cache = new InMemoryCache();
expect(cache.value('test')).toBe(undefined);
expect(cache.value('test', 'default')).toBe('default');
cache.setValue('test', 'something');
expect(cache.value('test')).toBe('something');
// Check we get the exact same object back (cache should not copy)
const someObj = { abcd: '123' };
cache.setValue('someObj', someObj);
expect(cache.value('someObj')).toBe(someObj);
});
it('should expire values', async () => {
const cache = new InMemoryCache();
// Check that the value is udefined once the cache has expired
cache.setValue('test', 'something', 500);
expect(cache.value('test')).toBe('something');
await time.msleep(510);
expect(cache.value('test')).toBe(undefined);
// Check that the TTL is reset every time setValue is called
cache.setValue('test', 'something', 300);
await time.msleep(100);
cache.setValue('test', 'something', 300);
await time.msleep(100);
cache.setValue('test', 'something', 300);
await time.msleep(100);
cache.setValue('test', 'something', 300);
await time.msleep(100);
expect(cache.value('test')).toBe('something');
});
it('should delete old records', async () => {
const cache = new InMemoryCache(5);
cache.setValue('1', '1');
cache.setValue('2', '2');
cache.setValue('3', '3');
cache.setValue('4', '4');
cache.setValue('5', '5');
expect(cache.value('1')).toBe('1');
cache.setValue('6', '6');
expect(cache.value('1')).toBe(undefined);
});
});

View File

@ -8072,11 +8072,6 @@
"resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-5.0.4.tgz", "resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-5.0.4.tgz",
"integrity": "sha512-P0z5IeAH6qHHGkJIXWw0xC2HNEgkx/9uWWBQw64FJj3/ol14VYdfVGWWr0fXfjhhv3TKVIqUq65os6O4GUNksA==" "integrity": "sha512-P0z5IeAH6qHHGkJIXWw0xC2HNEgkx/9uWWBQw64FJj3/ol14VYdfVGWWr0fXfjhhv3TKVIqUq65os6O4GUNksA=="
}, },
"memory-cache": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/memory-cache/-/memory-cache-0.2.0.tgz",
"integrity": "sha1-eJCwHVLADI68nVM+H46xfjA0hxo="
},
"mermaid": { "mermaid": {
"version": "8.8.1", "version": "8.8.1",
"resolved": "https://registry.npmjs.org/mermaid/-/mermaid-8.8.1.tgz", "resolved": "https://registry.npmjs.org/mermaid/-/mermaid-8.8.1.tgz",

View File

@ -169,7 +169,6 @@
"markdown-it-toc-done-right": "^4.1.0", "markdown-it-toc-done-right": "^4.1.0",
"md5": "^2.2.1", "md5": "^2.2.1",
"md5-file": "^4.0.0", "md5-file": "^4.0.0",
"memory-cache": "^0.2.0",
"mermaid": "^8.8.1", "mermaid": "^8.8.1",
"moment": "^2.22.2", "moment": "^2.22.2",
"multiparty": "^4.2.1", "multiparty": "^4.2.1",

View File

@ -0,0 +1,84 @@
// There are plenty of packages for in-memory caching but each have their
// own gotchas and often have extra complexity which makes it work in one
// platform but not in another (for example, the use of long timeouts would
// be fine in Node but not in React Native).
//
// So this class implements a simple in-memory cache with support for TTL.
// Checking for expired keys is a bit inefficient since it doesn't rely on
// timers, so it's checking every time a value is set or retrieved. But it
// should be fine in most cases, as long as the class is not used at a massive
// scale.
interface Record {
value: any,
expiredTime: number,
}
interface Records {
[key:string]: Record;
}
interface ExpirableKeys {
[key:string]: boolean,
}
export default class Cache {
private maxRecords_:number;
private records_:Records = {};
private expirableKeys_:ExpirableKeys = {};
private recordKeyHistory_:string[] = [];
constructor(maxRecords:number = 50) {
this.maxRecords_ = maxRecords;
}
private checkExpiredRecords() {
const now = Date.now();
for (let key in this.expirableKeys_) {
if (!this.records_[key]) {
delete this.expirableKeys_[key];
} else {
if (this.records_[key].expiredTime <= now) {
delete this.records_[key];
delete this.expirableKeys_[key];
}
}
}
while (this.recordKeyHistory_.length > this.maxRecords_) {
const key = this.recordKeyHistory_[0];
delete this.records_[key];
delete this.expirableKeys_[key];
this.recordKeyHistory_.splice(0, 1);
}
}
public value(key:string, defaultValue:any = undefined):any {
this.checkExpiredRecords();
if (key in this.records_) return this.records_[key].value;
return defaultValue;
}
public setValue(key:string, value:any, ttl:number = 0) {
this.checkExpiredRecords();
this.records_[key] = {
value: value,
expiredTime: ttl ? Date.now() + ttl : 0,
}
const idx = this.recordKeyHistory_.indexOf(key);
if (idx >= 0) this.recordKeyHistory_.splice(idx, 1);
this.recordKeyHistory_.push(key);
if (ttl) {
this.expirableKeys_[key] = true;
} else {
delete this.expirableKeys_[key];
}
}
}

View File

@ -1,4 +1,4 @@
import { RNCamera } from 'react-native-camera'; const { RNCamera } = require('react-native-camera');
const React = require('react'); const React = require('react');
const Component = React.Component; const Component = React.Component;
const { connect } = require('react-redux'); const { connect } = require('react-redux');
@ -178,7 +178,7 @@ class CameraView extends Component {
<View style={{ position: 'absolute', backgroundColor: '#000000', width: '100%', height: '100%' }}/> <View style={{ position: 'absolute', backgroundColor: '#000000', width: '100%', height: '100%' }}/>
<RNCamera <RNCamera
style={Object.assign({ position: 'absolute' }, cameraRect)} style={Object.assign({ position: 'absolute' }, cameraRect)}
ref={ref => { ref={(ref:any) => {
this.camera = ref; this.camera = ref;
}} }}
type={this.props.cameraType} type={this.props.cameraType}

View File

@ -3,15 +3,20 @@ const utils = require('./utils');
const noteStyle = require('./noteStyle'); const noteStyle = require('./noteStyle');
const Setting = require('lib/models/Setting').default; const Setting = require('lib/models/Setting').default;
const { themeStyle } = require('lib/theme'); const { themeStyle } = require('lib/theme');
const memoryCache = require('memory-cache'); const InMemoryCache = require('lib/InMemoryCache').default;
const md5 = require('md5'); const md5 = require('md5');
// Renderered notes can potentially be quite large (for example
// when they come from the clipper) so keep the cache size
// relatively small.
const inMemoryCache = new InMemoryCache(10);
class HtmlToHtml { class HtmlToHtml {
constructor(options) { constructor(options) {
if (!options) options = {}; if (!options) options = {};
this.resourceBaseUrl_ = 'resourceBaseUrl' in options ? options.resourceBaseUrl : null; this.resourceBaseUrl_ = 'resourceBaseUrl' in options ? options.resourceBaseUrl : null;
this.ResourceModel_ = options.ResourceModel; this.ResourceModel_ = options.ResourceModel;
this.cache_ = new memoryCache.Cache(); this.cache_ = inMemoryCache;
this.fsDriver_ = { this.fsDriver_ = {
writeFile: (/* path, content, encoding = 'base64'*/) => { throw new Error('writeFile not set'); }, writeFile: (/* path, content, encoding = 'base64'*/) => { throw new Error('writeFile not set'); },
exists: (/* path*/) => { throw new Error('exists not set'); }, exists: (/* path*/) => { throw new Error('exists not set'); },
@ -55,7 +60,7 @@ class HtmlToHtml {
}, options); }, options);
const cacheKey = md5(escape(markup)); const cacheKey = md5(escape(markup));
let html = this.cache_.get(cacheKey); let html = this.cache_.value(cacheKey);
if (!html) { if (!html) {
html = htmlUtils.sanitizeHtml(markup); html = htmlUtils.sanitizeHtml(markup);
@ -80,7 +85,7 @@ class HtmlToHtml {
}); });
} }
this.cache_.put(cacheKey, html, 1000 * 60 * 10); this.cache_.setValue(cacheKey, html, 1000 * 60 * 10);
if (options.bodyOnly) { if (options.bodyOnly) {
return { return {

View File

@ -2,7 +2,7 @@ const MarkdownIt = require('markdown-it');
const md5 = require('md5'); const md5 = require('md5');
const noteStyle = require('./noteStyle'); const noteStyle = require('./noteStyle');
const { fileExtension } = require('./pathUtils'); const { fileExtension } = require('./pathUtils');
const memoryCache = require('memory-cache'); const InMemoryCache = require('lib/InMemoryCache').default;
// /!\/!\ Note: the order of rules is important!! /!\/!\ // /!\/!\ Note: the order of rules is important!! /!\/!\
const rules = { const rules = {
@ -44,6 +44,9 @@ function slugify(s) {
return nodeSlug(s); return nodeSlug(s);
} }
// Share across all instances of MdToHtml
const inMemoryCache = new InMemoryCache(20);
class MdToHtml { class MdToHtml {
constructor(options = null) { constructor(options = null) {
if (!options) options = {}; if (!options) options = {};
@ -57,7 +60,7 @@ class MdToHtml {
this.cachedHighlightedCode_ = {}; this.cachedHighlightedCode_ = {};
this.ResourceModel_ = options.ResourceModel; this.ResourceModel_ = options.ResourceModel;
this.pluginOptions_ = options.pluginOptions ? options.pluginOptions : {}; this.pluginOptions_ = options.pluginOptions ? options.pluginOptions : {};
this.contextCache_ = new memoryCache.Cache(); this.contextCache_ = inMemoryCache;
this.tempDir_ = options.tempDir; this.tempDir_ = options.tempDir;
this.fsDriver_ = { this.fsDriver_ = {

View File

@ -16,7 +16,7 @@ function installRule(markdownIt:any, mdOptions:any, ruleOptions:any, context:any
} }
const cacheKey = md5(escape(token.content)); const cacheKey = md5(escape(token.content));
let sanitizedContent = context.cache.get(cacheKey); let sanitizedContent = context.cache.value(cacheKey);
// For html_inline, the content is only a fragment of HTML, as it will be rendered, but // For html_inline, the content is only a fragment of HTML, as it will be rendered, but
// it's not necessarily valid HTML. For example this HTML: // it's not necessarily valid HTML. For example this HTML:
@ -37,7 +37,7 @@ function installRule(markdownIt:any, mdOptions:any, ruleOptions:any, context:any
token.content = sanitizedContent; token.content = sanitizedContent;
context.cache.put(cacheKey, sanitizedContent, 1000 * 60 * 60); context.cache.setValue(cacheKey, sanitizedContent, 1000 * 60 * 60);
walkHtmlTokens(token.children); walkHtmlTokens(token.children);
} }
}; };

View File

@ -855,11 +855,6 @@
"resolved": "https://registry.npmjs.org/mdurl/-/mdurl-1.0.1.tgz", "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-1.0.1.tgz",
"integrity": "sha1-/oWy7HWlkDfyrf7BAP1sYBdhFS4=" "integrity": "sha1-/oWy7HWlkDfyrf7BAP1sYBdhFS4="
}, },
"memory-cache": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/memory-cache/-/memory-cache-0.2.0.tgz",
"integrity": "sha1-eJCwHVLADI68nVM+H46xfjA0hxo="
},
"mermaid": { "mermaid": {
"version": "8.8.1", "version": "8.8.1",
"resolved": "https://registry.npmjs.org/mermaid/-/mermaid-8.8.1.tgz", "resolved": "https://registry.npmjs.org/mermaid/-/mermaid-8.8.1.tgz",
@ -972,9 +967,9 @@
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
}, },
"slug": { "slug": {
"version": "3.3.5", "version": "3.5.1",
"resolved": "https://registry.npmjs.org/slug/-/slug-3.3.5.tgz", "resolved": "https://registry.npmjs.org/slug/-/slug-3.5.1.tgz",
"integrity": "sha512-d/9yTbJDtSIhJThaNRP/U5uxwCl0mWIlV42JmKSfvg8t7DiVt69G8rAWTc0FWhaQOier0fiNAWVs7ctvVhK1RA==" "integrity": "sha512-ei0JnJzg8HKhLunZy+vpNlILRRradfaAQ+p2YEI4b4r8yX/5TlFi1JSwcYQCg7INZxdTC43BT68rHMkRxzn7Xg=="
}, },
"source-map": { "source-map": {
"version": "0.5.7", "version": "0.5.7",

View File

@ -36,7 +36,6 @@
"markdown-it-sup": "^1.0.0", "markdown-it-sup": "^1.0.0",
"markdown-it-toc-done-right": "^4.1.0", "markdown-it-toc-done-right": "^4.1.0",
"md5": "^2.2.1", "md5": "^2.2.1",
"memory-cache": "^0.2.0",
"mermaid": "^8.8.1", "mermaid": "^8.8.1",
"slug": "^3.5.0" "slug": "^3.5.0"
} }

View File

@ -7930,11 +7930,6 @@
"resolved": "https://registry.npmjs.org/mdurl/-/mdurl-1.0.1.tgz", "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-1.0.1.tgz",
"integrity": "sha1-/oWy7HWlkDfyrf7BAP1sYBdhFS4=" "integrity": "sha1-/oWy7HWlkDfyrf7BAP1sYBdhFS4="
}, },
"memory-cache": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/memory-cache/-/memory-cache-0.2.0.tgz",
"integrity": "sha1-eJCwHVLADI68nVM+H46xfjA0hxo="
},
"merge-stream": { "merge-stream": {
"version": "2.0.0", "version": "2.0.0",
"resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz",

View File

@ -46,7 +46,6 @@
"markdown-it-sup": "^1.0.0", "markdown-it-sup": "^1.0.0",
"markdown-it-toc-done-right": "^4.1.0", "markdown-it-toc-done-right": "^4.1.0",
"md5": "^2.2.1", "md5": "^2.2.1",
"memory-cache": "^0.2.0",
"mermaid": "^8.8.1", "mermaid": "^8.8.1",
"moment": "^2.24.0", "moment": "^2.24.0",
"nanoid": "^3.1.12", "nanoid": "^3.1.12",

View File

@ -11,11 +11,10 @@ module.exports = {
'**/.git/**', '**/.git/**',
'**/ElectronClient/lib/**', '**/ElectronClient/lib/**',
'**/CliClient/build/**', '**/CliClient/build/**',
'**/CliClient/tests-build/lib/**', '**/CliClient/tests-build/**',
'**/ElectronClient/dist/**', '**/ElectronClient/dist/**',
'**/Modules/TinyMCE/JoplinLists/**', '**/Modules/TinyMCE/JoplinLists/**',
'**/Modules/TinyMCE/IconPack/**', '**/Modules/TinyMCE/IconPack/**',
'**/CliClient/tests-build/support/**',
'**/CliClient/tests/support/plugins/**', '**/CliClient/tests/support/plugins/**',
'**/plugin_types/**', '**/plugin_types/**',
], ],

View File

@ -704,7 +704,9 @@
"ReactNativeClient/lib/components/SelectDateTimeDialog.js": true, "ReactNativeClient/lib/components/SelectDateTimeDialog.js": true,
"ReactNativeClient/lib/PoorManIntervals.js": true, "ReactNativeClient/lib/PoorManIntervals.js": true,
"ReactNativeClient/lib/components/CameraView.js": true, "ReactNativeClient/lib/components/CameraView.js": true,
"ReactNativeClient/lib/components/NoteBodyViewer.js": true "ReactNativeClient/lib/components/NoteBodyViewer.js": true,
"CliClient/tests/InMemoryCache.js": true,
"ReactNativeClient/lib/InMemoryCache.js": true
}, },
"spellright.language": [ "spellright.language": [
"en" "en"