diff --git a/.eslintignore b/.eslintignore index bfd1ebbe5e..5c059cc0d1 100644 --- a/.eslintignore +++ b/.eslintignore @@ -933,6 +933,9 @@ packages/lib/PoorManIntervals.js.map packages/lib/SyncTargetJoplinServer.d.ts packages/lib/SyncTargetJoplinServer.js packages/lib/SyncTargetJoplinServer.js.map +packages/lib/SyncTargetOneDrive.d.ts +packages/lib/SyncTargetOneDrive.js +packages/lib/SyncTargetOneDrive.js.map packages/lib/Synchronizer.d.ts packages/lib/Synchronizer.js packages/lib/Synchronizer.js.map @@ -996,6 +999,9 @@ packages/lib/models/utils/types.js.map packages/lib/ntpDate.d.ts packages/lib/ntpDate.js packages/lib/ntpDate.js.map +packages/lib/onedrive-api.d.ts +packages/lib/onedrive-api.js +packages/lib/onedrive-api.js.map packages/lib/path-utils.d.ts packages/lib/path-utils.js packages/lib/path-utils.js.map diff --git a/.gitignore b/.gitignore index ce0868ae0e..cc8b8659ae 100644 --- a/.gitignore +++ b/.gitignore @@ -921,6 +921,9 @@ packages/lib/PoorManIntervals.js.map packages/lib/SyncTargetJoplinServer.d.ts packages/lib/SyncTargetJoplinServer.js packages/lib/SyncTargetJoplinServer.js.map +packages/lib/SyncTargetOneDrive.d.ts +packages/lib/SyncTargetOneDrive.js +packages/lib/SyncTargetOneDrive.js.map packages/lib/Synchronizer.d.ts packages/lib/Synchronizer.js packages/lib/Synchronizer.js.map @@ -984,6 +987,9 @@ packages/lib/models/utils/types.js.map packages/lib/ntpDate.d.ts packages/lib/ntpDate.js packages/lib/ntpDate.js.map +packages/lib/onedrive-api.d.ts +packages/lib/onedrive-api.js +packages/lib/onedrive-api.js.map packages/lib/path-utils.d.ts packages/lib/path-utils.js packages/lib/path-utils.js.map diff --git a/README.md b/README.md index a1c2a9ad2c..cf5526f419 100644 --- a/README.md +++ b/README.md @@ -34,7 +34,7 @@ Linux | Get it on Google Play | or download the APK file: [64-bit](https://github.com/laurent22/joplin-android/releases/download/android-v1.4.11/joplin-v1.4.11.apk) [32-bit](https://github.com/laurent22/joplin-android/releases/download/android-v1.4.11/joplin-v1.4.11-32bit.apk) +Android | Get it on Google Play | or download the APK file: [64-bit](https://github.com/laurent22/joplin-android/releases/download/android-v1.6.7/joplin-v1.6.7.apk) [32-bit](https://github.com/laurent22/joplin-android/releases/download/android-v1.6.7/joplin-v1.6.7-32bit.apk) iOS | Get it on the App Store | - ## Terminal application diff --git a/packages/app-cli/package-lock.json b/packages/app-cli/package-lock.json index 3f81aa8361..25aaca7173 100644 --- a/packages/app-cli/package-lock.json +++ b/packages/app-cli/package-lock.json @@ -1,6 +1,6 @@ { "name": "joplin", - "version": "1.6.3", + "version": "1.6.4", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/packages/app-cli/package.json b/packages/app-cli/package.json index b1bf314512..b54d5ba8a8 100644 --- a/packages/app-cli/package.json +++ b/packages/app-cli/package.json @@ -39,8 +39,8 @@ "node": ">=10.0.0" }, "dependencies": { - "@joplin/lib": "*", - "@joplin/renderer": "*", + "@joplin/lib": "1.0.18", + "@joplin/renderer": "1.0.26", "aws-sdk": "^2.588.0", "chalk": "^4.1.0", "clean-html": "^1.5.0", diff --git a/packages/app-cli/tests/test-utils.ts b/packages/app-cli/tests/test-utils.ts index 8c1aa9ceed..4bc16acc98 100644 --- a/packages/app-cli/tests/test-utils.ts +++ b/packages/app-cli/tests/test-utils.ts @@ -16,6 +16,8 @@ import KeychainServiceDriverDummy from '@joplin/lib/services/keychain/KeychainSe import PluginRunner from '../app/services/plugins/PluginRunner'; import PluginService from '@joplin/lib/services/plugins/PluginService'; import FileApiDriverJoplinServer from '@joplin/lib/file-api-driver-joplinServer'; +import OneDriveApi from '@joplin/lib/onedrive-api'; +import SyncTargetOneDrive from '@joplin/lib/SyncTargetOneDrive'; const fs = require('fs-extra'); const { JoplinDatabase } = require('@joplin/lib/joplin-database.js'); @@ -40,7 +42,6 @@ const { shimInit } = require('@joplin/lib/shim-init-node.js'); const SyncTargetRegistry = require('@joplin/lib/SyncTargetRegistry.js'); const SyncTargetMemory = require('@joplin/lib/SyncTargetMemory.js'); const SyncTargetFilesystem = require('@joplin/lib/SyncTargetFilesystem.js'); -const SyncTargetOneDrive = require('@joplin/lib/SyncTargetOneDrive.js'); const SyncTargetNextcloud = require('@joplin/lib/SyncTargetNextcloud.js'); const SyncTargetDropbox = require('@joplin/lib/SyncTargetDropbox.js'); const SyncTargetAmazonS3 = require('@joplin/lib/SyncTargetAmazonS3.js'); @@ -52,7 +53,6 @@ const ResourceFetcher = require('@joplin/lib/services/ResourceFetcher.js'); const WebDavApi = require('@joplin/lib/WebDavApi'); const DropboxApi = require('@joplin/lib/DropboxApi'); const JoplinServerApi = require('@joplin/lib/JoplinServerApi2').default; -const { OneDriveApi } = require('@joplin/lib/onedrive-api'); const { loadKeychainServiceAndSettings } = require('@joplin/lib/services/SettingUtils'); const md5 = require('md5'); const S3 = require('aws-sdk/clients/s3'); diff --git a/packages/app-mobile/android/app/build.gradle b/packages/app-mobile/android/app/build.gradle index f362f0a960..524945b738 100644 --- a/packages/app-mobile/android/app/build.gradle +++ b/packages/app-mobile/android/app/build.gradle @@ -141,7 +141,7 @@ android { applicationId "net.cozic.joplin" minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion - versionCode 2097620 + versionCode 2097621 versionName "1.7.0" ndk { abiFilters "armeabi-v7a", "x86", "arm64-v8a", "x86_64" diff --git a/packages/app-mobile/ios/Joplin.xcodeproj/project.pbxproj b/packages/app-mobile/ios/Joplin.xcodeproj/project.pbxproj index 170a8f1c23..2a708159fb 100644 --- a/packages/app-mobile/ios/Joplin.xcodeproj/project.pbxproj +++ b/packages/app-mobile/ios/Joplin.xcodeproj/project.pbxproj @@ -338,7 +338,7 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; - CURRENT_PROJECT_VERSION = 60; + CURRENT_PROJECT_VERSION = 61; DEVELOPMENT_TEAM = A9BXAFS6CT; ENABLE_BITCODE = NO; INFOPLIST_FILE = Joplin/Info.plist; @@ -365,7 +365,7 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; - CURRENT_PROJECT_VERSION = 60; + CURRENT_PROJECT_VERSION = 61; DEVELOPMENT_TEAM = A9BXAFS6CT; INFOPLIST_FILE = Joplin/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 9.0; diff --git a/packages/app-mobile/root.tsx b/packages/app-mobile/root.tsx index 7b727c0521..e3be1f080b 100644 --- a/packages/app-mobile/root.tsx +++ b/packages/app-mobile/root.tsx @@ -25,6 +25,7 @@ import { loadKeychainServiceAndSettings } from '@joplin/lib/services/SettingUtil import KeychainServiceDriverMobile from '@joplin/lib/services/keychain/KeychainServiceDriver.mobile'; import { setLocale, closestSupportedLocale, defaultLocale } from '@joplin/lib/locale'; import SyncTargetJoplinServer from '@joplin/lib/SyncTargetJoplinServer'; +import SyncTargetOneDrive from '@joplin/lib/SyncTargetOneDrive'; const { AppState, Keyboard, NativeModules, BackHandler, Animated, View, StatusBar } = require('react-native'); @@ -75,7 +76,6 @@ const WelcomeUtils = require('@joplin/lib/WelcomeUtils'); const { themeStyle } = require('./components/global-style.js'); const SyncTargetRegistry = require('@joplin/lib/SyncTargetRegistry.js'); -const SyncTargetOneDrive = require('@joplin/lib/SyncTargetOneDrive.js'); const SyncTargetFilesystem = require('@joplin/lib/SyncTargetFilesystem.js'); const SyncTargetNextcloud = require('@joplin/lib/SyncTargetNextcloud.js'); const SyncTargetWebDAV = require('@joplin/lib/SyncTargetWebDAV.js'); diff --git a/packages/lib/BaseApplication.ts b/packages/lib/BaseApplication.ts index 7edaad7a78..e2b102c96a 100644 --- a/packages/lib/BaseApplication.ts +++ b/packages/lib/BaseApplication.ts @@ -7,6 +7,7 @@ import KeychainServiceDriver from './services/keychain/KeychainServiceDriver.nod import { _, setLocale } from './locale'; import KvStore from './services/KvStore'; import SyncTargetJoplinServer from './SyncTargetJoplinServer'; +import SyncTargetOneDrive from './SyncTargetOneDrive'; const { createStore, applyMiddleware } = require('redux'); const { defaultState, stateUtils } = require('./reducer'); @@ -30,7 +31,6 @@ const EventEmitter = require('events'); const syswidecas = require('./vendor/syswide-cas'); const SyncTargetRegistry = require('./SyncTargetRegistry.js'); const SyncTargetFilesystem = require('./SyncTargetFilesystem.js'); -const SyncTargetOneDrive = require('./SyncTargetOneDrive.js'); const SyncTargetNextcloud = require('./SyncTargetNextcloud.js'); const SyncTargetWebDAV = require('./SyncTargetWebDAV.js'); const SyncTargetDropbox = require('./SyncTargetDropbox.js'); diff --git a/packages/lib/Logger.ts b/packages/lib/Logger.ts index aeea77e3da..e2edb3a6d5 100644 --- a/packages/lib/Logger.ts +++ b/packages/lib/Logger.ts @@ -2,6 +2,9 @@ const moment = require('moment'); const time = require('./time').default; const { FsDriverDummy } = require('./fs-driver-dummy.js'); const { sprintf } = require('sprintf-js'); +const Mutex = require('async-mutex').Mutex; + +const writeToFileMutex_ = new Mutex(); export enum TargetType { Database = 'database', @@ -205,12 +208,23 @@ class Logger { const line = [timestamp]; if (targetPrefix) line.push(targetPrefix); line.push(this.objectsToString(...object)); - try { - // TODO: Should log async - Logger.fsDriver().appendFileSync(target.path, `${line.join(': ')}\n`); - } catch (error) { + + // Write to file using a mutex so that log entries appear in the + // correct order (otherwise, since the async call is not awaited + // by caller, multiple log call in a row are not guaranteed to + // appear in the right order). We also can't use a sync call + // because that would slow down the main process, especially + // when many log operations are being done (eg. during sync in + // dev mode). + let release: Function = null; + writeToFileMutex_.acquire().then((r: Function) => { + release = r; + return Logger.fsDriver().appendFile(target.path, `${line.join(': ')}\n`, 'utf8'); + }).catch((error: any) => { console.error('Cannot write to log file:', error); - } + }).finally(() => { + if (release) release(); + }); } else if (target.type == 'database') { const msg = []; if (targetPrefix) msg.push(targetPrefix); diff --git a/packages/lib/SyncTargetOneDrive.js b/packages/lib/SyncTargetOneDrive.ts similarity index 87% rename from packages/lib/SyncTargetOneDrive.js rename to packages/lib/SyncTargetOneDrive.ts index 4d23a1a637..45d8dec03e 100644 --- a/packages/lib/SyncTargetOneDrive.js +++ b/packages/lib/SyncTargetOneDrive.ts @@ -1,18 +1,20 @@ +import OneDriveApi from './onedrive-api'; +import { _ } from './locale'; +import Setting from './models/Setting'; +import Synchronizer from './Synchronizer'; + const BaseSyncTarget = require('./BaseSyncTarget.js'); -const { _ } = require('./locale'); -const { OneDriveApi } = require('./onedrive-api.js'); -const Setting = require('./models/Setting').default; const { parameters } = require('./parameters.js'); const { FileApi } = require('./file-api.js'); -const Synchronizer = require('./Synchronizer').default; const { FileApiDriverOneDrive } = require('./file-api-driver-onedrive.js'); -class SyncTargetOneDrive extends BaseSyncTarget { +export default class SyncTargetOneDrive extends BaseSyncTarget { + static id() { return 3; } - constructor(db, options = null) { + constructor(db: any, options: any = null) { super(db, options); this.api_ = null; } @@ -58,9 +60,8 @@ class SyncTargetOneDrive extends BaseSyncTarget { const isPublic = Setting.value('appType') != 'cli' && Setting.value('appType') != 'desktop'; this.api_ = new OneDriveApi(this.oneDriveParameters().id, this.oneDriveParameters().secret, isPublic); - this.api_.setLogger(this.logger()); - this.api_.on('authRefreshed', a => { + this.api_.on('authRefreshed', (a: any) => { this.logger().info('Saving updated OneDrive auth.'); Setting.setValue(`sync.${this.syncTargetId()}.auth`, a ? JSON.stringify(a) : null); }); @@ -110,5 +111,3 @@ class SyncTargetOneDrive extends BaseSyncTarget { } } - -module.exports = SyncTargetOneDrive; diff --git a/packages/lib/SyncTargetOneDriveDev.js b/packages/lib/SyncTargetOneDriveDev.js deleted file mode 100644 index 48949925ff..0000000000 --- a/packages/lib/SyncTargetOneDriveDev.js +++ /dev/null @@ -1,27 +0,0 @@ -const SyncTargetOneDrive = require('./SyncTargetOneDrive.js'); -const { _ } = require('./locale'); -const { parameters } = require('./parameters.js'); - -class SyncTargetOneDriveDev extends SyncTargetOneDrive { - static id() { - return 4; - } - - static targetName() { - return 'onedrive_dev'; - } - - static label() { - return _('OneDrive Dev (For testing only)'); - } - - syncTargetId() { - return SyncTargetOneDriveDev.id(); - } - - oneDriveParameters() { - return parameters('dev').oneDrive; - } -} - -module.exports = SyncTargetOneDriveDev; diff --git a/packages/lib/file-api-driver-onedrive.js b/packages/lib/file-api-driver-onedrive.js index 8b0aa5ccc7..a9c07310e1 100644 --- a/packages/lib/file-api-driver-onedrive.js +++ b/packages/lib/file-api-driver-onedrive.js @@ -1,4 +1,5 @@ const moment = require('moment'); +const { basicDelta } = require('./file-api'); const { dirname, basename } = require('./path-utils'); const shim = require('./shim').default; const Buffer = require('buffer').Buffer; @@ -83,10 +84,12 @@ class FileApiDriverOneDrive { context: null, }, options); - let query = this.itemFilter_(); + let query = Object.assign({}, this.itemFilter_(), { '$top': 1000 }); let url = `${this.makePath_(path)}:/children`; if (options.context) { + // If there's a context, it already includes all required query + // parameters, including $top query = null; url = options.context; } @@ -213,6 +216,24 @@ class FileApiDriverOneDrive { } async delta(path, options = null) { + const getDirStats = async path => { + let items = []; + let context = null; + + while (true) { + const result = await this.list(path, { includeDirs: false, context: context }); + items = items.concat(result.items); + context = result.context; + if (!result.hasMore) break; + } + + return items; + }; + + return await basicDelta(path, getDirStats, options); + } + + async delta_BROKEN(path, options = null) { const output = { hasMore: false, context: {}, diff --git a/packages/lib/onedrive-api.js b/packages/lib/onedrive-api.ts similarity index 84% rename from packages/lib/onedrive-api.js rename to packages/lib/onedrive-api.ts index cf2fa07287..ee5b90a28c 100644 --- a/packages/lib/onedrive-api.js +++ b/packages/lib/onedrive-api.ts @@ -1,17 +1,28 @@ -const shim = require('./shim').default; +import shim from './shim'; +import time from './time'; +import Logger from './Logger'; +import { _ } from './locale'; + const { stringify } = require('query-string'); -const time = require('./time').default; -const Logger = require('./Logger').default; -const { _ } = require('./locale'); const urlUtils = require('./urlUtils.js'); const Buffer = require('buffer').Buffer; -class OneDriveApi { +const logger = Logger.create('OneDriveApi'); + +export default class OneDriveApi { + + private clientId_: string; + private clientSecret_: string; + private auth_: any = null; + private accountProperties_: any = null; + private isPublic_: boolean; + private listeners_: Record; + // `isPublic` is to tell OneDrive whether the application is a "public" one (Mobile and desktop // apps are considered "public"), in which case the secret should not be sent to the API. // In practice the React Native app is public, and the Node one is not because we // use a local server for the OAuth dance. - constructor(clientId, clientSecret, isPublic) { + constructor(clientId: string, clientSecret: string, isPublic: boolean) { this.clientId_ = clientId; this.clientSecret_ = clientSecret; this.auth_ = null; @@ -20,29 +31,20 @@ class OneDriveApi { this.listeners_ = { authRefreshed: [], }; - this.logger_ = new Logger(); - } - - setLogger(l) { - this.logger_ = l; - } - - logger() { - return this.logger_; } isPublic() { return this.isPublic_; } - dispatch(eventName, param) { + dispatch(eventName: string, param: any) { const ls = this.listeners_[eventName]; for (let i = 0; i < ls.length; i++) { ls[i](param); } } - on(eventName, callback) { + on(eventName: string, callback: Function) { this.listeners_[eventName].push(callback); } @@ -54,11 +56,11 @@ class OneDriveApi { return 'https://login.microsoftonline.com/common/oauth2/nativeclient'; } - auth() { + auth(): any { return this.auth_; } - setAuth(auth) { + setAuth(auth: any) { this.auth_ = auth; this.dispatch('authRefreshed', this.auth()); } @@ -81,7 +83,7 @@ class OneDriveApi { return `${r.parentReference.path}/${r.name}`; } - authCodeUrl(redirectUri) { + authCodeUrl(redirectUri: string) { const query = { client_id: this.clientId_, scope: 'files.readwrite offline_access sites.readwrite.all', @@ -91,8 +93,8 @@ class OneDriveApi { return `https://login.microsoftonline.com/common/oauth2/v2.0/authorize?${stringify(query)}`; } - async execTokenRequest(code, redirectUri) { - const body = {}; + async execTokenRequest(code: string, redirectUri: string) { + const body: any = {}; body['client_id'] = this.clientId(); if (!this.isPublic()) body['client_secret'] = this.clientSecret(); body['code'] = code; @@ -123,12 +125,12 @@ class OneDriveApi { } } - oneDriveErrorResponseToError(errorResponse) { + oneDriveErrorResponseToError(errorResponse: any) { if (!errorResponse) return new Error('Undefined error'); if (errorResponse.error) { const e = errorResponse.error; - const output = new Error(e.message); + const output: any = new Error(e.message); if (e.code) output.code = e.code; if (e.innerError) output.innerError = e.innerError; return output; @@ -137,7 +139,7 @@ class OneDriveApi { } } - async uploadChunk(url, handle, buffer, options) { + async uploadChunk(url: string, handle: any, buffer: any, options: any) { options = Object.assign({}, options); if (!options.method) { options.method = 'POST'; } @@ -159,7 +161,7 @@ class OneDriveApi { return response; } - async uploadBigFile(url, options) { + async uploadBigFile(url: string, options: any) { const response = await shim.fetch(url, { method: 'POST', headers: { @@ -199,7 +201,7 @@ class OneDriveApi { endByte = (i + 1) * chunkSize - 1; contentLength = chunkSize; } - this.logger().debug(`Uploading File Fragment ${(startByte / 1048576).toFixed(2)} - ${(endByte / 1048576).toFixed(2)} from ${(byteSize / 1048576).toFixed(2)} Mbit ...`); + logger.debug(`Uploading File Fragment ${(startByte / 1048576).toFixed(2)} - ${(endByte / 1048576).toFixed(2)} from ${(byteSize / 1048576).toFixed(2)} Mbit ...`); const headers = { 'Content-Length': contentLength, 'Content-Range': `bytes ${startByte}-${endByte}/${byteSize}`, @@ -215,7 +217,7 @@ class OneDriveApi { return { ok: true }; } catch (error) { const type = (handle) ? 'Resource' : 'Note Content'; - this.logger().error(`Couldn't upload ${type} > 4 Mb. Got unhandled error:`, error ? error.code : '', error ? error.message : '', error); + logger.error(`Couldn't upload ${type} > 4 Mb. Got unhandled error:`, error ? error.code : '', error ? error.message : '', error); throw error; } finally { if (handle) await shim.fsDriver().close(handle); @@ -224,7 +226,7 @@ class OneDriveApi { } } - async exec(method, path, query = null, data = null, options = null) { + async exec(method: string, path: string, query: any = null, data: any = null, options: any = null) { if (!path) throw new Error('Path is required'); method = method.toUpperCase(); @@ -264,6 +266,12 @@ class OneDriveApi { for (let i = 0; i < 5; i++) { options.headers['Authorization'] = `bearer ${this.token()}`; + const handleRequestRepeat = async (error: any) => { + logger.info(`Got error below - retrying (${i})...`); + logger.info(error); + await time.sleep((i + 1) * 5); + }; + let response = null; try { if (path.includes('/createUploadSession')) { @@ -277,24 +285,31 @@ class OneDriveApi { response = await shim.fetchBlob(url, options); } } catch (error) { - this.logger().error('Got unhandled error:', error ? error.code : '', error ? error.message : '', error); - throw error; + if (shim.fetchRequestCanBeRetried(error)) { + await handleRequestRepeat(error); + continue; + } else { + logger.error('Got unhandled error:', error ? error.code : '', error ? error.message : '', error); + throw error; + } } if (!response.ok) { const errorResponseText = await response.text(); let errorResponse = null; + try { errorResponse = JSON.parse(errorResponseText); // await response.json(); } catch (error) { error.message = `OneDriveApi::exec: Cannot parse JSON error: ${errorResponseText} ${error.message}`; - throw error; + await handleRequestRepeat(error); + continue; } const error = this.oneDriveErrorResponseToError(errorResponse); if (error.code == 'InvalidAuthenticationToken' || error.code == 'unauthenticated') { - this.logger().info('Token expired: refreshing...'); + logger.info('Token expired: refreshing...'); await this.refreshAccessToken(); continue; } else if (error && ((error.error && error.error.code == 'generalException') || error.code == 'generalException' || error.code == 'EAGAIN')) { @@ -312,9 +327,7 @@ class OneDriveApi { // type: 'system', // errno: 'EAGAIN', // code: 'EAGAIN' } - this.logger().info(`Got error below - retrying (${i})...`); - this.logger().info(error); - await time.sleep((i + 1) * 3); + await handleRequestRepeat(error); continue; } else if (error && (error.code === 'resourceModified' || (error.error && error.error.code === 'resourceModified'))) { // NOTE: not tested, very hard to reproduce and non-informative error message, but can be repeated @@ -324,9 +337,7 @@ class OneDriveApi { // Header: {"_headers":{"cache-control":["private"],"transfer-encoding":["chunked"],"content-type":["application/json"],"request-id":["d...ea47"],"client-request-id":["d99...ea47"],"x-ms-ags-diagnostic":["{\"ServerInfo\":{\"DataCenter\":\"North Europe\",\"Slice\":\"SliceA\",\"Ring\":\"2\",\"ScaleUnit\":\"000\",\"Host\":\"AGSFE_IN_13\",\"ADSiteName\":\"DUB\"}}"],"duration":["96.9464"],"date":[],"connection":["close"]}} // Request: PATCH https://graph.microsoft.com/v1.0/drive/root:/Apps/JoplinDev/f56c5601fee94b8085524513bf3e352f.md null "{\"fileSystemInfo\":{\"lastModifiedDateTime\":\"....\"}}" {"headers":{"Content-Type":"application/json","Authorization":"bearer ... - this.logger().info(`Got error below - retrying (${i})...`); - this.logger().info(error); - await time.sleep((i + 1) * 3); + await handleRequestRepeat(error); continue; } else if (error.code == 'itemNotFound' && method == 'DELETE') { // Deleting a non-existing item is ok - noop @@ -344,7 +355,7 @@ class OneDriveApi { throw new Error(`Could not execute request after multiple attempts: ${method} ${url}`); } - setAccountProperties(accountProperties) { + setAccountProperties(accountProperties: any) { this.accountProperties_ = accountProperties; } @@ -360,7 +371,7 @@ class OneDriveApi { } } - async execJson(method, path, query, data) { + async execJson(method: string, path: string, query: any = null, data: any = null) { const response = await this.exec(method, path, query, data); const errorResponseText = await response.text(); try { @@ -373,7 +384,7 @@ class OneDriveApi { } } - async execText(method, path, query, data) { + async execText(method: string, path: string, query: any = null, data: any = null) { const response = await this.exec(method, path, query, data); const output = await response.text(); return output; @@ -385,7 +396,7 @@ class OneDriveApi { throw new Error(_('Cannot refresh token: authentication data is missing. Starting the synchronisation again may fix the problem.')); } - const body = {}; + const body: any = {}; body['client_id'] = this.clientId(); if (!this.isPublic()) body['client_secret'] = this.clientSecret(); body['refresh_token'] = this.auth_.refresh_token; @@ -410,5 +421,3 @@ class OneDriveApi { this.setAuth(auth); } } - -module.exports = { OneDriveApi }; diff --git a/readme/changelog_cli.md b/readme/changelog_cli.md index 6772856dc5..4e7945180e 100644 --- a/readme/changelog_cli.md +++ b/readme/changelog_cli.md @@ -1,5 +1,9 @@ # Joplin terminal app changelog +## [cli-v1.6.4](https://github.com/laurent22/joplin/releases/tag/cli-v1.6.4) - 2021-01-21T10:01:15Z + +- Fixed: Fixed infinite sync issue with OneDrive (#4305) + ## [cli-v1.6.3](https://github.com/laurent22/joplin/releases/tag/cli-v1.6.3) - 2021-01-11T11:52:11Z - New: Add more log info when a revision cannot be deleted due to still-encrypted itel