1
0
mirror of https://github.com/laurent22/joplin.git synced 2025-01-11 18:24:43 +02:00

Desktop: Fixes #3729: Fix lock issue when device does not have the right time

This commit is contained in:
Laurent Cozic 2020-09-08 23:57:48 +01:00
parent 3a33e5f416
commit 1f70a76c7e
5 changed files with 203 additions and 4 deletions

View File

@ -62,6 +62,7 @@ Modules/TinyMCE/langs/
# AUTO-GENERATED - EXCLUDED TYPESCRIPT BUILD
CliClient/app/LinkSelector.js
CliClient/build/LinkSelector.js
CliClient/tests/synchronizer_LockHandler.js
CliClient/tests/synchronizer_MigrationHandler.js
ElectronClient/commands/focusElement.js
@ -151,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/sanitize_html.js
ReactNativeClient/lib/JoplinServerApi.js
ReactNativeClient/lib/ntpDate.js
ReactNativeClient/lib/services/CommandService.js
ReactNativeClient/lib/services/keychain/KeychainService.js
ReactNativeClient/lib/services/keychain/KeychainServiceDriver.dummy.js

2
.gitignore vendored
View File

@ -53,6 +53,7 @@ Tools/commit_hook.txt
# AUTO-GENERATED - EXCLUDED TYPESCRIPT BUILD
CliClient/app/LinkSelector.js
CliClient/build/LinkSelector.js
CliClient/tests/synchronizer_LockHandler.js
CliClient/tests/synchronizer_MigrationHandler.js
ElectronClient/commands/focusElement.js
@ -142,6 +143,7 @@ ReactNativeClient/lib/joplin-renderer/MdToHtml/rules/fence.js
ReactNativeClient/lib/joplin-renderer/MdToHtml/rules/mermaid.js
ReactNativeClient/lib/joplin-renderer/MdToHtml/rules/sanitize_html.js
ReactNativeClient/lib/JoplinServerApi.js
ReactNativeClient/lib/ntpDate.js
ReactNativeClient/lib/services/CommandService.js
ReactNativeClient/lib/services/keychain/KeychainService.js
ReactNativeClient/lib/services/keychain/KeychainServiceDriver.dummy.js

View File

@ -0,0 +1,46 @@
const ntpClient = require('lib/vendor/ntp-client');
const Mutex = require('async-mutex').Mutex;
let lastSyncTime = 0;
let timeOffset = 0;
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 !lastSyncTime || Date.now() - lastSyncTime >= 5 * 1000;
}
export default async function():Promise<Date> {
if (shouldSyncTime()) {
const release = await fetchingTimeMutex.acquire();
try {
if (shouldSyncTime()) {
const date = await networkTime();
lastSyncTime = Date.now();
timeOffset = date.getTime() - Date.now();
}
} finally {
release();
}
}
return new Date(Date.now() + timeOffset);
}

View File

@ -1,4 +1,6 @@
import { Dirnames } from './utils/types';
import ntpDate from 'lib/ntpDate';
const JoplinError = require('lib/JoplinError');
const { time } = require('lib/time-utils');
const { fileExtension, filename } = require('lib/path-utils.js');
@ -98,8 +100,8 @@ export default class LockHandler {
return output;
}
private lockIsActive(lock:Lock):boolean {
return Date.now() - lock.updatedTime < this.lockTtl;
private lockIsActive(lock:Lock, currentDate:Date):boolean {
return currentDate.getTime() - lock.updatedTime < this.lockTtl;
}
async hasActiveLock(lockType:LockType, clientType:string = null, clientId:string = null) {
@ -112,11 +114,12 @@ export default class LockHandler {
// of that type instead.
async activeLock(lockType:LockType, clientType:string = null, clientId:string = null) {
const locks = await this.locks(lockType);
const currentDate = await ntpDate();
if (lockType === LockType.Exclusive) {
const activeLocks = locks
.slice()
.filter((lock:Lock) => this.lockIsActive(lock))
.filter((lock:Lock) => this.lockIsActive(lock, currentDate))
.sort((a:Lock, b:Lock) => {
if (a.updatedTime === b.updatedTime) {
return a.clientId < b.clientId ? -1 : +1;
@ -134,7 +137,7 @@ export default class LockHandler {
for (const lock of locks) {
if (clientType && lock.clientType !== clientType) continue;
if (clientId && lock.clientId !== clientId) continue;
if (this.lockIsActive(lock)) return lock;
if (this.lockIsActive(lock, currentDate)) return lock;
}
return null;
}

View File

@ -0,0 +1,146 @@
/*
* 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
// ----------------------------------------------------------------------------------------
(function (exports) {
"use strict";
var dgram = require('dgram');
exports.defaultNtpPort = 123;
exports.defaultNtpServer = "pool.ntp.org";
/**
* 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;
var client = 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;
});
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));