mirror of
https://github.com/laurent22/joplin.git
synced 2024-12-24 10:27:10 +02:00
Merge branch 'release-1.0' into dev
This commit is contained in:
commit
5f410e80e6
@ -152,6 +152,7 @@ ReactNativeClient/lib/joplin-renderer/MdToHtml/rules/fence.js
|
|||||||
ReactNativeClient/lib/joplin-renderer/MdToHtml/rules/mermaid.js
|
ReactNativeClient/lib/joplin-renderer/MdToHtml/rules/mermaid.js
|
||||||
ReactNativeClient/lib/joplin-renderer/MdToHtml/rules/sanitize_html.js
|
ReactNativeClient/lib/joplin-renderer/MdToHtml/rules/sanitize_html.js
|
||||||
ReactNativeClient/lib/JoplinServerApi.js
|
ReactNativeClient/lib/JoplinServerApi.js
|
||||||
|
ReactNativeClient/lib/ntpDate.js
|
||||||
ReactNativeClient/lib/services/CommandService.js
|
ReactNativeClient/lib/services/CommandService.js
|
||||||
ReactNativeClient/lib/services/keychain/KeychainService.js
|
ReactNativeClient/lib/services/keychain/KeychainService.js
|
||||||
ReactNativeClient/lib/services/keychain/KeychainServiceDriver.dummy.js
|
ReactNativeClient/lib/services/keychain/KeychainServiceDriver.dummy.js
|
||||||
|
1
.gitignore
vendored
1
.gitignore
vendored
@ -145,6 +145,7 @@ ReactNativeClient/lib/joplin-renderer/MdToHtml/rules/fence.js
|
|||||||
ReactNativeClient/lib/joplin-renderer/MdToHtml/rules/mermaid.js
|
ReactNativeClient/lib/joplin-renderer/MdToHtml/rules/mermaid.js
|
||||||
ReactNativeClient/lib/joplin-renderer/MdToHtml/rules/sanitize_html.js
|
ReactNativeClient/lib/joplin-renderer/MdToHtml/rules/sanitize_html.js
|
||||||
ReactNativeClient/lib/JoplinServerApi.js
|
ReactNativeClient/lib/JoplinServerApi.js
|
||||||
|
ReactNativeClient/lib/ntpDate.js
|
||||||
ReactNativeClient/lib/services/CommandService.js
|
ReactNativeClient/lib/services/CommandService.js
|
||||||
ReactNativeClient/lib/services/keychain/KeychainService.js
|
ReactNativeClient/lib/services/keychain/KeychainService.js
|
||||||
ReactNativeClient/lib/services/keychain/KeychainServiceDriver.dummy.js
|
ReactNativeClient/lib/services/keychain/KeychainServiceDriver.dummy.js
|
||||||
|
2
ElectronClient/package-lock.json
generated
2
ElectronClient/package-lock.json
generated
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "Joplin",
|
"name": "Joplin",
|
||||||
"version": "1.0.242",
|
"version": "1.0.245",
|
||||||
"lockfileVersion": 1,
|
"lockfileVersion": 1,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "Joplin",
|
"name": "Joplin",
|
||||||
"version": "1.0.242",
|
"version": "1.0.245",
|
||||||
"description": "Joplin for Desktop",
|
"description": "Joplin for Desktop",
|
||||||
"main": "main.js",
|
"main": "main.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
@ -44,6 +44,9 @@ const KvStore = require('lib/services/KvStore');
|
|||||||
const MigrationService = require('lib/services/MigrationService');
|
const MigrationService = require('lib/services/MigrationService');
|
||||||
const { toSystemSlashes } = require('lib/path-utils.js');
|
const { toSystemSlashes } = require('lib/path-utils.js');
|
||||||
|
|
||||||
|
const ntpClient = require('lib/vendor/ntp-client');
|
||||||
|
ntpClient.dgram = require('dgram');
|
||||||
|
|
||||||
class BaseApplication {
|
class BaseApplication {
|
||||||
constructor() {
|
constructor() {
|
||||||
this.logger_ = new Logger();
|
this.logger_ = new Logger();
|
||||||
@ -673,6 +676,7 @@ class BaseApplication {
|
|||||||
reg.dispatch = () => {};
|
reg.dispatch = () => {};
|
||||||
|
|
||||||
BaseService.logger_ = this.logger_;
|
BaseService.logger_ = this.logger_;
|
||||||
|
require('lib/ntpDate').setLogger(reg.logger());
|
||||||
|
|
||||||
this.dbLogger_.addTarget('file', { path: `${profileDir}/log-database.txt` });
|
this.dbLogger_.addTarget('file', { path: `${profileDir}/log-database.txt` });
|
||||||
this.dbLogger_.setLevel(initArgs.logLevel);
|
this.dbLogger_.setLevel(initArgs.logLevel);
|
||||||
|
@ -44,7 +44,7 @@ class FileApiDriverDropbox {
|
|||||||
metadataToStat_(md, path) {
|
metadataToStat_(md, path) {
|
||||||
const output = {
|
const output = {
|
||||||
path: path,
|
path: path,
|
||||||
updated_time: md.server_modified ? new Date(md.server_modified) : new Date(),
|
updated_time: md.server_modified ? (new Date(md.server_modified)).getTime() : Date.now(),
|
||||||
isDir: md['.tag'] === 'folder',
|
isDir: md['.tag'] === 'folder',
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -6,6 +6,7 @@ const JoplinError = require('lib/JoplinError');
|
|||||||
const ArrayUtils = require('lib/ArrayUtils');
|
const ArrayUtils = require('lib/ArrayUtils');
|
||||||
const { time } = require('lib/time-utils.js');
|
const { time } = require('lib/time-utils.js');
|
||||||
const { sprintf } = require('sprintf-js');
|
const { sprintf } = require('sprintf-js');
|
||||||
|
const Mutex = require('async-mutex').Mutex;
|
||||||
|
|
||||||
function requestCanBeRepeated(error) {
|
function requestCanBeRepeated(error) {
|
||||||
const errorCode = typeof error === 'object' && error.code ? error.code : null;
|
const errorCode = typeof error === 'object' && error.code ? error.code : null;
|
||||||
@ -57,6 +58,65 @@ class FileApi {
|
|||||||
this.tempDirName_ = null;
|
this.tempDirName_ = null;
|
||||||
this.driver_.fileApi_ = this;
|
this.driver_.fileApi_ = this;
|
||||||
this.requestRepeatCount_ = null; // For testing purpose only - normally this value should come from the driver
|
this.requestRepeatCount_ = null; // For testing purpose only - normally this value should come from the driver
|
||||||
|
this.remoteDateOffset_ = 0;
|
||||||
|
this.remoteDateNextCheckTime_ = 0;
|
||||||
|
this.remoteDateMutex_ = new Mutex();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
async fetchRemoteDateOffset_() {
|
||||||
|
const tempFile = `${this.tempDirName()}/timeCheck${Math.round(Math.random() * 1000000)}.txt`;
|
||||||
|
const startTime = Date.now();
|
||||||
|
await this.put(tempFile, 'timeCheck');
|
||||||
|
|
||||||
|
// Normally it should be possible to read the file back immediately but
|
||||||
|
// just in case, read it in a loop.
|
||||||
|
const loopStartTime = Date.now();
|
||||||
|
let stat = null;
|
||||||
|
while (Date.now() - loopStartTime < 5000) {
|
||||||
|
stat = await this.stat(tempFile);
|
||||||
|
if (stat) break;
|
||||||
|
await time.msleep(200);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!stat) throw new Error('Timed out trying to get sync target clock time');
|
||||||
|
|
||||||
|
this.delete(tempFile); // No need to await for this call
|
||||||
|
|
||||||
|
const endTime = Date.now();
|
||||||
|
const expectedTime = Math.round((endTime + startTime) / 2);
|
||||||
|
return stat.updated_time - expectedTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Approximates the current time on the sync target. It caches the time offset to
|
||||||
|
// improve performance.
|
||||||
|
async remoteDate() {
|
||||||
|
const shouldSyncTime = () => {
|
||||||
|
return !this.remoteDateNextCheckTime_ || Date.now() > this.remoteDateNextCheckTime_;
|
||||||
|
};
|
||||||
|
|
||||||
|
if (shouldSyncTime()) {
|
||||||
|
const release = await this.remoteDateMutex_.acquire();
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Another call might have refreshed the time while we were waiting for the mutex,
|
||||||
|
// so check again if we need to refresh.
|
||||||
|
if (shouldSyncTime()) {
|
||||||
|
this.remoteDateOffset_ = await this.fetchRemoteDateOffset_();
|
||||||
|
// The sync target clock should rarely change but the device one might,
|
||||||
|
// so we need to refresh relatively frequently.
|
||||||
|
this.remoteDateNextCheckTime_ = Date.now() + 10 * 60 * 1000;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
this.logger().warn('Could not retrieve remote date - defaulting to device date:', error);
|
||||||
|
this.remoteDateOffset_ = 0;
|
||||||
|
this.remoteDateNextCheckTime_ = Date.now() + 60 * 1000;
|
||||||
|
} finally {
|
||||||
|
release();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Date(Date.now() + this.remoteDateOffset_);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ideally all requests repeating should be done at the FileApi level to remove duplicate code in the drivers, but
|
// Ideally all requests repeating should be done at the FileApi level to remove duplicate code in the drivers, but
|
||||||
|
59
ReactNativeClient/lib/ntpDate.ts
Normal file
59
ReactNativeClient/lib/ntpDate.ts
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
const ntpClient = require('lib/vendor/ntp-client');
|
||||||
|
const { Logger } = require('lib/logger.js');
|
||||||
|
const Mutex = require('async-mutex').Mutex;
|
||||||
|
|
||||||
|
let nextSyncTime = 0;
|
||||||
|
let timeOffset = 0;
|
||||||
|
let logger = new Logger();
|
||||||
|
|
||||||
|
const fetchingTimeMutex = new Mutex();
|
||||||
|
|
||||||
|
const server = {
|
||||||
|
domain: 'pool.ntp.org',
|
||||||
|
port: 123,
|
||||||
|
};
|
||||||
|
|
||||||
|
async function networkTime():Promise<Date> {
|
||||||
|
return new Promise(function(resolve:Function, reject:Function) {
|
||||||
|
ntpClient.getNetworkTime(server.domain, server.port, function(error:any, date:Date) {
|
||||||
|
if (error) {
|
||||||
|
reject(error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
resolve(date);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function shouldSyncTime() {
|
||||||
|
return !nextSyncTime || Date.now() > nextSyncTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function setLogger(v:any) {
|
||||||
|
logger = v;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default async function():Promise<Date> {
|
||||||
|
if (shouldSyncTime()) {
|
||||||
|
const release = await fetchingTimeMutex.acquire();
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (shouldSyncTime()) {
|
||||||
|
const date = await networkTime();
|
||||||
|
nextSyncTime = Date.now() + 60 * 1000;
|
||||||
|
timeOffset = date.getTime() - Date.now();
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
logger.warn('Could not get NTP time - falling back to device time:', error);
|
||||||
|
// Fallback to device time since
|
||||||
|
// most of the time it's actually correct
|
||||||
|
nextSyncTime = Date.now() + 20 * 1000;
|
||||||
|
timeOffset = 0;
|
||||||
|
} finally {
|
||||||
|
release();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Date(Date.now() + timeOffset);
|
||||||
|
}
|
@ -1,4 +1,5 @@
|
|||||||
import { Dirnames } from './utils/types';
|
import { Dirnames } from './utils/types';
|
||||||
|
|
||||||
const JoplinError = require('lib/JoplinError');
|
const JoplinError = require('lib/JoplinError');
|
||||||
const { time } = require('lib/time-utils');
|
const { time } = require('lib/time-utils');
|
||||||
const { fileExtension, filename } = require('lib/path-utils.js');
|
const { fileExtension, filename } = require('lib/path-utils.js');
|
||||||
@ -98,8 +99,8 @@ export default class LockHandler {
|
|||||||
return output;
|
return output;
|
||||||
}
|
}
|
||||||
|
|
||||||
private lockIsActive(lock:Lock):boolean {
|
private lockIsActive(lock:Lock, currentDate:Date):boolean {
|
||||||
return Date.now() - lock.updatedTime < this.lockTtl;
|
return currentDate.getTime() - lock.updatedTime < this.lockTtl;
|
||||||
}
|
}
|
||||||
|
|
||||||
async hasActiveLock(lockType:LockType, clientType:string = null, clientId:string = null) {
|
async hasActiveLock(lockType:LockType, clientType:string = null, clientId:string = null) {
|
||||||
@ -112,11 +113,12 @@ export default class LockHandler {
|
|||||||
// of that type instead.
|
// of that type instead.
|
||||||
async activeLock(lockType:LockType, clientType:string = null, clientId:string = null) {
|
async activeLock(lockType:LockType, clientType:string = null, clientId:string = null) {
|
||||||
const locks = await this.locks(lockType);
|
const locks = await this.locks(lockType);
|
||||||
|
const currentDate = await this.api_.remoteDate();
|
||||||
|
|
||||||
if (lockType === LockType.Exclusive) {
|
if (lockType === LockType.Exclusive) {
|
||||||
const activeLocks = locks
|
const activeLocks = locks
|
||||||
.slice()
|
.slice()
|
||||||
.filter((lock:Lock) => this.lockIsActive(lock))
|
.filter((lock:Lock) => this.lockIsActive(lock, currentDate))
|
||||||
.sort((a:Lock, b:Lock) => {
|
.sort((a:Lock, b:Lock) => {
|
||||||
if (a.updatedTime === b.updatedTime) {
|
if (a.updatedTime === b.updatedTime) {
|
||||||
return a.clientId < b.clientId ? -1 : +1;
|
return a.clientId < b.clientId ? -1 : +1;
|
||||||
@ -134,7 +136,7 @@ export default class LockHandler {
|
|||||||
for (const lock of locks) {
|
for (const lock of locks) {
|
||||||
if (clientType && lock.clientType !== clientType) continue;
|
if (clientType && lock.clientType !== clientType) continue;
|
||||||
if (clientId && lock.clientId !== clientId) continue;
|
if (clientId && lock.clientId !== clientId) continue;
|
||||||
if (this.lockIsActive(lock)) return lock;
|
if (this.lockIsActive(lock, currentDate)) return lock;
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
155
ReactNativeClient/lib/vendor/ntp-client.js
vendored
Normal file
155
ReactNativeClient/lib/vendor/ntp-client.js
vendored
Normal file
@ -0,0 +1,155 @@
|
|||||||
|
/*
|
||||||
|
* ntp-client
|
||||||
|
* https://github.com/moonpyk/node-ntp-client
|
||||||
|
*
|
||||||
|
* Copyright (c) 2013 Clément Bourgeois
|
||||||
|
* Licensed under the MIT license.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------------------------
|
||||||
|
// 2020-08-09: We vendor the package because although it works
|
||||||
|
// it has several bugs and is currently unmaintained
|
||||||
|
// ----------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
const Buffer = require('buffer').Buffer;
|
||||||
|
|
||||||
|
(function (exports) {
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
exports.defaultNtpPort = 123;
|
||||||
|
exports.defaultNtpServer = "pool.ntp.org";
|
||||||
|
|
||||||
|
exports.dgram = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Amount of acceptable time to await for a response from the remote server.
|
||||||
|
* Configured default to 10 seconds.
|
||||||
|
*/
|
||||||
|
exports.ntpReplyTimeout = 10000;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetches the current NTP Time from the given server and port.
|
||||||
|
* @param {string} server IP/Hostname of the remote NTP Server
|
||||||
|
* @param {number} port Remote NTP Server port number
|
||||||
|
* @param {function(Object, Date)} callback(err, date) Async callback for
|
||||||
|
* the result date or eventually error.
|
||||||
|
*/
|
||||||
|
exports.getNetworkTime = function (server, port, callback) {
|
||||||
|
if (callback === null || typeof callback !== "function") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
server = server || exports.defaultNtpServer;
|
||||||
|
port = port || exports.defaultNtpPort;
|
||||||
|
|
||||||
|
if (!exports.dgram) throw new Error('dgram package has not been set!!');
|
||||||
|
|
||||||
|
var client = exports.dgram.createSocket("udp4"),
|
||||||
|
ntpData = new Buffer(48);
|
||||||
|
|
||||||
|
// RFC 2030 -> LI = 0 (no warning, 2 bits), VN = 3 (IPv4 only, 3 bits), Mode = 3 (Client Mode, 3 bits) -> 1 byte
|
||||||
|
// -> rtol(LI, 6) ^ rotl(VN, 3) ^ rotl(Mode, 0)
|
||||||
|
// -> = 0x00 ^ 0x18 ^ 0x03
|
||||||
|
ntpData[0] = 0x1B;
|
||||||
|
|
||||||
|
for (var i = 1; i < 48; i++) {
|
||||||
|
ntpData[i] = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Some errors can happen before/after send() or cause send() to was impossible.
|
||||||
|
// Some errors will also be given to the send() callback.
|
||||||
|
// We keep a flag, therefore, to prevent multiple callbacks.
|
||||||
|
// NOTE : the error callback is not generalised, as the client has to lose the connection also, apparently.
|
||||||
|
var errorFired = false;
|
||||||
|
|
||||||
|
function closeClient(client) {
|
||||||
|
try {
|
||||||
|
client.close();
|
||||||
|
} catch (error) {
|
||||||
|
// Doesn't mater if it could not be closed
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var timeout = setTimeout(function () {
|
||||||
|
closeClient(client);
|
||||||
|
|
||||||
|
if (errorFired) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
callback(new Error("Timeout waiting for NTP response."), null);
|
||||||
|
errorFired = true;
|
||||||
|
}, exports.ntpReplyTimeout);
|
||||||
|
|
||||||
|
client.on('error', function (err) {
|
||||||
|
clearTimeout(timeout);
|
||||||
|
|
||||||
|
if (errorFired) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
callback(err, null);
|
||||||
|
errorFired = true;
|
||||||
|
});
|
||||||
|
|
||||||
|
// NOTE: To make it work in React Native (Android), a port need to be bound
|
||||||
|
// before calling client.send()
|
||||||
|
|
||||||
|
// client.bind(5555, '0.0.0.0', function() {
|
||||||
|
client.send(ntpData, 0, ntpData.length, port, server, function (err) {
|
||||||
|
if (err) {
|
||||||
|
clearTimeout(timeout);
|
||||||
|
if (errorFired) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
callback(err, null);
|
||||||
|
errorFired = true;
|
||||||
|
closeClient(client);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
client.once('message', function (msg) {
|
||||||
|
clearTimeout(timeout);
|
||||||
|
closeClient(client);
|
||||||
|
|
||||||
|
// Offset to get to the "Transmit Timestamp" field (time at which the reply
|
||||||
|
// departed the server for the client, in 64-bit timestamp format."
|
||||||
|
var offsetTransmitTime = 40,
|
||||||
|
intpart = 0,
|
||||||
|
fractpart = 0;
|
||||||
|
|
||||||
|
// Get the seconds part
|
||||||
|
for (var i = 0; i <= 3; i++) {
|
||||||
|
intpart = 256 * intpart + msg[offsetTransmitTime + i];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the seconds fraction
|
||||||
|
for (i = 4; i <= 7; i++) {
|
||||||
|
fractpart = 256 * fractpart + msg[offsetTransmitTime + i];
|
||||||
|
}
|
||||||
|
|
||||||
|
var milliseconds = (intpart * 1000 + (fractpart * 1000) / 0x100000000);
|
||||||
|
|
||||||
|
// **UTC** time
|
||||||
|
var date = new Date("Jan 01 1900 GMT");
|
||||||
|
date.setUTCMilliseconds(date.getUTCMilliseconds() + milliseconds);
|
||||||
|
|
||||||
|
callback(null, date);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
// });
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.demo = function (argv) {
|
||||||
|
exports.getNetworkTime(
|
||||||
|
exports.defaultNtpServer,
|
||||||
|
exports.defaultNtpPort,
|
||||||
|
function (err, date) {
|
||||||
|
if (err) {
|
||||||
|
console.error(err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(date);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}(exports));
|
@ -66,6 +66,9 @@ const WelcomeUtils = require('lib/WelcomeUtils');
|
|||||||
const { themeStyle } = require('lib/components/global-style.js');
|
const { themeStyle } = require('lib/components/global-style.js');
|
||||||
const { uuid } = require('lib/uuid.js');
|
const { uuid } = require('lib/uuid.js');
|
||||||
|
|
||||||
|
const ntpClient = require('lib/vendor/ntp-client');
|
||||||
|
ntpClient.dgram = require('react-native-udp');
|
||||||
|
|
||||||
const { loadKeychainServiceAndSettings } = require('lib/services/SettingUtils');
|
const { loadKeychainServiceAndSettings } = require('lib/services/SettingUtils');
|
||||||
const KeychainServiceDriverMobile = require('lib/services/keychain/KeychainServiceDriver.mobile').default;
|
const KeychainServiceDriverMobile = require('lib/services/keychain/KeychainServiceDriver.mobile').default;
|
||||||
|
|
||||||
@ -401,6 +404,7 @@ async function initialize(dispatch, messageHandler) {
|
|||||||
reg.setShowErrorMessageBoxHandler((message) => { alert(message); });
|
reg.setShowErrorMessageBoxHandler((message) => { alert(message); });
|
||||||
|
|
||||||
BaseService.logger_ = mainLogger;
|
BaseService.logger_ = mainLogger;
|
||||||
|
require('lib/ntpDate').setLogger(reg.logger());
|
||||||
|
|
||||||
reg.logger().info('====================================');
|
reg.logger().info('====================================');
|
||||||
reg.logger().info(`Starting application ${Setting.value('appId')} (${Setting.value('env')})`);
|
reg.logger().info(`Starting application ${Setting.value('appId')} (${Setting.value('env')})`);
|
||||||
|
Loading…
Reference in New Issue
Block a user