mirror of
https://github.com/laurent22/joplin.git
synced 2026-11-05 16:22:46 +00:00
Compare commits
11
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
84ec845499 | ||
|
|
5892a0678b | ||
|
|
cb26ab414d | ||
|
|
74fcd474af | ||
|
|
c7406f397c | ||
|
|
d22a29ee3d | ||
|
|
bcd568a496 | ||
|
|
66e79ccb23 | ||
|
|
08ee2b200f | ||
|
|
57b8aa1789 | ||
|
|
b5d792c606 |
No files matched your search
+9
-11
@@ -2,26 +2,24 @@ const gulp = require('gulp');
|
||||
const utils = require('./packages/tools/gulp/utils');
|
||||
|
||||
const tasks = {
|
||||
// copyLib: require('./packages/tools/gulp/tasks/copyLib'),
|
||||
// tsc: require('./packages/tools/gulp/tasks/tsc'),
|
||||
updateIgnoredTypeScriptBuild: require('./packages/tools/gulp/tasks/updateIgnoredTypeScriptBuild'),
|
||||
buildCommandIndex: require('./packages/tools/gulp/tasks/buildCommandIndex'),
|
||||
// deleteBuildDirs: require('./packages/tools/gulp/tasks/deleteBuildDirs'),
|
||||
completePublishAll: {
|
||||
fn: async () => {
|
||||
// await utils.execCommandVerbose('git pull');
|
||||
|
||||
await utils.execCommandVerbose('git', ['add', '-A']);
|
||||
await utils.execCommandVerbose('git', ['commit', '-m', 'Releasing sub-packages']);
|
||||
await utils.execCommandVerbose('lerna', ['publish', 'from-package', '-y']);
|
||||
|
||||
// Lerna does some unnecessary auth check that doesn't work with
|
||||
// automation tokens, thus the --no-verify-access. Automation token
|
||||
// is still used for access when publishing even with this flag
|
||||
// (publishing would fail otherwise).
|
||||
// https://github.com/lerna/lerna/issues/2788
|
||||
await utils.execCommandVerbose('lerna', ['publish', 'from-package', '-y', '--no-verify-access']);
|
||||
|
||||
await utils.execCommandVerbose('git', ['push']);
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
utils.registerGulpTasks(gulp, tasks);
|
||||
|
||||
// gulp.task('build', gulp.series('copyLib', 'tsc', 'updateIgnoredTypeScriptBuild'));
|
||||
|
||||
// // The clean task removes build directories and copy back the library. This is useful
|
||||
// // when switching from one branch to another.
|
||||
// gulp.task('clean', gulp.series('deleteBuildDirs', 'copyLib'));
|
||||
Generated
+2
-2
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "joplin",
|
||||
"version": "2.5.0",
|
||||
"version": "2.6.1",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "joplin",
|
||||
"version": "2.5.0",
|
||||
"version": "2.6.1",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"aws-sdk": "^2.588.0",
|
||||
|
||||
@@ -33,7 +33,7 @@
|
||||
],
|
||||
"owner": "Laurent Cozic"
|
||||
},
|
||||
"version": "2.5.0",
|
||||
"version": "2.6.1",
|
||||
"bin": {
|
||||
"joplin": "./main.js"
|
||||
},
|
||||
@@ -41,8 +41,8 @@
|
||||
"node": ">=10.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@joplin/lib": "~2.5",
|
||||
"@joplin/renderer": "~2.5",
|
||||
"@joplin/lib": "~2.6",
|
||||
"@joplin/renderer": "~2.6",
|
||||
"aws-sdk": "^2.588.0",
|
||||
"chalk": "^4.1.0",
|
||||
"compare-version": "^0.1.2",
|
||||
@@ -68,7 +68,7 @@
|
||||
"yargs-parser": "^7.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@joplin/tools": "~2.5",
|
||||
"@joplin/tools": "~2.6",
|
||||
"@types/fs-extra": "^9.0.6",
|
||||
"@types/jest": "^26.0.15",
|
||||
"@types/node": "^14.14.6",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"manifest_version": 2,
|
||||
"name": "Joplin Web Clipper [DEV]",
|
||||
"version": "2.5.0",
|
||||
"version": "2.6.0",
|
||||
"description": "Capture and save web pages and screenshots from your browser to Joplin.",
|
||||
"homepage_url": "https://joplinapp.org",
|
||||
"content_security_policy": "script-src 'self'; object-src 'self'",
|
||||
|
||||
Generated
+2
-2
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "@joplin/app-desktop",
|
||||
"version": "2.5.10",
|
||||
"version": "2.6.0",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@joplin/app-desktop",
|
||||
"version": "2.5.10",
|
||||
"version": "2.6.0",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@electron/remote": "^2.0.1",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@joplin/app-desktop",
|
||||
"version": "2.5.10",
|
||||
"version": "2.6.0",
|
||||
"description": "Joplin for Desktop",
|
||||
"main": "main.js",
|
||||
"private": true,
|
||||
@@ -105,7 +105,7 @@
|
||||
},
|
||||
"homepage": "https://github.com/laurent22/joplin#readme",
|
||||
"devDependencies": {
|
||||
"@joplin/tools": "~2.5",
|
||||
"@joplin/tools": "~2.6",
|
||||
"@testing-library/react-hooks": "^3.4.2",
|
||||
"@types/jest": "^26.0.15",
|
||||
"@types/node": "^14.14.6",
|
||||
@@ -136,8 +136,8 @@
|
||||
"dependencies": {
|
||||
"@electron/remote": "^2.0.1",
|
||||
"@fortawesome/fontawesome-free": "^5.13.0",
|
||||
"@joplin/lib": "~2.5",
|
||||
"@joplin/renderer": "~2.5",
|
||||
"@joplin/lib": "~2.6",
|
||||
"@joplin/renderer": "~2.6",
|
||||
"async-mutex": "^0.1.3",
|
||||
"codemirror": "^5.56.0",
|
||||
"color": "^3.1.2",
|
||||
|
||||
@@ -28,6 +28,7 @@ build/
|
||||
.gradle
|
||||
local.properties
|
||||
*.iml
|
||||
*.hprof
|
||||
|
||||
# node.js
|
||||
#
|
||||
|
||||
@@ -123,6 +123,11 @@ def jscFlavor = 'org.webkit:android-jsc-intl:+'
|
||||
*/
|
||||
def enableHermes = project.ext.react.get("enableHermes", false);
|
||||
|
||||
/**
|
||||
* Architectures to build native code for in debug.
|
||||
*/
|
||||
def nativeArchitectures = project.getProperties().get("reactNativeDebugArchitectures")
|
||||
|
||||
android {
|
||||
compileSdkVersion rootProject.ext.compileSdkVersion
|
||||
|
||||
@@ -141,8 +146,8 @@ android {
|
||||
applicationId "net.cozic.joplin"
|
||||
minSdkVersion rootProject.ext.minSdkVersion
|
||||
targetSdkVersion rootProject.ext.targetSdkVersion
|
||||
versionCode 2097656
|
||||
versionName "2.5.5"
|
||||
versionCode 2097657
|
||||
versionName "2.6.1"
|
||||
ndk {
|
||||
abiFilters "armeabi-v7a", "x86", "arm64-v8a", "x86_64"
|
||||
}
|
||||
@@ -180,6 +185,11 @@ android {
|
||||
buildTypes {
|
||||
debug {
|
||||
signingConfig signingConfigs.debug
|
||||
if (nativeArchitectures) {
|
||||
ndk {
|
||||
abiFilters nativeArchitectures.split(',')
|
||||
}
|
||||
}
|
||||
}
|
||||
release {
|
||||
// Caution! In production, you need to generate your own keystore file.
|
||||
@@ -215,7 +225,7 @@ dependencies {
|
||||
implementation "androidx.swiperefreshlayout:swiperefreshlayout:1.0.0"
|
||||
|
||||
debugImplementation("com.facebook.flipper:flipper:${FLIPPER_VERSION}") {
|
||||
exclude group:'com.facebook.fbjni'
|
||||
exclude group:'com.facebook.fbjni'
|
||||
}
|
||||
|
||||
debugImplementation("com.facebook.flipper:flipper-network-plugin:${FLIPPER_VERSION}") {
|
||||
@@ -242,7 +252,7 @@ dependencies {
|
||||
// Run this once to be able to run the application with BUCK
|
||||
// puts all compile dependencies into folder libs for BUCK to use
|
||||
task copyDownloadableDepsToLibs(type: Copy) {
|
||||
from configurations.compile
|
||||
from configurations.implementation
|
||||
into 'libs'
|
||||
}
|
||||
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
<!-- Base application theme. -->
|
||||
<style name="AppTheme" parent="Theme.AppCompat.DayNight.NoActionBar">
|
||||
<!-- Customize your theme here. -->
|
||||
<item name="android:textColor">#000000</item>
|
||||
</style>
|
||||
|
||||
</resources>
|
||||
@@ -2,18 +2,18 @@
|
||||
|
||||
buildscript {
|
||||
ext {
|
||||
buildToolsVersion = "29.0.3"
|
||||
buildToolsVersion = "30.0.2"
|
||||
minSdkVersion = 21
|
||||
compileSdkVersion = 29
|
||||
targetSdkVersion = 29
|
||||
ndkVersion = "20.1.5948944"
|
||||
compileSdkVersion = 30
|
||||
targetSdkVersion = 30
|
||||
ndkVersion = "21.4.7075529"
|
||||
}
|
||||
repositories {
|
||||
google()
|
||||
jcenter()
|
||||
mavenCentral()
|
||||
}
|
||||
dependencies {
|
||||
classpath("com.android.tools.build:gradle:4.1.0")
|
||||
classpath("com.android.tools.build:gradle:4.2.2")
|
||||
// NOTE: Do not place your application dependencies here; they belong
|
||||
// in the individual module build.gradle files
|
||||
}
|
||||
@@ -21,6 +21,7 @@ buildscript {
|
||||
|
||||
allprojects {
|
||||
repositories {
|
||||
mavenCentral()
|
||||
mavenLocal()
|
||||
maven {
|
||||
// All of React Native (JS, Obj-C sources, Android binaries) is installed from npm
|
||||
@@ -32,7 +33,6 @@ allprojects {
|
||||
}
|
||||
|
||||
google()
|
||||
jcenter()
|
||||
maven { url 'https://www.jitpack.io' }
|
||||
}
|
||||
}
|
||||
@@ -26,4 +26,4 @@ android.useAndroidX=true
|
||||
android.enableJetifier=true
|
||||
|
||||
# Version of flipper SDK to use with React Native
|
||||
FLIPPER_VERSION=0.75.1
|
||||
FLIPPER_VERSION=0.99.0
|
||||
@@ -1,5 +1,5 @@
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-6.7-all.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-6.9-all.zip
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
@@ -492,13 +492,13 @@
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_ENTITLEMENTS = Joplin/Joplin.entitlements;
|
||||
CURRENT_PROJECT_VERSION = 74;
|
||||
CURRENT_PROJECT_VERSION = 75;
|
||||
DEVELOPMENT_TEAM = A9BXAFS6CT;
|
||||
ENABLE_BITCODE = NO;
|
||||
INFOPLIST_FILE = Joplin/Info.plist;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
|
||||
MARKETING_VERSION = 12.5.0;
|
||||
MARKETING_VERSION = 12.6.0;
|
||||
OTHER_LDFLAGS = (
|
||||
"$(inherited)",
|
||||
"-ObjC",
|
||||
@@ -521,12 +521,12 @@
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_ENTITLEMENTS = Joplin/Joplin.entitlements;
|
||||
CURRENT_PROJECT_VERSION = 74;
|
||||
CURRENT_PROJECT_VERSION = 75;
|
||||
DEVELOPMENT_TEAM = A9BXAFS6CT;
|
||||
INFOPLIST_FILE = Joplin/Info.plist;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
|
||||
MARKETING_VERSION = 12.5.0;
|
||||
MARKETING_VERSION = 12.6.0;
|
||||
OTHER_LDFLAGS = (
|
||||
"$(inherited)",
|
||||
"-ObjC",
|
||||
@@ -667,14 +667,14 @@
|
||||
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
|
||||
CODE_SIGN_ENTITLEMENTS = ShareExtension/ShareExtension.entitlements;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 74;
|
||||
CURRENT_PROJECT_VERSION = 75;
|
||||
DEBUG_INFORMATION_FORMAT = dwarf;
|
||||
DEVELOPMENT_TEAM = A9BXAFS6CT;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu11;
|
||||
INFOPLIST_FILE = ShareExtension/Info.plist;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks";
|
||||
MARKETING_VERSION = 12.5.0;
|
||||
MARKETING_VERSION = 12.6.0;
|
||||
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
|
||||
MTL_FAST_MATH = YES;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = net.cozic.joplin.ShareExtension;
|
||||
@@ -698,14 +698,14 @@
|
||||
CODE_SIGN_ENTITLEMENTS = ShareExtension/ShareExtension.entitlements;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
COPY_PHASE_STRIP = NO;
|
||||
CURRENT_PROJECT_VERSION = 74;
|
||||
CURRENT_PROJECT_VERSION = 75;
|
||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||
DEVELOPMENT_TEAM = A9BXAFS6CT;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu11;
|
||||
INFOPLIST_FILE = ShareExtension/Info.plist;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks";
|
||||
MARKETING_VERSION = 12.5.0;
|
||||
MARKETING_VERSION = 12.6.0;
|
||||
MTL_FAST_MATH = YES;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = net.cozic.joplin.ShareExtension;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
|
||||
@@ -3,7 +3,7 @@ require_relative '../node_modules/@react-native-community/cli-platform-ios/nativ
|
||||
|
||||
# Note: it was 13.4 to get @react-native-community/datetimepicker to work
|
||||
# but it's probably not necessary actually. Just needed to upgrade XCode.
|
||||
platform :ios, '10.0'
|
||||
platform :ios, '11.0'
|
||||
|
||||
target 'Joplin' do
|
||||
config = use_native_modules!
|
||||
@@ -24,6 +24,7 @@ target 'Joplin' do
|
||||
# use_flipper!()
|
||||
# post_install do |installer|
|
||||
# react_native_post_install(installer)
|
||||
# __apply_Xcode_12_5_M1_post_install_workaround(installer)
|
||||
# end
|
||||
|
||||
# RN 0.63:
|
||||
|
||||
@@ -1,212 +1,218 @@
|
||||
PODS:
|
||||
- boost-for-react-native (1.63.0)
|
||||
- boost (1.76.0)
|
||||
- DoubleConversion (1.1.6)
|
||||
- FBLazyVector (0.64.2)
|
||||
- FBReactNativeSpec (0.64.2):
|
||||
- RCT-Folly (= 2020.01.13.00)
|
||||
- RCTRequired (= 0.64.2)
|
||||
- RCTTypeSafety (= 0.64.2)
|
||||
- React-Core (= 0.64.2)
|
||||
- React-jsi (= 0.64.2)
|
||||
- ReactCommon/turbomodule/core (= 0.64.2)
|
||||
- FBLazyVector (0.66.1)
|
||||
- FBReactNativeSpec (0.66.1):
|
||||
- RCT-Folly (= 2021.06.28.00-v2)
|
||||
- RCTRequired (= 0.66.1)
|
||||
- RCTTypeSafety (= 0.66.1)
|
||||
- React-Core (= 0.66.1)
|
||||
- React-jsi (= 0.66.1)
|
||||
- ReactCommon/turbomodule/core (= 0.66.1)
|
||||
- fmt (6.2.1)
|
||||
- glog (0.3.5)
|
||||
- JoplinCommonShareExtension (1.0.0)
|
||||
- JoplinRNShareExtension (1.0.0):
|
||||
- JoplinCommonShareExtension
|
||||
- React (= 0.64.2)
|
||||
- RCT-Folly (2020.01.13.00):
|
||||
- boost-for-react-native
|
||||
- React (= 0.66.1)
|
||||
- RCT-Folly (2021.06.28.00-v2):
|
||||
- boost
|
||||
- DoubleConversion
|
||||
- fmt (~> 6.2.1)
|
||||
- glog
|
||||
- RCT-Folly/Default (= 2021.06.28.00-v2)
|
||||
- RCT-Folly/Default (2021.06.28.00-v2):
|
||||
- boost
|
||||
- DoubleConversion
|
||||
- fmt (~> 6.2.1)
|
||||
- glog
|
||||
- RCTRequired (0.66.1)
|
||||
- RCTTypeSafety (0.66.1):
|
||||
- FBLazyVector (= 0.66.1)
|
||||
- RCT-Folly (= 2021.06.28.00-v2)
|
||||
- RCTRequired (= 0.66.1)
|
||||
- React-Core (= 0.66.1)
|
||||
- React (0.66.1):
|
||||
- React-Core (= 0.66.1)
|
||||
- React-Core/DevSupport (= 0.66.1)
|
||||
- React-Core/RCTWebSocket (= 0.66.1)
|
||||
- React-RCTActionSheet (= 0.66.1)
|
||||
- React-RCTAnimation (= 0.66.1)
|
||||
- React-RCTBlob (= 0.66.1)
|
||||
- React-RCTImage (= 0.66.1)
|
||||
- React-RCTLinking (= 0.66.1)
|
||||
- React-RCTNetwork (= 0.66.1)
|
||||
- React-RCTSettings (= 0.66.1)
|
||||
- React-RCTText (= 0.66.1)
|
||||
- React-RCTVibration (= 0.66.1)
|
||||
- React-callinvoker (0.66.1)
|
||||
- React-Core (0.66.1):
|
||||
- glog
|
||||
- RCT-Folly (= 2021.06.28.00-v2)
|
||||
- React-Core/Default (= 0.66.1)
|
||||
- React-cxxreact (= 0.66.1)
|
||||
- React-jsi (= 0.66.1)
|
||||
- React-jsiexecutor (= 0.66.1)
|
||||
- React-perflogger (= 0.66.1)
|
||||
- Yoga
|
||||
- React-Core/CoreModulesHeaders (0.66.1):
|
||||
- glog
|
||||
- RCT-Folly (= 2021.06.28.00-v2)
|
||||
- React-Core/Default
|
||||
- React-cxxreact (= 0.66.1)
|
||||
- React-jsi (= 0.66.1)
|
||||
- React-jsiexecutor (= 0.66.1)
|
||||
- React-perflogger (= 0.66.1)
|
||||
- Yoga
|
||||
- React-Core/Default (0.66.1):
|
||||
- glog
|
||||
- RCT-Folly (= 2021.06.28.00-v2)
|
||||
- React-cxxreact (= 0.66.1)
|
||||
- React-jsi (= 0.66.1)
|
||||
- React-jsiexecutor (= 0.66.1)
|
||||
- React-perflogger (= 0.66.1)
|
||||
- Yoga
|
||||
- React-Core/DevSupport (0.66.1):
|
||||
- glog
|
||||
- RCT-Folly (= 2021.06.28.00-v2)
|
||||
- React-Core/Default (= 0.66.1)
|
||||
- React-Core/RCTWebSocket (= 0.66.1)
|
||||
- React-cxxreact (= 0.66.1)
|
||||
- React-jsi (= 0.66.1)
|
||||
- React-jsiexecutor (= 0.66.1)
|
||||
- React-jsinspector (= 0.66.1)
|
||||
- React-perflogger (= 0.66.1)
|
||||
- Yoga
|
||||
- React-Core/RCTActionSheetHeaders (0.66.1):
|
||||
- glog
|
||||
- RCT-Folly (= 2021.06.28.00-v2)
|
||||
- React-Core/Default
|
||||
- React-cxxreact (= 0.66.1)
|
||||
- React-jsi (= 0.66.1)
|
||||
- React-jsiexecutor (= 0.66.1)
|
||||
- React-perflogger (= 0.66.1)
|
||||
- Yoga
|
||||
- React-Core/RCTAnimationHeaders (0.66.1):
|
||||
- glog
|
||||
- RCT-Folly (= 2021.06.28.00-v2)
|
||||
- React-Core/Default
|
||||
- React-cxxreact (= 0.66.1)
|
||||
- React-jsi (= 0.66.1)
|
||||
- React-jsiexecutor (= 0.66.1)
|
||||
- React-perflogger (= 0.66.1)
|
||||
- Yoga
|
||||
- React-Core/RCTBlobHeaders (0.66.1):
|
||||
- glog
|
||||
- RCT-Folly (= 2021.06.28.00-v2)
|
||||
- React-Core/Default
|
||||
- React-cxxreact (= 0.66.1)
|
||||
- React-jsi (= 0.66.1)
|
||||
- React-jsiexecutor (= 0.66.1)
|
||||
- React-perflogger (= 0.66.1)
|
||||
- Yoga
|
||||
- React-Core/RCTImageHeaders (0.66.1):
|
||||
- glog
|
||||
- RCT-Folly (= 2021.06.28.00-v2)
|
||||
- React-Core/Default
|
||||
- React-cxxreact (= 0.66.1)
|
||||
- React-jsi (= 0.66.1)
|
||||
- React-jsiexecutor (= 0.66.1)
|
||||
- React-perflogger (= 0.66.1)
|
||||
- Yoga
|
||||
- React-Core/RCTLinkingHeaders (0.66.1):
|
||||
- glog
|
||||
- RCT-Folly (= 2021.06.28.00-v2)
|
||||
- React-Core/Default
|
||||
- React-cxxreact (= 0.66.1)
|
||||
- React-jsi (= 0.66.1)
|
||||
- React-jsiexecutor (= 0.66.1)
|
||||
- React-perflogger (= 0.66.1)
|
||||
- Yoga
|
||||
- React-Core/RCTNetworkHeaders (0.66.1):
|
||||
- glog
|
||||
- RCT-Folly (= 2021.06.28.00-v2)
|
||||
- React-Core/Default
|
||||
- React-cxxreact (= 0.66.1)
|
||||
- React-jsi (= 0.66.1)
|
||||
- React-jsiexecutor (= 0.66.1)
|
||||
- React-perflogger (= 0.66.1)
|
||||
- Yoga
|
||||
- React-Core/RCTSettingsHeaders (0.66.1):
|
||||
- glog
|
||||
- RCT-Folly (= 2021.06.28.00-v2)
|
||||
- React-Core/Default
|
||||
- React-cxxreact (= 0.66.1)
|
||||
- React-jsi (= 0.66.1)
|
||||
- React-jsiexecutor (= 0.66.1)
|
||||
- React-perflogger (= 0.66.1)
|
||||
- Yoga
|
||||
- React-Core/RCTTextHeaders (0.66.1):
|
||||
- glog
|
||||
- RCT-Folly (= 2021.06.28.00-v2)
|
||||
- React-Core/Default
|
||||
- React-cxxreact (= 0.66.1)
|
||||
- React-jsi (= 0.66.1)
|
||||
- React-jsiexecutor (= 0.66.1)
|
||||
- React-perflogger (= 0.66.1)
|
||||
- Yoga
|
||||
- React-Core/RCTVibrationHeaders (0.66.1):
|
||||
- glog
|
||||
- RCT-Folly (= 2021.06.28.00-v2)
|
||||
- React-Core/Default
|
||||
- React-cxxreact (= 0.66.1)
|
||||
- React-jsi (= 0.66.1)
|
||||
- React-jsiexecutor (= 0.66.1)
|
||||
- React-perflogger (= 0.66.1)
|
||||
- Yoga
|
||||
- React-Core/RCTWebSocket (0.66.1):
|
||||
- glog
|
||||
- RCT-Folly (= 2021.06.28.00-v2)
|
||||
- React-Core/Default (= 0.66.1)
|
||||
- React-cxxreact (= 0.66.1)
|
||||
- React-jsi (= 0.66.1)
|
||||
- React-jsiexecutor (= 0.66.1)
|
||||
- React-perflogger (= 0.66.1)
|
||||
- Yoga
|
||||
- React-CoreModules (0.66.1):
|
||||
- FBReactNativeSpec (= 0.66.1)
|
||||
- RCT-Folly (= 2021.06.28.00-v2)
|
||||
- RCTTypeSafety (= 0.66.1)
|
||||
- React-Core/CoreModulesHeaders (= 0.66.1)
|
||||
- React-jsi (= 0.66.1)
|
||||
- React-RCTImage (= 0.66.1)
|
||||
- ReactCommon/turbomodule/core (= 0.66.1)
|
||||
- React-cxxreact (0.66.1):
|
||||
- boost (= 1.76.0)
|
||||
- DoubleConversion
|
||||
- glog
|
||||
- RCT-Folly/Default (= 2020.01.13.00)
|
||||
- RCT-Folly/Default (2020.01.13.00):
|
||||
- boost-for-react-native
|
||||
- RCT-Folly (= 2021.06.28.00-v2)
|
||||
- React-callinvoker (= 0.66.1)
|
||||
- React-jsi (= 0.66.1)
|
||||
- React-jsinspector (= 0.66.1)
|
||||
- React-logger (= 0.66.1)
|
||||
- React-perflogger (= 0.66.1)
|
||||
- React-runtimeexecutor (= 0.66.1)
|
||||
- React-jsi (0.66.1):
|
||||
- boost (= 1.76.0)
|
||||
- DoubleConversion
|
||||
- glog
|
||||
- RCTRequired (0.64.2)
|
||||
- RCTTypeSafety (0.64.2):
|
||||
- FBLazyVector (= 0.64.2)
|
||||
- RCT-Folly (= 2020.01.13.00)
|
||||
- RCTRequired (= 0.64.2)
|
||||
- React-Core (= 0.64.2)
|
||||
- React (0.64.2):
|
||||
- React-Core (= 0.64.2)
|
||||
- React-Core/DevSupport (= 0.64.2)
|
||||
- React-Core/RCTWebSocket (= 0.64.2)
|
||||
- React-RCTActionSheet (= 0.64.2)
|
||||
- React-RCTAnimation (= 0.64.2)
|
||||
- React-RCTBlob (= 0.64.2)
|
||||
- React-RCTImage (= 0.64.2)
|
||||
- React-RCTLinking (= 0.64.2)
|
||||
- React-RCTNetwork (= 0.64.2)
|
||||
- React-RCTSettings (= 0.64.2)
|
||||
- React-RCTText (= 0.64.2)
|
||||
- React-RCTVibration (= 0.64.2)
|
||||
- React-callinvoker (0.64.2)
|
||||
- React-Core (0.64.2):
|
||||
- glog
|
||||
- RCT-Folly (= 2020.01.13.00)
|
||||
- React-Core/Default (= 0.64.2)
|
||||
- React-cxxreact (= 0.64.2)
|
||||
- React-jsi (= 0.64.2)
|
||||
- React-jsiexecutor (= 0.64.2)
|
||||
- React-perflogger (= 0.64.2)
|
||||
- Yoga
|
||||
- React-Core/CoreModulesHeaders (0.64.2):
|
||||
- glog
|
||||
- RCT-Folly (= 2020.01.13.00)
|
||||
- React-Core/Default
|
||||
- React-cxxreact (= 0.64.2)
|
||||
- React-jsi (= 0.64.2)
|
||||
- React-jsiexecutor (= 0.64.2)
|
||||
- React-perflogger (= 0.64.2)
|
||||
- Yoga
|
||||
- React-Core/Default (0.64.2):
|
||||
- glog
|
||||
- RCT-Folly (= 2020.01.13.00)
|
||||
- React-cxxreact (= 0.64.2)
|
||||
- React-jsi (= 0.64.2)
|
||||
- React-jsiexecutor (= 0.64.2)
|
||||
- React-perflogger (= 0.64.2)
|
||||
- Yoga
|
||||
- React-Core/DevSupport (0.64.2):
|
||||
- glog
|
||||
- RCT-Folly (= 2020.01.13.00)
|
||||
- React-Core/Default (= 0.64.2)
|
||||
- React-Core/RCTWebSocket (= 0.64.2)
|
||||
- React-cxxreact (= 0.64.2)
|
||||
- React-jsi (= 0.64.2)
|
||||
- React-jsiexecutor (= 0.64.2)
|
||||
- React-jsinspector (= 0.64.2)
|
||||
- React-perflogger (= 0.64.2)
|
||||
- Yoga
|
||||
- React-Core/RCTActionSheetHeaders (0.64.2):
|
||||
- glog
|
||||
- RCT-Folly (= 2020.01.13.00)
|
||||
- React-Core/Default
|
||||
- React-cxxreact (= 0.64.2)
|
||||
- React-jsi (= 0.64.2)
|
||||
- React-jsiexecutor (= 0.64.2)
|
||||
- React-perflogger (= 0.64.2)
|
||||
- Yoga
|
||||
- React-Core/RCTAnimationHeaders (0.64.2):
|
||||
- glog
|
||||
- RCT-Folly (= 2020.01.13.00)
|
||||
- React-Core/Default
|
||||
- React-cxxreact (= 0.64.2)
|
||||
- React-jsi (= 0.64.2)
|
||||
- React-jsiexecutor (= 0.64.2)
|
||||
- React-perflogger (= 0.64.2)
|
||||
- Yoga
|
||||
- React-Core/RCTBlobHeaders (0.64.2):
|
||||
- glog
|
||||
- RCT-Folly (= 2020.01.13.00)
|
||||
- React-Core/Default
|
||||
- React-cxxreact (= 0.64.2)
|
||||
- React-jsi (= 0.64.2)
|
||||
- React-jsiexecutor (= 0.64.2)
|
||||
- React-perflogger (= 0.64.2)
|
||||
- Yoga
|
||||
- React-Core/RCTImageHeaders (0.64.2):
|
||||
- glog
|
||||
- RCT-Folly (= 2020.01.13.00)
|
||||
- React-Core/Default
|
||||
- React-cxxreact (= 0.64.2)
|
||||
- React-jsi (= 0.64.2)
|
||||
- React-jsiexecutor (= 0.64.2)
|
||||
- React-perflogger (= 0.64.2)
|
||||
- Yoga
|
||||
- React-Core/RCTLinkingHeaders (0.64.2):
|
||||
- glog
|
||||
- RCT-Folly (= 2020.01.13.00)
|
||||
- React-Core/Default
|
||||
- React-cxxreact (= 0.64.2)
|
||||
- React-jsi (= 0.64.2)
|
||||
- React-jsiexecutor (= 0.64.2)
|
||||
- React-perflogger (= 0.64.2)
|
||||
- Yoga
|
||||
- React-Core/RCTNetworkHeaders (0.64.2):
|
||||
- glog
|
||||
- RCT-Folly (= 2020.01.13.00)
|
||||
- React-Core/Default
|
||||
- React-cxxreact (= 0.64.2)
|
||||
- React-jsi (= 0.64.2)
|
||||
- React-jsiexecutor (= 0.64.2)
|
||||
- React-perflogger (= 0.64.2)
|
||||
- Yoga
|
||||
- React-Core/RCTSettingsHeaders (0.64.2):
|
||||
- glog
|
||||
- RCT-Folly (= 2020.01.13.00)
|
||||
- React-Core/Default
|
||||
- React-cxxreact (= 0.64.2)
|
||||
- React-jsi (= 0.64.2)
|
||||
- React-jsiexecutor (= 0.64.2)
|
||||
- React-perflogger (= 0.64.2)
|
||||
- Yoga
|
||||
- React-Core/RCTTextHeaders (0.64.2):
|
||||
- glog
|
||||
- RCT-Folly (= 2020.01.13.00)
|
||||
- React-Core/Default
|
||||
- React-cxxreact (= 0.64.2)
|
||||
- React-jsi (= 0.64.2)
|
||||
- React-jsiexecutor (= 0.64.2)
|
||||
- React-perflogger (= 0.64.2)
|
||||
- Yoga
|
||||
- React-Core/RCTVibrationHeaders (0.64.2):
|
||||
- glog
|
||||
- RCT-Folly (= 2020.01.13.00)
|
||||
- React-Core/Default
|
||||
- React-cxxreact (= 0.64.2)
|
||||
- React-jsi (= 0.64.2)
|
||||
- React-jsiexecutor (= 0.64.2)
|
||||
- React-perflogger (= 0.64.2)
|
||||
- Yoga
|
||||
- React-Core/RCTWebSocket (0.64.2):
|
||||
- glog
|
||||
- RCT-Folly (= 2020.01.13.00)
|
||||
- React-Core/Default (= 0.64.2)
|
||||
- React-cxxreact (= 0.64.2)
|
||||
- React-jsi (= 0.64.2)
|
||||
- React-jsiexecutor (= 0.64.2)
|
||||
- React-perflogger (= 0.64.2)
|
||||
- Yoga
|
||||
- React-CoreModules (0.64.2):
|
||||
- FBReactNativeSpec (= 0.64.2)
|
||||
- RCT-Folly (= 2020.01.13.00)
|
||||
- RCTTypeSafety (= 0.64.2)
|
||||
- React-Core/CoreModulesHeaders (= 0.64.2)
|
||||
- React-jsi (= 0.64.2)
|
||||
- React-RCTImage (= 0.64.2)
|
||||
- ReactCommon/turbomodule/core (= 0.64.2)
|
||||
- React-cxxreact (0.64.2):
|
||||
- boost-for-react-native (= 1.63.0)
|
||||
- RCT-Folly (= 2021.06.28.00-v2)
|
||||
- React-jsi/Default (= 0.66.1)
|
||||
- React-jsi/Default (0.66.1):
|
||||
- boost (= 1.76.0)
|
||||
- DoubleConversion
|
||||
- glog
|
||||
- RCT-Folly (= 2020.01.13.00)
|
||||
- React-callinvoker (= 0.64.2)
|
||||
- React-jsi (= 0.64.2)
|
||||
- React-jsinspector (= 0.64.2)
|
||||
- React-perflogger (= 0.64.2)
|
||||
- React-runtimeexecutor (= 0.64.2)
|
||||
- React-jsi (0.64.2):
|
||||
- boost-for-react-native (= 1.63.0)
|
||||
- RCT-Folly (= 2021.06.28.00-v2)
|
||||
- React-jsiexecutor (0.66.1):
|
||||
- DoubleConversion
|
||||
- glog
|
||||
- RCT-Folly (= 2020.01.13.00)
|
||||
- React-jsi/Default (= 0.64.2)
|
||||
- React-jsi/Default (0.64.2):
|
||||
- boost-for-react-native (= 1.63.0)
|
||||
- DoubleConversion
|
||||
- RCT-Folly (= 2021.06.28.00-v2)
|
||||
- React-cxxreact (= 0.66.1)
|
||||
- React-jsi (= 0.66.1)
|
||||
- React-perflogger (= 0.66.1)
|
||||
- React-jsinspector (0.66.1)
|
||||
- React-logger (0.66.1):
|
||||
- glog
|
||||
- RCT-Folly (= 2020.01.13.00)
|
||||
- React-jsiexecutor (0.64.2):
|
||||
- DoubleConversion
|
||||
- glog
|
||||
- RCT-Folly (= 2020.01.13.00)
|
||||
- React-cxxreact (= 0.64.2)
|
||||
- React-jsi (= 0.64.2)
|
||||
- React-perflogger (= 0.64.2)
|
||||
- React-jsinspector (0.64.2)
|
||||
- react-native-alarm-notification (1.0.3):
|
||||
- React
|
||||
- react-native-camera (3.40.0):
|
||||
@@ -237,70 +243,71 @@ PODS:
|
||||
- React-Core
|
||||
- react-native-webview (10.9.2):
|
||||
- React-Core
|
||||
- React-perflogger (0.64.2)
|
||||
- React-RCTActionSheet (0.64.2):
|
||||
- React-Core/RCTActionSheetHeaders (= 0.64.2)
|
||||
- React-RCTAnimation (0.64.2):
|
||||
- FBReactNativeSpec (= 0.64.2)
|
||||
- RCT-Folly (= 2020.01.13.00)
|
||||
- RCTTypeSafety (= 0.64.2)
|
||||
- React-Core/RCTAnimationHeaders (= 0.64.2)
|
||||
- React-jsi (= 0.64.2)
|
||||
- ReactCommon/turbomodule/core (= 0.64.2)
|
||||
- React-RCTBlob (0.64.2):
|
||||
- FBReactNativeSpec (= 0.64.2)
|
||||
- RCT-Folly (= 2020.01.13.00)
|
||||
- React-Core/RCTBlobHeaders (= 0.64.2)
|
||||
- React-Core/RCTWebSocket (= 0.64.2)
|
||||
- React-jsi (= 0.64.2)
|
||||
- React-RCTNetwork (= 0.64.2)
|
||||
- ReactCommon/turbomodule/core (= 0.64.2)
|
||||
- React-RCTImage (0.64.2):
|
||||
- FBReactNativeSpec (= 0.64.2)
|
||||
- RCT-Folly (= 2020.01.13.00)
|
||||
- RCTTypeSafety (= 0.64.2)
|
||||
- React-Core/RCTImageHeaders (= 0.64.2)
|
||||
- React-jsi (= 0.64.2)
|
||||
- React-RCTNetwork (= 0.64.2)
|
||||
- ReactCommon/turbomodule/core (= 0.64.2)
|
||||
- React-RCTLinking (0.64.2):
|
||||
- FBReactNativeSpec (= 0.64.2)
|
||||
- React-Core/RCTLinkingHeaders (= 0.64.2)
|
||||
- React-jsi (= 0.64.2)
|
||||
- ReactCommon/turbomodule/core (= 0.64.2)
|
||||
- React-RCTNetwork (0.64.2):
|
||||
- FBReactNativeSpec (= 0.64.2)
|
||||
- RCT-Folly (= 2020.01.13.00)
|
||||
- RCTTypeSafety (= 0.64.2)
|
||||
- React-Core/RCTNetworkHeaders (= 0.64.2)
|
||||
- React-jsi (= 0.64.2)
|
||||
- ReactCommon/turbomodule/core (= 0.64.2)
|
||||
- React-RCTSettings (0.64.2):
|
||||
- FBReactNativeSpec (= 0.64.2)
|
||||
- RCT-Folly (= 2020.01.13.00)
|
||||
- RCTTypeSafety (= 0.64.2)
|
||||
- React-Core/RCTSettingsHeaders (= 0.64.2)
|
||||
- React-jsi (= 0.64.2)
|
||||
- ReactCommon/turbomodule/core (= 0.64.2)
|
||||
- React-RCTText (0.64.2):
|
||||
- React-Core/RCTTextHeaders (= 0.64.2)
|
||||
- React-RCTVibration (0.64.2):
|
||||
- FBReactNativeSpec (= 0.64.2)
|
||||
- RCT-Folly (= 2020.01.13.00)
|
||||
- React-Core/RCTVibrationHeaders (= 0.64.2)
|
||||
- React-jsi (= 0.64.2)
|
||||
- ReactCommon/turbomodule/core (= 0.64.2)
|
||||
- React-runtimeexecutor (0.64.2):
|
||||
- React-jsi (= 0.64.2)
|
||||
- ReactCommon/turbomodule/core (0.64.2):
|
||||
- React-perflogger (0.66.1)
|
||||
- React-RCTActionSheet (0.66.1):
|
||||
- React-Core/RCTActionSheetHeaders (= 0.66.1)
|
||||
- React-RCTAnimation (0.66.1):
|
||||
- FBReactNativeSpec (= 0.66.1)
|
||||
- RCT-Folly (= 2021.06.28.00-v2)
|
||||
- RCTTypeSafety (= 0.66.1)
|
||||
- React-Core/RCTAnimationHeaders (= 0.66.1)
|
||||
- React-jsi (= 0.66.1)
|
||||
- ReactCommon/turbomodule/core (= 0.66.1)
|
||||
- React-RCTBlob (0.66.1):
|
||||
- FBReactNativeSpec (= 0.66.1)
|
||||
- RCT-Folly (= 2021.06.28.00-v2)
|
||||
- React-Core/RCTBlobHeaders (= 0.66.1)
|
||||
- React-Core/RCTWebSocket (= 0.66.1)
|
||||
- React-jsi (= 0.66.1)
|
||||
- React-RCTNetwork (= 0.66.1)
|
||||
- ReactCommon/turbomodule/core (= 0.66.1)
|
||||
- React-RCTImage (0.66.1):
|
||||
- FBReactNativeSpec (= 0.66.1)
|
||||
- RCT-Folly (= 2021.06.28.00-v2)
|
||||
- RCTTypeSafety (= 0.66.1)
|
||||
- React-Core/RCTImageHeaders (= 0.66.1)
|
||||
- React-jsi (= 0.66.1)
|
||||
- React-RCTNetwork (= 0.66.1)
|
||||
- ReactCommon/turbomodule/core (= 0.66.1)
|
||||
- React-RCTLinking (0.66.1):
|
||||
- FBReactNativeSpec (= 0.66.1)
|
||||
- React-Core/RCTLinkingHeaders (= 0.66.1)
|
||||
- React-jsi (= 0.66.1)
|
||||
- ReactCommon/turbomodule/core (= 0.66.1)
|
||||
- React-RCTNetwork (0.66.1):
|
||||
- FBReactNativeSpec (= 0.66.1)
|
||||
- RCT-Folly (= 2021.06.28.00-v2)
|
||||
- RCTTypeSafety (= 0.66.1)
|
||||
- React-Core/RCTNetworkHeaders (= 0.66.1)
|
||||
- React-jsi (= 0.66.1)
|
||||
- ReactCommon/turbomodule/core (= 0.66.1)
|
||||
- React-RCTSettings (0.66.1):
|
||||
- FBReactNativeSpec (= 0.66.1)
|
||||
- RCT-Folly (= 2021.06.28.00-v2)
|
||||
- RCTTypeSafety (= 0.66.1)
|
||||
- React-Core/RCTSettingsHeaders (= 0.66.1)
|
||||
- React-jsi (= 0.66.1)
|
||||
- ReactCommon/turbomodule/core (= 0.66.1)
|
||||
- React-RCTText (0.66.1):
|
||||
- React-Core/RCTTextHeaders (= 0.66.1)
|
||||
- React-RCTVibration (0.66.1):
|
||||
- FBReactNativeSpec (= 0.66.1)
|
||||
- RCT-Folly (= 2021.06.28.00-v2)
|
||||
- React-Core/RCTVibrationHeaders (= 0.66.1)
|
||||
- React-jsi (= 0.66.1)
|
||||
- ReactCommon/turbomodule/core (= 0.66.1)
|
||||
- React-runtimeexecutor (0.66.1):
|
||||
- React-jsi (= 0.66.1)
|
||||
- ReactCommon/turbomodule/core (0.66.1):
|
||||
- DoubleConversion
|
||||
- glog
|
||||
- RCT-Folly (= 2020.01.13.00)
|
||||
- React-callinvoker (= 0.64.2)
|
||||
- React-Core (= 0.64.2)
|
||||
- React-cxxreact (= 0.64.2)
|
||||
- React-jsi (= 0.64.2)
|
||||
- React-perflogger (= 0.64.2)
|
||||
- RCT-Folly (= 2021.06.28.00-v2)
|
||||
- React-callinvoker (= 0.66.1)
|
||||
- React-Core (= 0.66.1)
|
||||
- React-cxxreact (= 0.66.1)
|
||||
- React-jsi (= 0.66.1)
|
||||
- React-logger (= 0.66.1)
|
||||
- React-perflogger (= 0.66.1)
|
||||
- rn-fetch-blob (0.12.0):
|
||||
- React-Core
|
||||
- RNCClipboard (1.5.0):
|
||||
@@ -324,6 +331,7 @@ PODS:
|
||||
- Yoga (1.14.0)
|
||||
|
||||
DEPENDENCIES:
|
||||
- boost (from `../node_modules/react-native/third-party-podspecs/boost.podspec`)
|
||||
- DoubleConversion (from `../node_modules/react-native/third-party-podspecs/DoubleConversion.podspec`)
|
||||
- FBLazyVector (from `../node_modules/react-native/Libraries/FBLazyVector`)
|
||||
- FBReactNativeSpec (from `../node_modules/react-native/React/FBReactNativeSpec`)
|
||||
@@ -343,6 +351,7 @@ DEPENDENCIES:
|
||||
- React-jsi (from `../node_modules/react-native/ReactCommon/jsi`)
|
||||
- React-jsiexecutor (from `../node_modules/react-native/ReactCommon/jsiexecutor`)
|
||||
- React-jsinspector (from `../node_modules/react-native/ReactCommon/jsinspector`)
|
||||
- React-logger (from `../node_modules/react-native/ReactCommon/logger`)
|
||||
- react-native-alarm-notification (from `../node_modules/joplin-rn-alarm-notification`)
|
||||
- react-native-camera (from `../node_modules/react-native-camera`)
|
||||
- react-native-document-picker (from `../node_modules/react-native-document-picker`)
|
||||
@@ -381,9 +390,11 @@ DEPENDENCIES:
|
||||
|
||||
SPEC REPOS:
|
||||
trunk:
|
||||
- boost-for-react-native
|
||||
- fmt
|
||||
|
||||
EXTERNAL SOURCES:
|
||||
boost:
|
||||
:podspec: "../node_modules/react-native/third-party-podspecs/boost.podspec"
|
||||
DoubleConversion:
|
||||
:podspec: "../node_modules/react-native/third-party-podspecs/DoubleConversion.podspec"
|
||||
FBLazyVector:
|
||||
@@ -418,6 +429,8 @@ EXTERNAL SOURCES:
|
||||
:path: "../node_modules/react-native/ReactCommon/jsiexecutor"
|
||||
React-jsinspector:
|
||||
:path: "../node_modules/react-native/ReactCommon/jsinspector"
|
||||
React-logger:
|
||||
:path: "../node_modules/react-native/ReactCommon/logger"
|
||||
react-native-alarm-notification:
|
||||
:path: "../node_modules/joplin-rn-alarm-notification"
|
||||
react-native-camera:
|
||||
@@ -490,24 +503,26 @@ EXTERNAL SOURCES:
|
||||
:path: "../node_modules/react-native/ReactCommon/yoga"
|
||||
|
||||
SPEC CHECKSUMS:
|
||||
boost-for-react-native: 39c7adb57c4e60d6c5479dd8623128eb5b3f0f2c
|
||||
DoubleConversion: cf9b38bf0b2d048436d9a82ad2abe1404f11e7de
|
||||
FBLazyVector: e686045572151edef46010a6f819ade377dfeb4b
|
||||
FBReactNativeSpec: 009b310a5134a345e702b4402de70b5ee2bb4832
|
||||
glog: 73c2498ac6884b13ede40eda8228cb1eee9d9d62
|
||||
boost: a7c83b31436843459a1961bfd74b96033dc77234
|
||||
DoubleConversion: 831926d9b8bf8166fd87886c4abab286c2422662
|
||||
FBLazyVector: 500821d196c3d1bd10e7e828bc93ce075234080f
|
||||
FBReactNativeSpec: 74c869e2cffa2ffec685cd1bac6788c021da6005
|
||||
fmt: ff9d55029c625d3757ed641535fd4a75fedc7ce9
|
||||
glog: 5337263514dd6f09803962437687240c5dc39aa4
|
||||
JoplinCommonShareExtension: 270b4f8eb4e22828eeda433a04ed689fc1fd09b5
|
||||
JoplinRNShareExtension: 7137e9787374e1b0797ecbef9103d1588d90e403
|
||||
RCT-Folly: ec7a233ccc97cc556cf7237f0db1ff65b986f27c
|
||||
RCTRequired: 6d3e854f0e7260a648badd0d44fc364bc9da9728
|
||||
RCTTypeSafety: c1f31d19349c6b53085766359caac425926fafaa
|
||||
React: bda6b6d7ae912de97d7a61aa5c160db24aa2ad69
|
||||
React-callinvoker: 9840ea7e8e88ed73d438edb725574820b29b5baa
|
||||
React-Core: b5e385da7ce5f16a220fc60fd0749eae2c6120f0
|
||||
React-CoreModules: 17071a4e2c5239b01585f4aa8070141168ab298f
|
||||
React-cxxreact: 9be7b6340ed9f7c53e53deca7779f07cd66525ba
|
||||
React-jsi: 67747b9722f6dab2ffe15b011bcf6b3f2c3f1427
|
||||
React-jsiexecutor: 80c46bd381fd06e418e0d4f53672dc1d1945c4c3
|
||||
React-jsinspector: cc614ec18a9ca96fd275100c16d74d62ee11f0ae
|
||||
JoplinRNShareExtension: cb790ce4c0692367acd1a06c56330c9a440f8b58
|
||||
RCT-Folly: a21c126816d8025b547704b777a2ba552f3d9fa9
|
||||
RCTRequired: 3cc065b52aa18db729268b9bd78a2feffb4d0f91
|
||||
RCTTypeSafety: 3c4fc37d5dea452d2ef17324db5504ec2f05083a
|
||||
React: 4a00720816c52a213424442954acb7e4b724804a
|
||||
React-callinvoker: 911fc6570538f3bb5c61edf9dc907c1beb4355bf
|
||||
React-Core: e134d3a5d7b2a1a731589be776e20dbb14868f27
|
||||
React-CoreModules: 2f8588b2aa47e7fef27125c8eaaabda963b3ac62
|
||||
React-cxxreact: 8f1382538cad0cc8b8eafca6d66268828e353bea
|
||||
React-jsi: 9fe1854d2c0486216acebd5db3c38b4ccb23ca0b
|
||||
React-jsiexecutor: db2f6e22a534d466fc0e34e622df47d9d20bab2f
|
||||
React-jsinspector: 8c0517dee5e8c70cd6c3066f20213ff7ce54f176
|
||||
React-logger: bfddd3418dc1d45b77b822958f3e31422e2c179b
|
||||
react-native-alarm-notification: 466e4ad56fbd948ecac26e657f292dca8bf483d5
|
||||
react-native-camera: 35854c4f764a4a6cf61c1c3525888b92f0fe4b31
|
||||
react-native-document-picker: 0bba80cc56caab1f67dbaa81ff557e3a9b7f2b9f
|
||||
@@ -520,18 +535,18 @@ SPEC CHECKSUMS:
|
||||
react-native-sqlite-storage: 418ef4afc5e6df6ce3574c4617e5f0b65cffde55
|
||||
react-native-version-info: 36490da17d2c6b5cc21321c70e433784dee7ed0b
|
||||
react-native-webview: 4e96d493f9f90ba4f03b28933f30b2964df07e39
|
||||
React-perflogger: 25373e382fed75ce768a443822f07098a15ab737
|
||||
React-RCTActionSheet: af7796ba49ffe4ca92e7277a5d992d37203f7da5
|
||||
React-RCTAnimation: 6a2e76ab50c6f25b428d81b76a5a45351c4d77aa
|
||||
React-RCTBlob: 02a2887023e0eed99391b6445b2e23a2a6f9226d
|
||||
React-RCTImage: ce5bf8e7438f2286d9b646a05d6ab11f38b0323d
|
||||
React-RCTLinking: ccd20742de14e020cb5f99d5c7e0bf0383aefbd9
|
||||
React-RCTNetwork: dfb9d089ab0753e5e5f55fc4b1210858f7245647
|
||||
React-RCTSettings: b14aef2d83699e48b410fb7c3ba5b66cd3291ae2
|
||||
React-RCTText: 41a2e952dd9adc5caf6fb68ed46b275194d5da5f
|
||||
React-RCTVibration: 24600e3b1aaa77126989bc58b6747509a1ba14f3
|
||||
React-runtimeexecutor: a9904c6d0218fb9f8b19d6dd88607225927668f9
|
||||
ReactCommon: 149906e01aa51142707a10665185db879898e966
|
||||
React-perflogger: fcac6090a80e3d967791b4c7f1b1a017f9d4a398
|
||||
React-RCTActionSheet: caf5913d9f9e605f5467206cf9d1caa6d47d7ad6
|
||||
React-RCTAnimation: 6539e3bf594f6a529cd861985ba6548286ae1ead
|
||||
React-RCTBlob: 6e2e999d28b15fd03ed533f164ce33e0fcde571a
|
||||
React-RCTImage: c6bbb10eedb6b840c4474f2108b864173b83de15
|
||||
React-RCTLinking: 8fda9bb8fdb104e78110a903a9a77754318c7d11
|
||||
React-RCTNetwork: 2b26daad93830501cf14aab03eac04e304f942d3
|
||||
React-RCTSettings: 89c0dcee7adb706c749383596f57c1e882a27843
|
||||
React-RCTText: 71734fce8e6cb854daeb4a5eec182c303ea58473
|
||||
React-RCTVibration: 6600b5eed7c0fda4a433fa1198d1cb2690151791
|
||||
React-runtimeexecutor: 33a949a51bec5f8a3c9e8d8092deb259600d761e
|
||||
ReactCommon: 620442811dc6f707b4bf5e3b27d4f19c12d5a821
|
||||
rn-fetch-blob: f065bb7ab7fb48dd002629f8bdcb0336602d3cba
|
||||
RNCClipboard: c7abea1baea58adca5c1f29e56dd5261837b4892
|
||||
RNCPushNotificationIOS: ec7ffe65c7b5097f8d287fd627e1c1674ea69cef
|
||||
@@ -542,8 +557,8 @@ SPEC CHECKSUMS:
|
||||
RNSecureRandom: 1f19ad1492f7ed416b8fc79e92216a1f73f13a4c
|
||||
RNShare: 9cdd23357981cf4dee275eb79239e860dccc0faf
|
||||
RNVectorIcons: bc69e6a278b14842063605de32bec61f0b251a59
|
||||
Yoga: 575c581c63e0d35c9a83f4b46d01d63abc1100ac
|
||||
Yoga: 2b4a01651f42a32f82e6cef3830a3ba48088237f
|
||||
|
||||
PODFILE CHECKSUM: 6aeb91c381b1a03c0c8c78b2f504699bd569b7e3
|
||||
PODFILE CHECKSUM: 9f8b595b05d63f54759fc6d9b1d2c5838fff9626
|
||||
|
||||
COCOAPODS: 1.10.2
|
||||
@@ -9,6 +9,6 @@ Pod::Spec.new do |spec|
|
||||
spec.platform = :ios, "9.0"
|
||||
spec.source = { :path => "." }
|
||||
spec.source_files = "Source/RNShareExtension/**/*.{h,m}"
|
||||
spec.dependency "React", "0.64.2"
|
||||
spec.dependency "React", "0.66.1"
|
||||
spec.dependency "JoplinCommonShareExtension"
|
||||
end
|
||||
Generated
+1485
-8637
File diff suppressed because it is too large.
Load diff
@@ -33,8 +33,8 @@
|
||||
"md5": "^2.2.1",
|
||||
"prop-types": "^15.6.0",
|
||||
"punycode": "^2.1.1",
|
||||
"react": "17.0.1",
|
||||
"react-native": "0.64.2",
|
||||
"react": "17.0.2",
|
||||
"react-native": "0.66.1",
|
||||
"react-native-action-button": "^2.8.5",
|
||||
"react-native-camera": "^3.40.0",
|
||||
"react-native-dialogbox": "^0.6.10",
|
||||
@@ -84,7 +84,7 @@
|
||||
"fs-extra": "^8.1.0",
|
||||
"gulp": "^4.0.2",
|
||||
"jetifier": "^1.6.5",
|
||||
"metro-react-native-babel-preset": "^0.64.0",
|
||||
"metro-react-native-babel-preset": "^0.66.2",
|
||||
"nodemon": "^2.0.12",
|
||||
"rollup": "^2.53.1",
|
||||
"typescript": "^4.0.5",
|
||||
|
||||
@@ -505,9 +505,9 @@ async function initialize(dispatch: Function) {
|
||||
Setting.setValue('sync.10.path', 'http://api.joplincloud.local:22300');
|
||||
Setting.setValue('sync.10.userContentPath', 'http://joplinusercontent.local:22300');
|
||||
|
||||
Setting.setValue('sync.target', 10);
|
||||
Setting.setValue('sync.10.username', 'user1@example.com');
|
||||
Setting.setValue('sync.10.password', 'hunter1hunter2hunter3');
|
||||
// Setting.setValue('sync.target', 10);
|
||||
// Setting.setValue('sync.10.username', 'user1@example.com');
|
||||
// Setting.setValue('sync.10.password', 'hunter1hunter2hunter3');
|
||||
}
|
||||
|
||||
if (Setting.value('db.ftsEnabled') === -1) {
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@joplin/fork-htmlparser2",
|
||||
"version": "4.1.36",
|
||||
"version": "4.1.38",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@joplin/fork-htmlparser2",
|
||||
"description": "Fast & forgiving HTML/XML/RSS parser",
|
||||
"version": "4.1.36",
|
||||
"version": "4.1.38",
|
||||
"author": "Felix Boehm <me@feedic.com>",
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
|
||||
Generated
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@joplin/fork-sax",
|
||||
"version": "1.2.40",
|
||||
"version": "1.2.42",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"name": "@joplin/fork-sax",
|
||||
"description": "An evented streaming XML parser in JavaScript",
|
||||
"author": "Isaac Z. Schlueter <i@izs.me> (http://blog.izs.me/)",
|
||||
"version": "1.2.40",
|
||||
"version": "1.2.42",
|
||||
"main": "lib/sax.js",
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"manifest_version": 1,
|
||||
"id": "<%= pluginId %>",
|
||||
"app_min_version": "2.5",
|
||||
"app_min_version": "2.6",
|
||||
"version": "1.0.0",
|
||||
"name": "<%= pluginName %>",
|
||||
"description": "<%= pluginDescription %>",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "generator-joplin",
|
||||
"version": "2.5.0",
|
||||
"version": "2.6.0",
|
||||
"description": "Scaffolds out a new Joplin plugin",
|
||||
"homepage": "https://github.com/laurent22/joplin/tree/dev/packages/generator-joplin",
|
||||
"author": {
|
||||
|
||||
@@ -142,7 +142,7 @@ export default class JoplinServerApi {
|
||||
}
|
||||
|
||||
if (sessionId) headers['X-API-AUTH'] = sessionId;
|
||||
headers['X-API-MIN-VERSION'] = '2.6.0'; // Need server 2.6 for new lock support
|
||||
headers['X-API-MIN-VERSION'] = '2.1.4';
|
||||
|
||||
const fetchOptions: any = {};
|
||||
fetchOptions.headers = headers;
|
||||
@@ -253,12 +253,8 @@ export default class JoplinServerApi {
|
||||
const output = await loadResponseJson();
|
||||
return output;
|
||||
} catch (error) {
|
||||
// Don't print error info for file not found (handled by the
|
||||
// driver), or lock-acquisition errors because it's handled by
|
||||
// LockHandler.
|
||||
if (![404, 'hasExclusiveLock', 'hasSyncLock'].includes(error.code)) {
|
||||
if (error.code !== 404) {
|
||||
logger.warn(this.requestToCurl_(url, fetchOptions));
|
||||
logger.warn('Code:', error.code);
|
||||
logger.warn(error);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import Logger from './Logger';
|
||||
import LockHandler, { hasActiveLock, LockClientType, LockType } from './services/synchronizer/LockHandler';
|
||||
import Setting, { AppType } from './models/Setting';
|
||||
import LockHandler, { LockType } from './services/synchronizer/LockHandler';
|
||||
import Setting from './models/Setting';
|
||||
import shim from './shim';
|
||||
import MigrationHandler from './services/synchronizer/MigrationHandler';
|
||||
import eventManager from './eventManager';
|
||||
@@ -66,7 +66,6 @@ export default class Synchronizer {
|
||||
private resourceService_: ResourceService = null;
|
||||
private syncTargetIsLocked_: boolean = false;
|
||||
private shareService_: ShareService = null;
|
||||
private lockClientType_: LockClientType = null;
|
||||
|
||||
// Debug flags are used to test certain hard-to-test conditions
|
||||
// such as cancelling in the middle of a loop.
|
||||
@@ -121,21 +120,9 @@ export default class Synchronizer {
|
||||
return this.lockHandler_;
|
||||
}
|
||||
|
||||
private lockClientType(): LockClientType {
|
||||
if (this.lockClientType_) return this.lockClientType_;
|
||||
|
||||
if (this.appType_ === AppType.Desktop) this.lockClientType_ = LockClientType.Desktop;
|
||||
if (this.appType_ === AppType.Mobile) this.lockClientType_ = LockClientType.Mobile;
|
||||
if (this.appType_ === AppType.Cli) this.lockClientType_ = LockClientType.Cli;
|
||||
|
||||
if (!this.lockClientType_) throw new Error(`Invalid client type: ${this.appType_}`);
|
||||
|
||||
return this.lockClientType_;
|
||||
}
|
||||
|
||||
migrationHandler() {
|
||||
if (this.migrationHandler_) return this.migrationHandler_;
|
||||
this.migrationHandler_ = new MigrationHandler(this.api(), this.db(), this.lockHandler(), this.lockClientType(), this.clientId_);
|
||||
this.migrationHandler_ = new MigrationHandler(this.api(), this.db(), this.lockHandler(), this.appType_, this.clientId_);
|
||||
return this.migrationHandler_;
|
||||
}
|
||||
|
||||
@@ -177,12 +164,6 @@ export default class Synchronizer {
|
||||
return !!report && !!report.errors && !!report.errors.length;
|
||||
}
|
||||
|
||||
private static completionTime(report: any): string {
|
||||
const duration = report.completedTime - report.startTime;
|
||||
if (duration > 1000) return `${Math.round(duration / 1000)}s`;
|
||||
return `${duration}ms`;
|
||||
}
|
||||
|
||||
static reportToLines(report: any) {
|
||||
const lines = [];
|
||||
if (report.createLocal) lines.push(_('Created local items: %d.', report.createLocal));
|
||||
@@ -193,7 +174,7 @@ export default class Synchronizer {
|
||||
if (report.deleteRemote) lines.push(_('Deleted remote items: %d.', report.deleteRemote));
|
||||
if (report.fetchingTotal && report.fetchingProcessed) lines.push(_('Fetched items: %d/%d.', report.fetchingProcessed, report.fetchingTotal));
|
||||
if (report.cancelling && !report.completedTime) lines.push(_('Cancelling...'));
|
||||
if (report.completedTime) lines.push(_('Completed: %s (%s)', time.formatMsToLocal(report.completedTime), this.completionTime(report)));
|
||||
if (report.completedTime) lines.push(_('Completed: %s (%s)', time.formatMsToLocal(report.completedTime), `${Math.round((report.completedTime - report.startTime) / 1000)}s`));
|
||||
if (this.reportHasErrors(report)) lines.push(_('Last error: %s', report.errors[report.errors.length - 1].toString().substr(0, 500)));
|
||||
|
||||
return lines;
|
||||
@@ -317,13 +298,10 @@ export default class Synchronizer {
|
||||
}
|
||||
|
||||
async lockErrorStatus_() {
|
||||
const locks = await this.lockHandler().locks();
|
||||
const currentDate = await this.lockHandler().currentDate();
|
||||
|
||||
const hasActiveExclusiveLock = await hasActiveLock(locks, currentDate, this.lockHandler().lockTtl, LockType.Exclusive);
|
||||
const hasActiveExclusiveLock = await this.lockHandler().hasActiveLock(LockType.Exclusive);
|
||||
if (hasActiveExclusiveLock) return 'hasExclusiveLock';
|
||||
|
||||
const hasActiveSyncLock = await hasActiveLock(locks, currentDate, this.lockHandler().lockTtl, LockType.Sync, this.lockClientType(), this.clientId_);
|
||||
const hasActiveSyncLock = await this.lockHandler().hasActiveLock(LockType.Sync, this.appType_, this.clientId_);
|
||||
if (!hasActiveSyncLock) return 'syncLockGone';
|
||||
|
||||
return '';
|
||||
@@ -468,10 +446,10 @@ export default class Synchronizer {
|
||||
const previousE2EE = localInfo.e2ee;
|
||||
logger.info('Sync target info differs between local and remote - merging infos: ', newInfo.toObject());
|
||||
|
||||
await this.lockHandler().acquireLock(LockType.Exclusive, this.lockClientType(), this.clientId_, { clearExistingSyncLocksFromTheSameClient: true });
|
||||
await this.lockHandler().acquireLock(LockType.Exclusive, this.appType_, this.clientId_, { clearExistingSyncLocksFromTheSameClient: true });
|
||||
await uploadSyncInfo(this.api(), newInfo);
|
||||
await saveLocalSyncInfo(newInfo);
|
||||
await this.lockHandler().releaseLock(LockType.Exclusive, this.lockClientType(), this.clientId_);
|
||||
await this.lockHandler().releaseLock(LockType.Exclusive, this.appType_, this.clientId_);
|
||||
|
||||
// console.info('NEW', newInfo);
|
||||
|
||||
@@ -495,7 +473,7 @@ export default class Synchronizer {
|
||||
throw error;
|
||||
}
|
||||
|
||||
syncLock = await this.lockHandler().acquireLock(LockType.Sync, this.lockClientType(), this.clientId_);
|
||||
syncLock = await this.lockHandler().acquireLock(LockType.Sync, this.appType_, this.clientId_);
|
||||
|
||||
this.lockHandler().startAutoLockRefresh(syncLock, (error: any) => {
|
||||
logger.warn('Could not refresh lock - cancelling sync. Error was:', error);
|
||||
@@ -1106,7 +1084,7 @@ export default class Synchronizer {
|
||||
|
||||
if (syncLock) {
|
||||
this.lockHandler().stopAutoLockRefresh(syncLock);
|
||||
await this.lockHandler().releaseLock(LockType.Sync, this.lockClientType(), this.clientId_);
|
||||
await this.lockHandler().releaseLock(LockType.Sync, this.appType_, this.clientId_);
|
||||
}
|
||||
|
||||
this.syncTargetIsLocked_ = false;
|
||||
|
||||
@@ -2,7 +2,6 @@ import { MultiPutItem } from './file-api';
|
||||
import JoplinError from './JoplinError';
|
||||
import JoplinServerApi from './JoplinServerApi';
|
||||
import { trimSlashes } from './path-utils';
|
||||
import { Lock, LockClientType, LockType } from './services/synchronizer/LockHandler';
|
||||
|
||||
// All input paths should be in the format: "path/to/file". This is converted to
|
||||
// "root:/path/to/file:" when doing the API call.
|
||||
@@ -41,10 +40,6 @@ export default class FileApiDriverJoplinServer {
|
||||
return true;
|
||||
}
|
||||
|
||||
public get supportsLocks() {
|
||||
return true;
|
||||
}
|
||||
|
||||
public requestRepeatCount() {
|
||||
return 3;
|
||||
}
|
||||
@@ -201,50 +196,6 @@ export default class FileApiDriverJoplinServer {
|
||||
throw new Error('Not supported');
|
||||
}
|
||||
|
||||
// private lockClientTypeToId(clientType:AppType):number {
|
||||
// if (clientType === AppType.Desktop) return 1;
|
||||
// if (clientType === AppType.Mobile) return 2;
|
||||
// if (clientType === AppType.Cli) return 3;
|
||||
// throw new Error('Invalid client type: ' + clientType);
|
||||
// }
|
||||
|
||||
// private lockTypeToId(lockType:LockType):number {
|
||||
// if (lockType === LockType.None) return 0; // probably not possible?
|
||||
// if (lockType === LockType.Sync) return 1;
|
||||
// if (lockType === LockType.Exclusive) return 2;
|
||||
// throw new Error('Invalid lock type: ' + lockType);
|
||||
// }
|
||||
|
||||
// private lockClientIdTypeToType(clientType:number):AppType {
|
||||
// if (clientType === 1) return AppType.Desktop;
|
||||
// if (clientType === 2) return AppType.Mobile;
|
||||
// if (clientType === 3) return AppType.Cli;
|
||||
// throw new Error('Invalid client type: ' + clientType);
|
||||
// }
|
||||
|
||||
// private lockIdToType(lockType:number):LockType {
|
||||
// if (lockType === 0) return LockType.None; // probably not possible?
|
||||
// if (lockType === 1) return LockType.Sync;
|
||||
// if (lockType === 2) return LockType.Exclusive;
|
||||
// throw new Error('Invalid lock type: ' + lockType);
|
||||
// }
|
||||
|
||||
public async acquireLock(type: LockType, clientType: LockClientType, clientId: string): Promise<Lock> {
|
||||
return this.api().exec('POST', 'api/locks', null, {
|
||||
type,
|
||||
clientType,
|
||||
clientId: clientId,
|
||||
});
|
||||
}
|
||||
|
||||
public async releaseLock(type: LockType, clientType: LockClientType, clientId: string) {
|
||||
await this.api().exec('DELETE', `api/locks/${type}_${clientType}_${clientId}`);
|
||||
}
|
||||
|
||||
public async listLocks() {
|
||||
return this.api().exec('GET', 'api/locks');
|
||||
}
|
||||
|
||||
public async clearRoot(path: string) {
|
||||
const response = await this.list(path);
|
||||
|
||||
@@ -252,8 +203,6 @@ export default class FileApiDriverJoplinServer {
|
||||
await this.delete(item.path);
|
||||
}
|
||||
|
||||
await this.api().exec('POST', 'api/debug', null, { action: 'clearKeyValues' });
|
||||
|
||||
if (response.has_more) throw new Error('has_more support not implemented');
|
||||
}
|
||||
}
|
||||
@@ -5,7 +5,6 @@ import time from './time';
|
||||
|
||||
const { isHidden } = require('./path-utils');
|
||||
import JoplinError from './JoplinError';
|
||||
import { Lock, LockClientType, LockType } from './services/synchronizer/LockHandler';
|
||||
const ArrayUtils = require('./ArrayUtils');
|
||||
const { sprintf } = require('sprintf-js');
|
||||
const Mutex = require('async-mutex').Mutex;
|
||||
@@ -37,7 +36,7 @@ export interface RemoteItem {
|
||||
|
||||
export interface PaginatedList {
|
||||
items: RemoteItem[];
|
||||
hasMore: boolean;
|
||||
has_more: boolean;
|
||||
context: any;
|
||||
}
|
||||
|
||||
@@ -131,10 +130,6 @@ class FileApi {
|
||||
return !!this.driver().supportsAccurateTimestamp;
|
||||
}
|
||||
|
||||
public get supportsLocks(): boolean {
|
||||
return !!this.driver().supportsLocks;
|
||||
}
|
||||
|
||||
async fetchRemoteDateOffset_() {
|
||||
const tempFile = `${this.tempDirName()}/timeCheck${Math.round(Math.random() * 1000000)}.txt`;
|
||||
const startTime = Date.now();
|
||||
@@ -354,22 +349,6 @@ class FileApi {
|
||||
logger.debug(`delta ${this.fullPath(path)}`);
|
||||
return tryAndRepeat(() => this.driver_.delta(this.fullPath(path), options), this.requestRepeatCount());
|
||||
}
|
||||
|
||||
public async acquireLock(type: LockType, clientType: LockClientType, clientId: string): Promise<Lock> {
|
||||
if (!this.supportsLocks) throw new Error('Sync target does not support built-in locks');
|
||||
return tryAndRepeat(() => this.driver_.acquireLock(type, clientType, clientId), this.requestRepeatCount());
|
||||
}
|
||||
|
||||
public async releaseLock(type: LockType, clientType: LockClientType, clientId: string) {
|
||||
if (!this.supportsLocks) throw new Error('Sync target does not support built-in locks');
|
||||
return tryAndRepeat(() => this.driver_.releaseLock(type, clientType, clientId), this.requestRepeatCount());
|
||||
}
|
||||
|
||||
public async listLocks() {
|
||||
if (!this.supportsLocks) throw new Error('Sync target does not support built-in locks');
|
||||
return tryAndRepeat(() => this.driver_.listLocks(), this.requestRepeatCount());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function basicDeltaContextFromOptions_(options: any) {
|
||||
|
||||
Generated
+2
-2
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "@joplin/lib",
|
||||
"version": "2.5.0",
|
||||
"version": "2.6.2",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@joplin/lib",
|
||||
"version": "2.5.0",
|
||||
"version": "2.6.0",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"async-mutex": "^0.1.3",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@joplin/lib",
|
||||
"version": "2.5.0",
|
||||
"version": "2.6.2",
|
||||
"description": "Joplin Core library",
|
||||
"author": "Laurent Cozic",
|
||||
"homepage": "",
|
||||
@@ -28,11 +28,11 @@
|
||||
"typescript": "^4.0.5"
|
||||
},
|
||||
"dependencies": {
|
||||
"@joplin/fork-htmlparser2": "^4.1.36",
|
||||
"@joplin/fork-sax": "^1.2.40",
|
||||
"@joplin/renderer": "~2.5",
|
||||
"@joplin/turndown": "^4.0.58",
|
||||
"@joplin/turndown-plugin-gfm": "^1.0.40",
|
||||
"@joplin/fork-htmlparser2": "^4.1.38",
|
||||
"@joplin/fork-sax": "^1.2.42",
|
||||
"@joplin/renderer": "^2.6.2",
|
||||
"@joplin/turndown": "^4.0.60",
|
||||
"@joplin/turndown-plugin-gfm": "^1.0.42",
|
||||
"async-mutex": "^0.1.3",
|
||||
"aws-sdk": "^2.588.0",
|
||||
"base-64": "^0.1.0",
|
||||
@@ -46,7 +46,6 @@
|
||||
"es6-promise-pool": "^2.5.0",
|
||||
"file-uri-to-path": "^1.0.0",
|
||||
"follow-redirects": "^1.2.4",
|
||||
"sqlite3": "^5.0.2",
|
||||
"form-data": "^2.1.4",
|
||||
"fs-extra": "^5.0.0",
|
||||
"html-entities": "^1.2.1",
|
||||
@@ -77,6 +76,7 @@
|
||||
"reselect": "^4.0.0",
|
||||
"server-destroy": "^1.0.1",
|
||||
"sprintf-js": "^1.1.2",
|
||||
"sqlite3": "^5.0.2",
|
||||
"string-padding": "^1.0.2",
|
||||
"string-to-stream": "^1.1.0",
|
||||
"tar": "^4.4.10",
|
||||
|
||||
@@ -1,91 +1,23 @@
|
||||
import { Dirnames } from './utils/types';
|
||||
import shim from '../../shim';
|
||||
|
||||
import JoplinError from '../../JoplinError';
|
||||
import time from '../../time';
|
||||
import { FileApi } from '../../file-api';
|
||||
const { fileExtension, filename } = require('../../path-utils');
|
||||
|
||||
export enum LockType {
|
||||
None = 0,
|
||||
Sync = 1,
|
||||
Exclusive = 2,
|
||||
}
|
||||
|
||||
export enum LockClientType {
|
||||
Desktop = 1,
|
||||
Mobile = 2,
|
||||
Cli = 3,
|
||||
None = '',
|
||||
Sync = 'sync',
|
||||
Exclusive = 'exclusive',
|
||||
}
|
||||
|
||||
export interface Lock {
|
||||
id?: string;
|
||||
type: LockType;
|
||||
clientType: LockClientType;
|
||||
clientType: string;
|
||||
clientId: string;
|
||||
updatedTime?: number;
|
||||
}
|
||||
|
||||
function lockIsActive(lock: Lock, currentDate: Date, lockTtl: number): boolean {
|
||||
return currentDate.getTime() - lock.updatedTime < lockTtl;
|
||||
}
|
||||
|
||||
export function lockNameToObject(name: string, updatedTime: number = null): Lock {
|
||||
const p = name.split('_');
|
||||
|
||||
const lock: Lock = {
|
||||
id: null,
|
||||
type: Number(p[0]) as LockType,
|
||||
clientType: Number(p[1]) as LockClientType,
|
||||
clientId: p[2],
|
||||
updatedTime,
|
||||
};
|
||||
|
||||
if (isNaN(lock.clientType)) throw new Error(`Invalid lock client type: ${name}`);
|
||||
if (isNaN(lock.type)) throw new Error(`Invalid lock type: ${name}`);
|
||||
|
||||
return lock;
|
||||
}
|
||||
|
||||
export function hasActiveLock(locks: Lock[], currentDate: Date, lockTtl: number, lockType: LockType, clientType: LockClientType = null, clientId: string = null) {
|
||||
const lock = activeLock(locks, currentDate, lockTtl, lockType, clientType, clientId);
|
||||
return !!lock;
|
||||
}
|
||||
|
||||
// Finds if there's an active lock for this clientType and clientId and returns it.
|
||||
// If clientType and clientId are not specified, returns the first active lock
|
||||
// of that type instead.
|
||||
export function activeLock(locks: Lock[], currentDate: Date, lockTtl: number, lockType: LockType, clientType: LockClientType = null, clientId: string = null) {
|
||||
if (lockType === LockType.Exclusive) {
|
||||
const activeLocks = locks
|
||||
.slice()
|
||||
.filter((lock: Lock) => lockIsActive(lock, currentDate, lockTtl) && lock.type === lockType)
|
||||
.sort((a: Lock, b: Lock) => {
|
||||
if (a.updatedTime === b.updatedTime) {
|
||||
return a.clientId < b.clientId ? -1 : +1;
|
||||
}
|
||||
return a.updatedTime < b.updatedTime ? -1 : +1;
|
||||
});
|
||||
|
||||
if (!activeLocks.length) return null;
|
||||
const lock = activeLocks[0];
|
||||
|
||||
if (clientType && clientType !== lock.clientType) return null;
|
||||
if (clientId && clientId !== lock.clientId) return null;
|
||||
return lock;
|
||||
} else if (lockType === LockType.Sync) {
|
||||
for (const lock of locks) {
|
||||
if (lock.type !== lockType) continue;
|
||||
if (clientType && lock.clientType !== clientType) continue;
|
||||
if (clientId && lock.clientId !== clientId) continue;
|
||||
if (lockIsActive(lock, currentDate, lockTtl)) return lock;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
throw new Error(`Unsupported lock type: ${lockType}`);
|
||||
}
|
||||
|
||||
|
||||
export interface AcquireLockOptions {
|
||||
// In theory, a client that tries to acquire an exclusive lock shouldn't
|
||||
// also have a sync lock. It can however happen when the app is closed
|
||||
@@ -123,16 +55,14 @@ export interface LockHandlerOptions {
|
||||
lockTtl?: number;
|
||||
}
|
||||
|
||||
export const defaultLockTtl = 1000 * 60 * 3;
|
||||
|
||||
export default class LockHandler {
|
||||
|
||||
private api_: FileApi = null;
|
||||
private api_: any = null;
|
||||
private refreshTimers_: RefreshTimers = {};
|
||||
private autoRefreshInterval_: number = 1000 * 60;
|
||||
private lockTtl_: number = defaultLockTtl;
|
||||
private lockTtl_: number = 1000 * 60 * 3;
|
||||
|
||||
public constructor(api: FileApi, options: LockHandlerOptions = null) {
|
||||
constructor(api: any, options: LockHandlerOptions = null) {
|
||||
if (!options) options = {};
|
||||
|
||||
this.api_ = api;
|
||||
@@ -150,10 +80,6 @@ export default class LockHandler {
|
||||
this.lockTtl_ = v;
|
||||
}
|
||||
|
||||
public get useBuiltInLocks() {
|
||||
return this.api_.supportsLocks;
|
||||
}
|
||||
|
||||
private lockFilename(lock: Lock) {
|
||||
return `${[lock.type, lock.clientType, lock.clientId].join('_')}.json`;
|
||||
}
|
||||
@@ -161,8 +87,8 @@ export default class LockHandler {
|
||||
private lockTypeFromFilename(name: string): LockType {
|
||||
const ext = fileExtension(name);
|
||||
if (ext !== 'json') return LockType.None;
|
||||
if (name.indexOf(LockType.Sync.toString()) === 0) return LockType.Sync;
|
||||
if (name.indexOf(LockType.Exclusive.toString()) === 0) return LockType.Exclusive;
|
||||
if (name.indexOf(LockType.Sync) === 0) return LockType.Sync;
|
||||
if (name.indexOf(LockType.Exclusive) === 0) return LockType.Exclusive;
|
||||
return LockType.None;
|
||||
}
|
||||
|
||||
@@ -171,15 +97,17 @@ export default class LockHandler {
|
||||
}
|
||||
|
||||
private lockFileToObject(file: any): Lock {
|
||||
return lockNameToObject(filename(file.path), file.updated_time);
|
||||
const p = filename(file.path).split('_');
|
||||
|
||||
return {
|
||||
type: p[0],
|
||||
clientType: p[1],
|
||||
clientId: p[2],
|
||||
updatedTime: file.updated_time,
|
||||
};
|
||||
}
|
||||
|
||||
async locks(lockType: LockType = null): Promise<Lock[]> {
|
||||
if (this.useBuiltInLocks) {
|
||||
const locks = (await this.api_.listLocks()).items;
|
||||
return locks;
|
||||
}
|
||||
|
||||
const result = await this.api_.list(Dirnames.Locks);
|
||||
if (result.hasMore) throw new Error('hasMore not handled'); // Shouldn't happen anyway
|
||||
|
||||
@@ -195,6 +123,51 @@ export default class LockHandler {
|
||||
return output;
|
||||
}
|
||||
|
||||
private lockIsActive(lock: Lock, currentDate: Date): boolean {
|
||||
return currentDate.getTime() - lock.updatedTime < this.lockTtl;
|
||||
}
|
||||
|
||||
async hasActiveLock(lockType: LockType, clientType: string = null, clientId: string = null) {
|
||||
const lock = await this.activeLock(lockType, clientType, clientId);
|
||||
return !!lock;
|
||||
}
|
||||
|
||||
// Finds if there's an active lock for this clientType and clientId and returns it.
|
||||
// If clientType and clientId are not specified, returns the first active lock
|
||||
// of that type instead.
|
||||
async activeLock(lockType: LockType, clientType: string = null, clientId: string = null) {
|
||||
const locks = await this.locks(lockType);
|
||||
const currentDate = await this.api_.remoteDate();
|
||||
|
||||
if (lockType === LockType.Exclusive) {
|
||||
const activeLocks = locks
|
||||
.slice()
|
||||
.filter((lock: Lock) => this.lockIsActive(lock, currentDate))
|
||||
.sort((a: Lock, b: Lock) => {
|
||||
if (a.updatedTime === b.updatedTime) {
|
||||
return a.clientId < b.clientId ? -1 : +1;
|
||||
}
|
||||
return a.updatedTime < b.updatedTime ? -1 : +1;
|
||||
});
|
||||
|
||||
if (!activeLocks.length) return null;
|
||||
const activeLock = activeLocks[0];
|
||||
|
||||
if (clientType && clientType !== activeLock.clientType) return null;
|
||||
if (clientId && clientId !== activeLock.clientId) return null;
|
||||
return activeLock;
|
||||
} else if (lockType === LockType.Sync) {
|
||||
for (const lock of locks) {
|
||||
if (clientType && lock.clientType !== clientType) continue;
|
||||
if (clientId && lock.clientId !== clientId) continue;
|
||||
if (this.lockIsActive(lock, currentDate)) return lock;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
throw new Error(`Unsupported lock type: ${lockType}`);
|
||||
}
|
||||
|
||||
private async saveLock(lock: Lock) {
|
||||
await this.api_.put(this.lockFilePath(lock), JSON.stringify(lock));
|
||||
}
|
||||
@@ -204,18 +177,13 @@ export default class LockHandler {
|
||||
return this.saveLock(lock);
|
||||
}
|
||||
|
||||
private async acquireSyncLock(clientType: LockClientType, clientId: string): Promise<Lock> {
|
||||
if (this.useBuiltInLocks) return this.api_.acquireLock(LockType.Sync, clientType, clientId);
|
||||
|
||||
private async acquireSyncLock(clientType: string, clientId: string): Promise<Lock> {
|
||||
try {
|
||||
let isFirstPass = true;
|
||||
while (true) {
|
||||
const locks = await this.locks();
|
||||
const currentDate = await this.currentDate();
|
||||
|
||||
const [exclusiveLock, syncLock] = await Promise.all([
|
||||
activeLock(locks, currentDate, this.lockTtl, LockType.Exclusive),
|
||||
activeLock(locks, currentDate, this.lockTtl, LockType.Sync, clientType, clientId),
|
||||
this.activeLock(LockType.Exclusive),
|
||||
this.activeLock(LockType.Sync, clientType, clientId),
|
||||
]);
|
||||
|
||||
if (exclusiveLock) {
|
||||
@@ -253,9 +221,7 @@ export default class LockHandler {
|
||||
return `(${lock.clientType} #${lock.clientId})`;
|
||||
}
|
||||
|
||||
private async acquireExclusiveLock(clientType: LockClientType, clientId: string, options: AcquireLockOptions = null): Promise<Lock> {
|
||||
if (this.useBuiltInLocks) return this.api_.acquireLock(LockType.Exclusive, clientType, clientId);
|
||||
|
||||
private async acquireExclusiveLock(clientType: string, clientId: string, options: AcquireLockOptions = null): Promise<Lock> {
|
||||
// The logic to acquire an exclusive lock, while avoiding race conditions is as follow:
|
||||
//
|
||||
// - Check if there is a lock file present
|
||||
@@ -286,12 +252,9 @@ export default class LockHandler {
|
||||
|
||||
try {
|
||||
while (true) {
|
||||
const locks = await this.locks();
|
||||
const currentDate = await this.currentDate();
|
||||
|
||||
const [activeSyncLock, activeExclusiveLock] = await Promise.all([
|
||||
activeLock(locks, currentDate, this.lockTtl, LockType.Sync),
|
||||
activeLock(locks, currentDate, this.lockTtl, LockType.Exclusive),
|
||||
this.activeLock(LockType.Sync),
|
||||
this.activeLock(LockType.Exclusive),
|
||||
]);
|
||||
|
||||
if (activeSyncLock) {
|
||||
@@ -336,11 +299,7 @@ export default class LockHandler {
|
||||
return [lock.type, lock.clientType, lock.clientId].join('_');
|
||||
}
|
||||
|
||||
public async currentDate() {
|
||||
return this.api_.remoteDate();
|
||||
}
|
||||
|
||||
public startAutoLockRefresh(lock: Lock, errorHandler: Function): string {
|
||||
startAutoLockRefresh(lock: Lock, errorHandler: Function): string {
|
||||
const handle = this.autoLockRefreshHandle(lock);
|
||||
if (this.refreshTimers_[handle]) {
|
||||
throw new Error(`There is already a timer refreshing this lock: ${handle}`);
|
||||
@@ -362,11 +321,10 @@ export default class LockHandler {
|
||||
this.refreshTimers_[handle].inProgress = true;
|
||||
|
||||
let error = null;
|
||||
const hasActiveLock = await this.hasActiveLock(lock.type, lock.clientType, lock.clientId);
|
||||
if (!this.refreshTimers_[handle]) return defer(); // Timeout has been cleared
|
||||
|
||||
const locks = await this.locks(lock.type);
|
||||
|
||||
if (!hasActiveLock(locks, await this.currentDate(), this.lockTtl, lock.type, lock.clientType, lock.clientId)) {
|
||||
if (!hasActiveLock) {
|
||||
// If the previous lock has expired, we shouldn't try to acquire a new one. This is because other clients might have performed
|
||||
// in the meantime operations that invalidates the current operation. For example, another client might have upgraded the
|
||||
// sync target in the meantime, so any active operation should be cancelled here. Or if the current client was upgraded
|
||||
@@ -410,7 +368,7 @@ export default class LockHandler {
|
||||
delete this.refreshTimers_[handle];
|
||||
}
|
||||
|
||||
public async acquireLock(lockType: LockType, clientType: LockClientType, clientId: string, options: AcquireLockOptions = null): Promise<Lock> {
|
||||
public async acquireLock(lockType: LockType, clientType: string, clientId: string, options: AcquireLockOptions = null): Promise<Lock> {
|
||||
options = {
|
||||
...defaultAcquireLockOptions(),
|
||||
...options,
|
||||
@@ -425,12 +383,7 @@ export default class LockHandler {
|
||||
}
|
||||
}
|
||||
|
||||
public async releaseLock(lockType: LockType, clientType: LockClientType, clientId: string) {
|
||||
if (this.useBuiltInLocks) {
|
||||
await this.api_.releaseLock(lockType, clientType, clientId);
|
||||
return;
|
||||
}
|
||||
|
||||
public async releaseLock(lockType: LockType, clientType: string, clientId: string) {
|
||||
await this.api_.delete(this.lockFilePath({
|
||||
type: lockType,
|
||||
clientType: clientType,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import LockHandler, { LockClientType, LockType } from './LockHandler';
|
||||
import LockHandler, { LockType } from './LockHandler';
|
||||
import { Dirnames } from './utils/types';
|
||||
import BaseService from '../BaseService';
|
||||
import migration1 from './migrations/1';
|
||||
@@ -33,11 +33,11 @@ export default class MigrationHandler extends BaseService {
|
||||
|
||||
private api_: FileApi = null;
|
||||
private lockHandler_: LockHandler = null;
|
||||
private clientType_: LockClientType;
|
||||
private clientType_: string;
|
||||
private clientId_: string;
|
||||
private db_: JoplinDatabase;
|
||||
|
||||
public constructor(api: FileApi, db: JoplinDatabase, lockHandler: LockHandler, clientType: LockClientType, clientId: string) {
|
||||
public constructor(api: FileApi, db: JoplinDatabase, lockHandler: LockHandler, clientType: string, clientId: string) {
|
||||
super();
|
||||
this.api_ = api;
|
||||
this.db_ = db;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import LockHandler, { LockType, LockHandlerOptions, Lock, activeLock, LockClientType } from '../../services/synchronizer/LockHandler';
|
||||
import LockHandler, { LockType, LockHandlerOptions, Lock } from '../../services/synchronizer/LockHandler';
|
||||
import { isNetworkSyncTarget, fileApi, setupDatabaseAndSynchronizer, synchronizer, switchClient, msleep, expectThrow, expectNotThrow } from '../../testing/test-utils';
|
||||
|
||||
// For tests with memory of file system we can use low intervals to make the tests faster.
|
||||
@@ -34,46 +34,38 @@ describe('synchronizer_LockHandler', function() {
|
||||
});
|
||||
|
||||
it('should acquire and release a sync lock', (async () => {
|
||||
await lockHandler().acquireLock(LockType.Sync, LockClientType.Mobile, '123456');
|
||||
await lockHandler().acquireLock(LockType.Sync, 'mobile', '123456');
|
||||
const locks = await lockHandler().locks(LockType.Sync);
|
||||
expect(locks.length).toBe(1);
|
||||
expect(locks[0].type).toBe(LockType.Sync);
|
||||
expect(locks[0].clientId).toBe('123456');
|
||||
expect(locks[0].clientType).toBe(LockClientType.Mobile);
|
||||
expect(locks[0].clientType).toBe('mobile');
|
||||
|
||||
await lockHandler().releaseLock(LockType.Sync, LockClientType.Mobile, '123456');
|
||||
await lockHandler().releaseLock(LockType.Sync, 'mobile', '123456');
|
||||
expect((await lockHandler().locks(LockType.Sync)).length).toBe(0);
|
||||
}));
|
||||
|
||||
it('should not use files that are not locks', (async () => {
|
||||
if (lockHandler().useBuiltInLocks) return; // Doesn't make sense with built-in locks
|
||||
|
||||
await fileApi().put('locks/desktop.ini', 'a');
|
||||
await fileApi().put('locks/exclusive.json', 'a');
|
||||
await fileApi().put('locks/garbage.json', 'a');
|
||||
await fileApi().put('locks/1_2_72c4d1b7253a4475bfb2f977117d26ed.json', 'a');
|
||||
|
||||
// Check that it doesn't cause an error if it fetches an old style lock
|
||||
await fileApi().put('locks/sync_desktop_82c4d1b7253a4475bfb2f977117d26ed.json', 'a');
|
||||
await fileApi().put('locks/sync_mobile_72c4d1b7253a4475bfb2f977117d26ed.json', 'a');
|
||||
|
||||
const locks = await lockHandler().locks(LockType.Sync);
|
||||
expect(locks.length).toBe(1);
|
||||
expect(locks[0].type).toBe(LockType.Sync);
|
||||
expect(locks[0].clientType).toBe(LockClientType.Mobile);
|
||||
expect(locks[0].clientId).toBe('72c4d1b7253a4475bfb2f977117d26ed');
|
||||
}));
|
||||
|
||||
it('should allow multiple sync locks', (async () => {
|
||||
await lockHandler().acquireLock(LockType.Sync, LockClientType.Mobile, '111');
|
||||
await lockHandler().acquireLock(LockType.Sync, 'mobile', '111');
|
||||
|
||||
await switchClient(2);
|
||||
|
||||
await lockHandler().acquireLock(LockType.Sync, LockClientType.Mobile, '222');
|
||||
await lockHandler().acquireLock(LockType.Sync, 'mobile', '222');
|
||||
|
||||
expect((await lockHandler().locks(LockType.Sync)).length).toBe(2);
|
||||
|
||||
{
|
||||
await lockHandler().releaseLock(LockType.Sync, LockClientType.Mobile, '222');
|
||||
await lockHandler().releaseLock(LockType.Sync, 'mobile', '222');
|
||||
const locks = await lockHandler().locks(LockType.Sync);
|
||||
expect(locks.length).toBe(1);
|
||||
expect(locks[0].clientId).toBe('111');
|
||||
@@ -82,11 +74,11 @@ describe('synchronizer_LockHandler', function() {
|
||||
|
||||
it('should auto-refresh a lock', (async () => {
|
||||
const handler = newLockHandler({ autoRefreshInterval: 100 * timeoutMultipler });
|
||||
const lock = await handler.acquireLock(LockType.Sync, LockClientType.Desktop, '111');
|
||||
const lockBefore = activeLock(await handler.locks(), new Date(), handler.lockTtl, LockType.Sync, LockClientType.Desktop, '111');
|
||||
const lock = await handler.acquireLock(LockType.Sync, 'desktop', '111');
|
||||
const lockBefore = await handler.activeLock(LockType.Sync, 'desktop', '111');
|
||||
handler.startAutoLockRefresh(lock, () => {});
|
||||
await msleep(500 * timeoutMultipler);
|
||||
const lockAfter = activeLock(await handler.locks(), new Date(), handler.lockTtl, LockType.Sync, LockClientType.Desktop, '111');
|
||||
const lockAfter = await handler.activeLock(LockType.Sync, 'desktop', '111');
|
||||
expect(lockAfter.updatedTime).toBeGreaterThan(lockBefore.updatedTime);
|
||||
handler.stopAutoLockRefresh(lock);
|
||||
}));
|
||||
@@ -97,53 +89,48 @@ describe('synchronizer_LockHandler', function() {
|
||||
autoRefreshInterval: 200 * timeoutMultipler,
|
||||
});
|
||||
|
||||
const lock = await handler.acquireLock(LockType.Sync, LockClientType.Desktop, '111');
|
||||
const lock = await handler.acquireLock(LockType.Sync, 'desktop', '111');
|
||||
let autoLockError: any = null;
|
||||
handler.startAutoLockRefresh(lock, (error: any) => {
|
||||
autoLockError = error;
|
||||
});
|
||||
|
||||
try {
|
||||
await msleep(250 * timeoutMultipler);
|
||||
await msleep(250 * timeoutMultipler);
|
||||
|
||||
expect(autoLockError).toBeTruthy();
|
||||
expect(autoLockError.code).toBe('lockExpired');
|
||||
} finally {
|
||||
handler.stopAutoLockRefresh(lock);
|
||||
}
|
||||
expect(autoLockError.code).toBe('lockExpired');
|
||||
|
||||
handler.stopAutoLockRefresh(lock);
|
||||
}));
|
||||
|
||||
it('should not allow sync locks if there is an exclusive lock', (async () => {
|
||||
await lockHandler().acquireLock(LockType.Exclusive, LockClientType.Desktop, '111');
|
||||
await lockHandler().acquireLock(LockType.Exclusive, 'desktop', '111');
|
||||
|
||||
await expectThrow(async () => {
|
||||
await lockHandler().acquireLock(LockType.Sync, LockClientType.Mobile, '222');
|
||||
await lockHandler().acquireLock(LockType.Sync, 'mobile', '222');
|
||||
}, 'hasExclusiveLock');
|
||||
}));
|
||||
|
||||
it('should not allow exclusive lock if there are sync locks', (async () => {
|
||||
const lockHandler = newLockHandler({ lockTtl: 1000 * 60 * 60 });
|
||||
if (lockHandler.useBuiltInLocks) return; // Tested server side
|
||||
|
||||
await lockHandler.acquireLock(LockType.Sync, LockClientType.Mobile, '111');
|
||||
await lockHandler.acquireLock(LockType.Sync, LockClientType.Mobile, '222');
|
||||
await lockHandler.acquireLock(LockType.Sync, 'mobile', '111');
|
||||
await lockHandler.acquireLock(LockType.Sync, 'mobile', '222');
|
||||
|
||||
await expectThrow(async () => {
|
||||
await lockHandler.acquireLock(LockType.Exclusive, LockClientType.Desktop, '333');
|
||||
await lockHandler.acquireLock(LockType.Exclusive, 'desktop', '333');
|
||||
}, 'hasSyncLock');
|
||||
}));
|
||||
|
||||
it('should allow exclusive lock if the sync locks have expired', (async () => {
|
||||
const lockHandler = newLockHandler({ lockTtl: 500 * timeoutMultipler });
|
||||
if (lockHandler.useBuiltInLocks) return; // Tested server side
|
||||
|
||||
await lockHandler.acquireLock(LockType.Sync, LockClientType.Mobile, '111');
|
||||
await lockHandler.acquireLock(LockType.Sync, LockClientType.Mobile, '222');
|
||||
await lockHandler.acquireLock(LockType.Sync, 'mobile', '111');
|
||||
await lockHandler.acquireLock(LockType.Sync, 'mobile', '222');
|
||||
|
||||
await msleep(600 * timeoutMultipler);
|
||||
|
||||
await expectNotThrow(async () => {
|
||||
await lockHandler.acquireLock(LockType.Exclusive, LockClientType.Desktop, '333');
|
||||
await lockHandler.acquireLock(LockType.Exclusive, 'desktop', '333');
|
||||
});
|
||||
}));
|
||||
|
||||
@@ -151,44 +138,74 @@ describe('synchronizer_LockHandler', function() {
|
||||
const lockHandler = newLockHandler();
|
||||
|
||||
{
|
||||
const locks: Lock[] = [
|
||||
{
|
||||
type: LockType.Exclusive,
|
||||
clientId: '1',
|
||||
clientType: LockClientType.Desktop,
|
||||
updatedTime: Date.now(),
|
||||
},
|
||||
];
|
||||
|
||||
const lock1: Lock = { type: LockType.Exclusive, clientId: '1', clientType: 'd' };
|
||||
const lock2: Lock = { type: LockType.Exclusive, clientId: '2', clientType: 'd' };
|
||||
await lockHandler.saveLock_(lock1);
|
||||
await msleep(100);
|
||||
await lockHandler.saveLock_(lock2);
|
||||
|
||||
locks.push({
|
||||
type: LockType.Exclusive,
|
||||
clientId: '2',
|
||||
clientType: LockClientType.Desktop,
|
||||
updatedTime: Date.now(),
|
||||
});
|
||||
|
||||
const lock = activeLock(locks, new Date(), lockHandler.lockTtl, LockType.Exclusive);
|
||||
expect(lock.clientId).toBe('1');
|
||||
const activeLock = await lockHandler.activeLock(LockType.Exclusive);
|
||||
expect(activeLock.clientId).toBe('1');
|
||||
}
|
||||
}));
|
||||
|
||||
// it('should ignore locks by same client when trying to acquire exclusive lock', (async () => {
|
||||
it('should ignore locks by same client when trying to acquire exclusive lock', (async () => {
|
||||
const lockHandler = newLockHandler();
|
||||
|
||||
await lockHandler.acquireLock(LockType.Sync, 'desktop', '111');
|
||||
|
||||
await expectThrow(async () => {
|
||||
await lockHandler.acquireLock(LockType.Exclusive, 'desktop', '111', { clearExistingSyncLocksFromTheSameClient: false });
|
||||
}, 'hasSyncLock');
|
||||
|
||||
await expectNotThrow(async () => {
|
||||
await lockHandler.acquireLock(LockType.Exclusive, 'desktop', '111', { clearExistingSyncLocksFromTheSameClient: true });
|
||||
});
|
||||
|
||||
const activeLock = await lockHandler.activeLock(LockType.Exclusive);
|
||||
expect(activeLock.clientId).toBe('111');
|
||||
}));
|
||||
|
||||
// it('should not have race conditions', (async () => {
|
||||
// const lockHandler = newLockHandler();
|
||||
|
||||
// await lockHandler.acquireLock(LockType.Sync, LockClientType.Desktop, '111');
|
||||
// const clients = [];
|
||||
// for (let i = 0; i < 20; i++) {
|
||||
// clients.push({
|
||||
// id: 'client' + i,
|
||||
// type: 'desktop',
|
||||
// });
|
||||
// }
|
||||
|
||||
// await expectThrow(async () => {
|
||||
// await lockHandler.acquireLock(LockType.Exclusive, LockClientType.Desktop, '111', { clearExistingSyncLocksFromTheSameClient: false });
|
||||
// }, 'hasSyncLock');
|
||||
// for (let loopIndex = 0; loopIndex < 1000; loopIndex++) {
|
||||
// const promises:Promise<void | Lock>[] = [];
|
||||
// for (let clientIndex = 0; clientIndex < clients.length; clientIndex++) {
|
||||
// const client = clients[clientIndex];
|
||||
|
||||
// await expectNotThrow(async () => {
|
||||
// await lockHandler.acquireLock(LockType.Exclusive, LockClientType.Desktop, '111', { clearExistingSyncLocksFromTheSameClient: true });
|
||||
// });
|
||||
// promises.push(
|
||||
// lockHandler.acquireLock(LockType.Exclusive, client.type, client.id).catch(() => {})
|
||||
// );
|
||||
|
||||
// const lock = activeLock(await lockHandler.locks(), new Date(), lockHandler.lockTtl, LockType.Exclusive);
|
||||
// expect(lock.clientId).toBe('111');
|
||||
// // if (gotLock) {
|
||||
// // await msleep(100);
|
||||
// // const locks = await lockHandler.locks(LockType.Exclusive);
|
||||
// // console.info('=======================================');
|
||||
// // console.info(locks);
|
||||
// // lockHandler.releaseLock(LockType.Exclusive, client.type, client.id);
|
||||
// // }
|
||||
|
||||
// // await msleep(500);
|
||||
// }
|
||||
|
||||
// const result = await Promise.all(promises);
|
||||
// const locks = result.filter((lock:any) => !!lock);
|
||||
|
||||
// expect(locks.length).toBe(1);
|
||||
// const lock:Lock = locks[0] as Lock;
|
||||
// const allLocks = await lockHandler.locks();
|
||||
// console.info('================================', allLocks);
|
||||
// lockHandler.releaseLock(LockType.Exclusive, lock.clientType, lock.clientId);
|
||||
// }
|
||||
// }));
|
||||
|
||||
});
|
||||
@@ -5,7 +5,7 @@
|
||||
//
|
||||
// These tests work by a taking a sync target snapshot at a version n and upgrading it to n+1.
|
||||
|
||||
import LockHandler, { LockClientType } from './LockHandler';
|
||||
import LockHandler from './LockHandler';
|
||||
import MigrationHandler from './MigrationHandler';
|
||||
import { Dirnames } from './utils/types';
|
||||
import { setSyncTargetName, fileApi, synchronizer, decryptionWorker, encryptionService, setupDatabaseAndSynchronizer, switchClient, expectThrow, expectNotThrow, db } from '../../testing/test-utils';
|
||||
@@ -28,7 +28,7 @@ function lockHandler(): LockHandler {
|
||||
|
||||
function migrationHandler(clientId: string = 'abcd'): MigrationHandler {
|
||||
if (migrationHandler_) return migrationHandler_;
|
||||
migrationHandler_ = new MigrationHandler(fileApi(), db(), lockHandler(), LockClientType.Desktop, clientId);
|
||||
migrationHandler_ = new MigrationHandler(fileApi(), db(), lockHandler(), 'desktop', clientId);
|
||||
return migrationHandler_;
|
||||
}
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ export async function allNotesFolders() {
|
||||
|
||||
async function remoteItemsByTypes(types: number[]) {
|
||||
const list = await fileApi().list('', { includeDirs: false, syncItemsOnly: true });
|
||||
if (list.hasMore) throw new Error('Not implemented!!!');
|
||||
if (list.has_more) throw new Error('Not implemented!!!');
|
||||
const files = list.items;
|
||||
|
||||
const output = [];
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@joplin/plugin-repo-cli",
|
||||
"version": "2.4.3",
|
||||
"version": "2.6.2",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@joplin/plugin-repo-cli",
|
||||
"version": "2.5.0",
|
||||
"version": "2.6.2",
|
||||
"description": "",
|
||||
"main": "index.js",
|
||||
"bin": {
|
||||
@@ -19,8 +19,8 @@
|
||||
"author": "",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@joplin/lib": "~2.5",
|
||||
"@joplin/tools": "~2.5",
|
||||
"@joplin/lib": "^2.6.2",
|
||||
"@joplin/tools": "^2.6.2",
|
||||
"fs-extra": "^9.0.1",
|
||||
"gh-release-assets": "^2.0.0",
|
||||
"node-fetch": "^2.6.1",
|
||||
|
||||
Generated
+2
-2
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "@joplin/renderer",
|
||||
"version": "2.5.0",
|
||||
"version": "2.6.2",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@joplin/renderer",
|
||||
"version": "2.5.0",
|
||||
"version": "2.6.0",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"font-awesome-filetypes": "^2.1.0",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@joplin/renderer",
|
||||
"version": "2.5.0",
|
||||
"version": "2.6.2",
|
||||
"description": "The Joplin note renderer, used the mobile and desktop application",
|
||||
"repository": "https://github.com/laurent22/joplin/tree/dev/packages/renderer",
|
||||
"main": "index.js",
|
||||
@@ -24,7 +24,7 @@
|
||||
"typescript": "^4.0.5"
|
||||
},
|
||||
"dependencies": {
|
||||
"@joplin/fork-htmlparser2": "^4.1.36",
|
||||
"@joplin/fork-htmlparser2": "^4.1.38",
|
||||
"font-awesome-filetypes": "^2.1.0",
|
||||
"fs-extra": "^8.1.0",
|
||||
"highlight.js": "^11.2.0",
|
||||
|
||||
Generated
+2
-2
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "@joplin/server",
|
||||
"version": "2.5.9",
|
||||
"version": "2.5.10",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@joplin/server",
|
||||
"version": "2.5.9",
|
||||
"version": "2.5.10",
|
||||
"dependencies": {
|
||||
"@fortawesome/fontawesome-free": "^5.15.1",
|
||||
"@koa/cors": "^3.1.0",
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
"devDropTables": "node dist/app.js --env dev --drop-tables",
|
||||
"devDropDb": "node dist/app.js --env dev --drop-db",
|
||||
"start": "node dist/app.js",
|
||||
"generateTypes": "rm -f db-buildTypes.sqlite && npm run start -- --env buildTypes migrate latest && node dist/tools/generateTypes.js && mv db-buildTypes.sqlite schema.sqlite",
|
||||
"generateTypes": "rm -f db-buildTypes.sqlite && npm run start -- --migrate-latest --env buildTypes && node dist/tools/generateTypes.js && mv db-buildTypes.sqlite schema.sqlite",
|
||||
"tsc": "tsc --project tsconfig.json",
|
||||
"test": "jest --verbose=false",
|
||||
"test-ci": "npm run test",
|
||||
@@ -22,8 +22,8 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@fortawesome/fontawesome-free": "^5.15.1",
|
||||
"@joplin/lib": "~2.5",
|
||||
"@joplin/renderer": "~2.5",
|
||||
"@joplin/lib": "~2.6",
|
||||
"@joplin/renderer": "~2.6",
|
||||
"@koa/cors": "^3.1.0",
|
||||
"@types/uuid": "^8.3.1",
|
||||
"bcryptjs": "^2.4.3",
|
||||
@@ -56,7 +56,7 @@
|
||||
"zxcvbn": "^4.4.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@joplin/tools": "~2.5",
|
||||
"@joplin/tools": "~2.6",
|
||||
"@rmp135/sql-ts": "^1.7.0",
|
||||
"@types/fs-extra": "^8.0.0",
|
||||
"@types/jest": "^26.0.15",
|
||||
|
||||
@@ -4,7 +4,7 @@ require('source-map-support').install();
|
||||
import * as Koa from 'koa';
|
||||
import * as fs from 'fs-extra';
|
||||
import Logger, { LoggerWrapper, TargetType } from '@joplin/lib/Logger';
|
||||
import config, { initConfig, runningInDocker, EnvVariables } from './config';
|
||||
import config, { initConfig, runningInDocker } from './config';
|
||||
import { migrateLatest, waitForConnection, sqliteDefaultDir } from './db';
|
||||
import { AppContext, Env, KoaNext } from './utils/types';
|
||||
import FsDriverNode from '@joplin/lib/fs-driver-node';
|
||||
@@ -20,6 +20,7 @@ import clickJackingHandler from './middleware/clickJackingHandler';
|
||||
import newModelFactory from './models/factory';
|
||||
import setupCommands from './utils/setupCommands';
|
||||
import { RouteResponseFormat, routeResponseFormat } from './utils/routeUtils';
|
||||
import { parseEnv } from './env';
|
||||
|
||||
interface Argv {
|
||||
env?: Env;
|
||||
@@ -33,7 +34,7 @@ const nodeEnvFile = require('node-env-file');
|
||||
const { shimInit } = require('@joplin/lib/shim-init-node.js');
|
||||
shimInit({ nodeSqlite });
|
||||
|
||||
const defaultEnvVariables: Record<Env, EnvVariables> = {
|
||||
const defaultEnvVariables: Record<Env, any> = {
|
||||
dev: {
|
||||
// To test with the Postgres database, uncomment DB_CLIENT below and
|
||||
// comment out SQLITE_DATABASE. Then start the Postgres server using
|
||||
@@ -95,10 +96,7 @@ async function main() {
|
||||
|
||||
if (!defaultEnvVariables[env]) throw new Error(`Invalid env: ${env}`);
|
||||
|
||||
const envVariables: EnvVariables = {
|
||||
...defaultEnvVariables[env],
|
||||
...process.env,
|
||||
};
|
||||
const envVariables = parseEnv(process.env, defaultEnvVariables[env]);
|
||||
|
||||
const app = new Koa();
|
||||
|
||||
@@ -254,6 +252,7 @@ async function main() {
|
||||
appLogger().info('User content base URL:', config().userContentBaseUrl);
|
||||
appLogger().info('Log dir:', config().logDir);
|
||||
appLogger().info('DB Config:', markPasswords(config().database));
|
||||
appLogger().info('Mailer Config:', markPasswords(config().mailer));
|
||||
|
||||
appLogger().info('Trying to connect to database...');
|
||||
const connectionCheck = await waitForConnection(config().database);
|
||||
|
||||
+28
-109
@@ -2,6 +2,7 @@ import { rtrimSlashes } from '@joplin/lib/path-utils';
|
||||
import { Config, DatabaseConfig, DatabaseConfigClient, Env, MailerConfig, RouteType, StripeConfig } from './utils/types';
|
||||
import * as pathUtils from 'path';
|
||||
import { loadStripeConfig, StripePublicConfig } from '@joplin/lib/utils/joplinCloud';
|
||||
import { EnvVariables } from './env';
|
||||
|
||||
interface PackageJson {
|
||||
version: string;
|
||||
@@ -9,94 +10,12 @@ interface PackageJson {
|
||||
|
||||
const packageJson: PackageJson = require(`${__dirname}/packageInfo.js`);
|
||||
|
||||
export interface EnvVariables {
|
||||
// ==================================================
|
||||
// General config
|
||||
// ==================================================
|
||||
|
||||
APP_NAME?: string;
|
||||
APP_PORT?: string;
|
||||
SIGNUP_ENABLED?: string;
|
||||
TERMS_ENABLED?: string;
|
||||
ACCOUNT_TYPES_ENABLED?: string;
|
||||
ERROR_STACK_TRACES?: string;
|
||||
COOKIES_SECURE?: string;
|
||||
RUNNING_IN_DOCKER?: string;
|
||||
|
||||
// ==================================================
|
||||
// URL config
|
||||
// ==================================================
|
||||
|
||||
APP_BASE_URL?: string;
|
||||
USER_CONTENT_BASE_URL?: string;
|
||||
API_BASE_URL?: string;
|
||||
JOPLINAPP_BASE_URL?: string;
|
||||
|
||||
// ==================================================
|
||||
// Database config
|
||||
// ==================================================
|
||||
|
||||
DB_CLIENT?: string;
|
||||
DB_SLOW_QUERY_LOG_ENABLED?: string;
|
||||
DB_SLOW_QUERY_LOG_MIN_DURATION?: string; // ms
|
||||
DB_AUTO_MIGRATION?: string;
|
||||
|
||||
POSTGRES_PASSWORD?: string;
|
||||
POSTGRES_DATABASE?: string;
|
||||
POSTGRES_USER?: string;
|
||||
POSTGRES_HOST?: string;
|
||||
POSTGRES_PORT?: string;
|
||||
|
||||
// This must be the full path to the database file
|
||||
SQLITE_DATABASE?: string;
|
||||
|
||||
// ==================================================
|
||||
// Mailer config
|
||||
// ==================================================
|
||||
|
||||
MAILER_ENABLED?: string;
|
||||
MAILER_HOST?: string;
|
||||
MAILER_PORT?: string;
|
||||
MAILER_SECURE?: string;
|
||||
MAILER_AUTH_USER?: string;
|
||||
MAILER_AUTH_PASSWORD?: string;
|
||||
MAILER_NOREPLY_NAME?: string;
|
||||
MAILER_NOREPLY_EMAIL?: string;
|
||||
|
||||
SUPPORT_EMAIL?: string;
|
||||
SUPPORT_NAME?: string;
|
||||
BUSINESS_EMAIL?: string;
|
||||
|
||||
// ==================================================
|
||||
// Stripe config
|
||||
// ==================================================
|
||||
|
||||
STRIPE_SECRET_KEY?: string;
|
||||
STRIPE_WEBHOOK_SECRET?: string;
|
||||
}
|
||||
|
||||
let runningInDocker_: boolean = false;
|
||||
|
||||
export function runningInDocker(): boolean {
|
||||
return runningInDocker_;
|
||||
}
|
||||
|
||||
function envReadString(s: string, defaultValue: string = ''): string {
|
||||
return s === undefined || s === null ? defaultValue : s;
|
||||
}
|
||||
|
||||
function envReadBool(s: string, defaultValue = false): boolean {
|
||||
if (s === undefined || s === null) return defaultValue;
|
||||
return s === '1';
|
||||
}
|
||||
|
||||
function envReadInt(s: string, defaultValue: number = null): number {
|
||||
if (!s) return defaultValue === null ? 0 : defaultValue;
|
||||
const output = Number(s);
|
||||
if (isNaN(output)) throw new Error(`Invalid number: ${s}`);
|
||||
return output;
|
||||
}
|
||||
|
||||
function databaseHostFromEnv(runningInDocker: boolean, env: EnvVariables): string {
|
||||
if (env.POSTGRES_HOST) {
|
||||
// When running within Docker, the app localhost is different from the
|
||||
@@ -116,19 +35,19 @@ function databaseConfigFromEnv(runningInDocker: boolean, env: EnvVariables): Dat
|
||||
const baseConfig: DatabaseConfig = {
|
||||
client: DatabaseConfigClient.Null,
|
||||
name: '',
|
||||
slowQueryLogEnabled: envReadBool(env.DB_SLOW_QUERY_LOG_ENABLED),
|
||||
slowQueryLogMinDuration: envReadInt(env.DB_SLOW_QUERY_LOG_MIN_DURATION, 10000),
|
||||
autoMigration: envReadBool(env.DB_AUTO_MIGRATION, true),
|
||||
slowQueryLogEnabled: env.DB_SLOW_QUERY_LOG_ENABLED,
|
||||
slowQueryLogMinDuration: env.DB_SLOW_QUERY_LOG_MIN_DURATION,
|
||||
autoMigration: env.DB_AUTO_MIGRATION,
|
||||
};
|
||||
|
||||
if (env.DB_CLIENT === 'pg') {
|
||||
return {
|
||||
...baseConfig,
|
||||
client: DatabaseConfigClient.PostgreSQL,
|
||||
name: env.POSTGRES_DATABASE || 'joplin',
|
||||
user: env.POSTGRES_USER || 'joplin',
|
||||
password: env.POSTGRES_PASSWORD || 'joplin',
|
||||
port: env.POSTGRES_PORT ? Number(env.POSTGRES_PORT) : 5432,
|
||||
name: env.POSTGRES_DATABASE,
|
||||
user: env.POSTGRES_USER,
|
||||
password: env.POSTGRES_PASSWORD,
|
||||
port: env.POSTGRES_PORT,
|
||||
host: databaseHostFromEnv(runningInDocker, env) || 'localhost',
|
||||
};
|
||||
}
|
||||
@@ -143,14 +62,14 @@ function databaseConfigFromEnv(runningInDocker: boolean, env: EnvVariables): Dat
|
||||
|
||||
function mailerConfigFromEnv(env: EnvVariables): MailerConfig {
|
||||
return {
|
||||
enabled: env.MAILER_ENABLED !== '0',
|
||||
host: env.MAILER_HOST || '',
|
||||
port: Number(env.MAILER_PORT || 587),
|
||||
secure: !!Number(env.MAILER_SECURE) || true,
|
||||
authUser: env.MAILER_AUTH_USER || '',
|
||||
authPassword: env.MAILER_AUTH_PASSWORD || '',
|
||||
noReplyName: env.MAILER_NOREPLY_NAME || '',
|
||||
noReplyEmail: env.MAILER_NOREPLY_EMAIL || '',
|
||||
enabled: env.MAILER_ENABLED,
|
||||
host: env.MAILER_HOST,
|
||||
port: env.MAILER_PORT,
|
||||
secure: env.MAILER_SECURE,
|
||||
authUser: env.MAILER_AUTH_USER,
|
||||
authPassword: env.MAILER_AUTH_PASSWORD,
|
||||
noReplyName: env.MAILER_NOREPLY_NAME,
|
||||
noReplyEmail: env.MAILER_NOREPLY_EMAIL,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -158,12 +77,12 @@ function stripeConfigFromEnv(publicConfig: StripePublicConfig, env: EnvVariables
|
||||
return {
|
||||
...publicConfig,
|
||||
enabled: !!env.STRIPE_SECRET_KEY,
|
||||
secretKey: env.STRIPE_SECRET_KEY || '',
|
||||
webhookSecret: env.STRIPE_WEBHOOK_SECRET || '',
|
||||
secretKey: env.STRIPE_SECRET_KEY,
|
||||
webhookSecret: env.STRIPE_WEBHOOK_SECRET,
|
||||
};
|
||||
}
|
||||
|
||||
function baseUrlFromEnv(env: any, appPort: number): string {
|
||||
function baseUrlFromEnv(env: EnvVariables, appPort: number): string {
|
||||
if (env.APP_BASE_URL) {
|
||||
return rtrimSlashes(env.APP_BASE_URL);
|
||||
} else {
|
||||
@@ -178,12 +97,12 @@ export async function initConfig(envType: Env, env: EnvVariables, overrides: any
|
||||
|
||||
const rootDir = pathUtils.dirname(__dirname);
|
||||
const stripePublicConfig = loadStripeConfig(envType === Env.BuildTypes ? Env.Dev : envType, `${rootDir}/stripeConfig.json`);
|
||||
const appName = env.APP_NAME || 'Joplin Server';
|
||||
const appName = env.APP_NAME;
|
||||
const viewDir = `${rootDir}/src/views`;
|
||||
const appPort = env.APP_PORT ? Number(env.APP_PORT) : 22300;
|
||||
const appPort = env.APP_PORT;
|
||||
const baseUrl = baseUrlFromEnv(env, appPort);
|
||||
const apiBaseUrl = env.API_BASE_URL ? env.API_BASE_URL : baseUrl;
|
||||
const supportEmail = env.SUPPORT_EMAIL || 'SUPPORT_EMAIL'; // Defaults to "SUPPORT_EMAIL" so that server admin knows they have to set it.
|
||||
const supportEmail = env.SUPPORT_EMAIL;
|
||||
|
||||
config_ = {
|
||||
appVersion: packageJson.version,
|
||||
@@ -200,17 +119,17 @@ export async function initConfig(envType: Env, env: EnvVariables, overrides: any
|
||||
stripe: stripeConfigFromEnv(stripePublicConfig, env),
|
||||
port: appPort,
|
||||
baseUrl,
|
||||
showErrorStackTraces: (env.ERROR_STACK_TRACES === undefined && envType === Env.Dev) || env.ERROR_STACK_TRACES === '1',
|
||||
showErrorStackTraces: env.ERROR_STACK_TRACES,
|
||||
apiBaseUrl,
|
||||
userContentBaseUrl: env.USER_CONTENT_BASE_URL ? env.USER_CONTENT_BASE_URL : baseUrl,
|
||||
joplinAppBaseUrl: envReadString(env.JOPLINAPP_BASE_URL, 'https://joplinapp.org'),
|
||||
signupEnabled: env.SIGNUP_ENABLED === '1',
|
||||
termsEnabled: env.TERMS_ENABLED === '1',
|
||||
accountTypesEnabled: env.ACCOUNT_TYPES_ENABLED === '1',
|
||||
joplinAppBaseUrl: env.JOPLINAPP_BASE_URL,
|
||||
signupEnabled: env.SIGNUP_ENABLED,
|
||||
termsEnabled: env.TERMS_ENABLED,
|
||||
accountTypesEnabled: env.ACCOUNT_TYPES_ENABLED,
|
||||
supportEmail,
|
||||
supportName: env.SUPPORT_NAME || appName,
|
||||
businessEmail: env.BUSINESS_EMAIL || supportEmail,
|
||||
cookieSecure: env.COOKIES_SECURE === '1',
|
||||
cookieSecure: env.COOKIES_SECURE,
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -116,10 +116,6 @@ export const clientType = (db: DbConnection): DatabaseConfigClient => {
|
||||
return db.client.config.client;
|
||||
};
|
||||
|
||||
export const returningSupported = (db: DbConnection) => {
|
||||
return clientType(db) === DatabaseConfigClient.PostgreSQL;
|
||||
};
|
||||
|
||||
export const isPostgres = (db: DbConnection) => {
|
||||
return clientType(db) === DatabaseConfigClient.PostgreSQL;
|
||||
};
|
||||
|
||||
@@ -0,0 +1,138 @@
|
||||
export interface EnvVariables {
|
||||
// ==================================================
|
||||
// General config
|
||||
// ==================================================
|
||||
|
||||
APP_NAME: string;
|
||||
APP_PORT: number;
|
||||
SIGNUP_ENABLED: boolean;
|
||||
TERMS_ENABLED: boolean;
|
||||
ACCOUNT_TYPES_ENABLED: boolean;
|
||||
ERROR_STACK_TRACES: boolean;
|
||||
COOKIES_SECURE: boolean;
|
||||
RUNNING_IN_DOCKER: boolean;
|
||||
|
||||
// ==================================================
|
||||
// URL config
|
||||
// ==================================================
|
||||
|
||||
APP_BASE_URL: string;
|
||||
USER_CONTENT_BASE_URL: string;
|
||||
API_BASE_URL: string;
|
||||
JOPLINAPP_BASE_URL: string;
|
||||
|
||||
// ==================================================
|
||||
// Database config
|
||||
// ==================================================
|
||||
|
||||
DB_CLIENT: string;
|
||||
DB_SLOW_QUERY_LOG_ENABLED: boolean;
|
||||
DB_SLOW_QUERY_LOG_MIN_DURATION: number;
|
||||
DB_AUTO_MIGRATION: boolean;
|
||||
|
||||
POSTGRES_PASSWORD: string;
|
||||
POSTGRES_DATABASE: string;
|
||||
POSTGRES_USER: string;
|
||||
POSTGRES_HOST: string;
|
||||
POSTGRES_PORT: number;
|
||||
|
||||
// This must be the full path to the database file
|
||||
SQLITE_DATABASE: string;
|
||||
|
||||
// ==================================================
|
||||
// Mailer config
|
||||
// ==================================================
|
||||
|
||||
MAILER_ENABLED: boolean;
|
||||
MAILER_HOST: string;
|
||||
MAILER_PORT: number;
|
||||
MAILER_SECURE: boolean;
|
||||
MAILER_AUTH_USER: string;
|
||||
MAILER_AUTH_PASSWORD: string;
|
||||
MAILER_NOREPLY_NAME: string;
|
||||
MAILER_NOREPLY_EMAIL: string;
|
||||
|
||||
SUPPORT_EMAIL: string;
|
||||
SUPPORT_NAME: string;
|
||||
BUSINESS_EMAIL: string;
|
||||
|
||||
// ==================================================
|
||||
// Stripe config
|
||||
// ==================================================
|
||||
|
||||
STRIPE_SECRET_KEY: string;
|
||||
STRIPE_WEBHOOK_SECRET: string;
|
||||
}
|
||||
|
||||
const defaultEnvValues: EnvVariables = {
|
||||
APP_NAME: 'Joplin Server',
|
||||
APP_PORT: 22300,
|
||||
SIGNUP_ENABLED: false,
|
||||
TERMS_ENABLED: false,
|
||||
ACCOUNT_TYPES_ENABLED: false,
|
||||
ERROR_STACK_TRACES: false,
|
||||
COOKIES_SECURE: false,
|
||||
RUNNING_IN_DOCKER: false,
|
||||
|
||||
APP_BASE_URL: '',
|
||||
USER_CONTENT_BASE_URL: '',
|
||||
API_BASE_URL: '',
|
||||
JOPLINAPP_BASE_URL: 'https://joplinapp.org',
|
||||
|
||||
DB_CLIENT: 'sqlite3',
|
||||
DB_SLOW_QUERY_LOG_ENABLED: false,
|
||||
DB_SLOW_QUERY_LOG_MIN_DURATION: 1000,
|
||||
DB_AUTO_MIGRATION: true,
|
||||
|
||||
POSTGRES_PASSWORD: 'joplin',
|
||||
POSTGRES_DATABASE: 'joplin',
|
||||
POSTGRES_USER: 'joplin',
|
||||
POSTGRES_HOST: '',
|
||||
POSTGRES_PORT: 5432,
|
||||
|
||||
SQLITE_DATABASE: '',
|
||||
|
||||
MAILER_ENABLED: false,
|
||||
MAILER_HOST: '',
|
||||
MAILER_PORT: 587,
|
||||
MAILER_SECURE: true,
|
||||
MAILER_AUTH_USER: '',
|
||||
MAILER_AUTH_PASSWORD: '',
|
||||
MAILER_NOREPLY_NAME: '',
|
||||
MAILER_NOREPLY_EMAIL: '',
|
||||
|
||||
SUPPORT_EMAIL: 'SUPPORT_EMAIL', // Defaults to "SUPPORT_EMAIL" so that server admin knows they have to set it.
|
||||
SUPPORT_NAME: '',
|
||||
BUSINESS_EMAIL: '',
|
||||
|
||||
STRIPE_SECRET_KEY: '',
|
||||
STRIPE_WEBHOOK_SECRET: '',
|
||||
};
|
||||
|
||||
export function parseEnv(rawEnv: any, defaultOverrides: any = null): EnvVariables {
|
||||
const output: EnvVariables = {
|
||||
...defaultEnvValues,
|
||||
...defaultOverrides,
|
||||
};
|
||||
|
||||
for (const [key, value] of Object.entries(defaultEnvValues)) {
|
||||
const rawEnvValue = rawEnv[key];
|
||||
|
||||
if (rawEnvValue === undefined) continue;
|
||||
|
||||
if (typeof value === 'number') {
|
||||
const v = Number(rawEnvValue);
|
||||
if (isNaN(v)) throw new Error(`Invalid number value for env variable ${key} = ${rawEnvValue}`);
|
||||
(output as any)[key] = v;
|
||||
} else if (typeof value === 'boolean') {
|
||||
if (rawEnvValue !== '0' && rawEnvValue !== '1') throw new Error(`Invalid boolean for for env variable ${key}: ${rawEnvValue}`);
|
||||
(output as any)[key] = rawEnvValue === '1';
|
||||
} else if (typeof value === 'string') {
|
||||
(output as any)[key] = `${rawEnvValue}`;
|
||||
} else {
|
||||
throw new Error(`Invalid env default value type: ${typeof value}`);
|
||||
}
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
@@ -1,6 +1,4 @@
|
||||
import { returningSupported } from '../db';
|
||||
import { KeyValue } from '../services/database/types';
|
||||
import { msleep } from '../utils/time';
|
||||
import BaseModel from './BaseModel';
|
||||
|
||||
export enum ValueType {
|
||||
@@ -8,9 +6,7 @@ export enum ValueType {
|
||||
String = 2,
|
||||
}
|
||||
|
||||
export type Value = number | string;
|
||||
|
||||
export type ReadThenWriteHandler = (value: Value)=> Promise<Value>;
|
||||
type Value = number | string;
|
||||
|
||||
export default class KeyValueModel extends BaseModel<KeyValue> {
|
||||
|
||||
@@ -61,45 +57,6 @@ export default class KeyValueModel extends BaseModel<KeyValue> {
|
||||
return this.unserializeValue(row.type, row.value) as any;
|
||||
}
|
||||
|
||||
public async readThenWrite(key: string, handler: ReadThenWriteHandler) {
|
||||
if (!returningSupported(this.db)) {
|
||||
// While inside a transaction SQlite should lock the whole database
|
||||
// file, which should allow atomic read then write.
|
||||
await this.withTransaction(async () => {
|
||||
const value: any = await this.value(key);
|
||||
const newValue = await handler(value);
|
||||
await this.setValue(key, newValue);
|
||||
}, 'KeyValueModel::readThenWrite');
|
||||
return;
|
||||
}
|
||||
|
||||
let loopCount = 0;
|
||||
while (true) {
|
||||
const row: KeyValue = await this.db(this.tableName).where('key', '=', key).first();
|
||||
const newValue = await handler(row ? row.value : null);
|
||||
|
||||
let previousValue: Value = null;
|
||||
if (row) {
|
||||
previousValue = row.value;
|
||||
} else {
|
||||
await this.setValue(key, newValue);
|
||||
previousValue = newValue;
|
||||
}
|
||||
|
||||
const updatedRows = await this
|
||||
.db(this.tableName)
|
||||
.update({ value: newValue }, ['id'])
|
||||
.where('key', '=', key)
|
||||
.where('value', '=', previousValue);
|
||||
|
||||
if (updatedRows.length) return;
|
||||
|
||||
loopCount++;
|
||||
if (loopCount >= 10) throw new Error(`Could not update key: ${key}`);
|
||||
await msleep(10000 * Math.random());
|
||||
}
|
||||
}
|
||||
|
||||
public async deleteValue(key: string): Promise<void> {
|
||||
await this.db(this.tableName).where('key', '=', key).delete();
|
||||
}
|
||||
@@ -108,8 +65,4 @@ export default class KeyValueModel extends BaseModel<KeyValue> {
|
||||
throw new Error('Call ::deleteValue()');
|
||||
}
|
||||
|
||||
public async deleteAll(): Promise<void> {
|
||||
await this.db(this.tableName).delete();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,94 +0,0 @@
|
||||
// Note that a lot of the testing logic is done from
|
||||
// synchronizer_LockHandler.test so to fully test that it works, Joplin Server
|
||||
// should be setup as a sync target for the test units.
|
||||
|
||||
import { ErrorConflict, ErrorUnprocessableEntity } from '../utils/errors';
|
||||
import { beforeAllDb, afterAllTests, beforeEachDb, models, createUserAndSession, expectHttpError } from '../utils/testing/testUtils';
|
||||
import { LockType, LockClientType, defaultLockTtl } from '@joplin/lib/services/synchronizer/LockHandler';
|
||||
|
||||
describe('LockModel', function() {
|
||||
|
||||
beforeAll(async () => {
|
||||
await beforeAllDb('LockModel');
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
await afterAllTests();
|
||||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
await beforeEachDb();
|
||||
});
|
||||
|
||||
test('should allow exclusive lock if the sync locks have expired', async function() {
|
||||
jest.useFakeTimers('modern');
|
||||
|
||||
const { user } = await createUserAndSession(1);
|
||||
|
||||
const t1 = new Date('2020-01-01').getTime();
|
||||
jest.setSystemTime(t1);
|
||||
|
||||
await models().lock().acquireLock(user.id, LockType.Sync, LockClientType.Desktop, '1111');
|
||||
await models().lock().acquireLock(user.id, LockType.Sync, LockClientType.Desktop, '2222');
|
||||
|
||||
// First confirm that it's not possible to acquire an exclusive lock if
|
||||
// there are sync locks.
|
||||
await expectHttpError(async () => models().lock().acquireLock(user.id, LockType.Exclusive, LockClientType.Desktop, '3333'), ErrorConflict.httpCode);
|
||||
|
||||
jest.setSystemTime(t1 + defaultLockTtl + 1);
|
||||
|
||||
// Now that the sync locks have expired check that it's possible to
|
||||
// acquire a sync lock.
|
||||
const exclusiveLock = await models().lock().acquireLock(user.id, LockType.Exclusive, LockClientType.Desktop, '3333');
|
||||
expect(exclusiveLock).toBeTruthy();
|
||||
|
||||
jest.useRealTimers();
|
||||
});
|
||||
|
||||
test('should keep user locks separated', async function() {
|
||||
const { user: user1 } = await createUserAndSession(1);
|
||||
const { user: user2 } = await createUserAndSession(2);
|
||||
|
||||
await models().lock().acquireLock(user1.id, LockType.Sync, LockClientType.Desktop, '1111');
|
||||
|
||||
// If user 1 tries to get an exclusive lock, it should fail
|
||||
await expectHttpError(async () => models().lock().acquireLock(user1.id, LockType.Exclusive, LockClientType.Desktop, '3333'), ErrorConflict.httpCode);
|
||||
|
||||
// But it should work for user 2
|
||||
const exclusiveLock = await models().lock().acquireLock(user2.id, LockType.Exclusive, LockClientType.Desktop, '3333');
|
||||
expect(exclusiveLock).toBeTruthy();
|
||||
});
|
||||
|
||||
test('should validate locks', async function() {
|
||||
const { user: user1 } = await createUserAndSession(1);
|
||||
|
||||
await expectHttpError(async () => models().lock().acquireLock(user1.id, 'wrongtype' as any, LockClientType.Desktop, '1111'), ErrorUnprocessableEntity.httpCode);
|
||||
await expectHttpError(async () => models().lock().acquireLock(user1.id, LockType.Exclusive, 'wrongclienttype' as any, '1111'), ErrorUnprocessableEntity.httpCode);
|
||||
await expectHttpError(async () => models().lock().acquireLock(user1.id, LockType.Exclusive, LockClientType.Desktop, 'veryverylongclientidveryverylongclientidveryverylongclientidveryverylongclientid'), ErrorUnprocessableEntity.httpCode);
|
||||
});
|
||||
|
||||
test('should expire locks', async function() {
|
||||
const { user } = await createUserAndSession(1);
|
||||
|
||||
jest.useFakeTimers('modern');
|
||||
|
||||
const t1 = new Date('2020-01-01').getTime();
|
||||
jest.setSystemTime(t1);
|
||||
|
||||
await models().lock().acquireLock(user.id, LockType.Sync, LockClientType.Desktop, '1111');
|
||||
const lock1 = (await models().lock().allLocks(user.id))[0];
|
||||
|
||||
jest.setSystemTime(t1 + models().lock().lockTtl + 1);
|
||||
|
||||
// If we call this again, at the same time it should expire old timers.
|
||||
await models().lock().acquireLock(user.id, LockType.Sync, LockClientType.Desktop, '2222');
|
||||
|
||||
expect((await models().lock().allLocks(user.id)).length).toBe(1);
|
||||
const lock2 = (await models().lock().allLocks(user.id))[0];
|
||||
|
||||
expect(lock1.id).not.toBe(lock2.id);
|
||||
|
||||
jest.useRealTimers();
|
||||
});
|
||||
|
||||
});
|
||||
@@ -1,174 +0,0 @@
|
||||
import BaseModel, { UuidType } from './BaseModel';
|
||||
import { Uuid } from '../services/database/types';
|
||||
import { LockType, Lock, LockClientType, defaultLockTtl, activeLock } from '@joplin/lib/services/synchronizer/LockHandler';
|
||||
import { Value } from './KeyValueModel';
|
||||
import { ErrorConflict, ErrorUnprocessableEntity } from '../utils/errors';
|
||||
import uuidgen from '../utils/uuidgen';
|
||||
|
||||
export default class LockModel extends BaseModel<Lock> {
|
||||
|
||||
private lockTtl_: number = defaultLockTtl;
|
||||
|
||||
protected get tableName(): string {
|
||||
return 'locks';
|
||||
}
|
||||
|
||||
protected uuidType(): UuidType {
|
||||
return UuidType.Native;
|
||||
}
|
||||
|
||||
public get lockTtl() {
|
||||
return this.lockTtl_;
|
||||
}
|
||||
|
||||
public async allLocks(userId: Uuid): Promise<Lock[]> {
|
||||
const userKey = `locks::${userId}`;
|
||||
const v = await this.models().keyValue().value<string>(userKey);
|
||||
return v ? JSON.parse(v) : [];
|
||||
}
|
||||
|
||||
protected async validate(lock: Lock): Promise<Lock> {
|
||||
if (![LockType.Sync, LockType.Exclusive].includes(lock.type)) throw new ErrorUnprocessableEntity(`Invalid lock type: ${lock.type}`);
|
||||
if (![LockClientType.Desktop, LockClientType.Mobile, LockClientType.Cli].includes(lock.clientType)) throw new ErrorUnprocessableEntity(`Invalid lock client type: ${lock.clientType}`);
|
||||
if (lock.clientId.length > 64) throw new ErrorUnprocessableEntity(`Invalid client ID length: ${lock.clientId}`);
|
||||
return lock;
|
||||
}
|
||||
|
||||
private expireLocks(locks: Lock[]): Lock[] {
|
||||
const cutOffTime = Date.now() - this.lockTtl;
|
||||
|
||||
const output: Lock[] = [];
|
||||
|
||||
for (const lock of locks) {
|
||||
if (lock.updatedTime > cutOffTime) {
|
||||
output.push(lock);
|
||||
}
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
private async acquireSyncLock(userId: Uuid, clientType: LockClientType, clientId: string): Promise<Lock> {
|
||||
const userKey = `locks::${userId}`;
|
||||
let output: Lock = null;
|
||||
|
||||
await this.models().keyValue().readThenWrite(userKey, async (value: Value) => {
|
||||
let locks: Lock[] = value ? JSON.parse(value as string) : [];
|
||||
locks = this.expireLocks(locks);
|
||||
|
||||
const exclusiveLock = activeLock(locks, new Date(), this.lockTtl, LockType.Exclusive);
|
||||
|
||||
if (exclusiveLock) {
|
||||
throw new ErrorConflict(`Cannot acquire lock because there is already an exclusive lock for client: ${exclusiveLock.clientType} #${exclusiveLock.clientId}`, 'hasExclusiveLock');
|
||||
}
|
||||
|
||||
const syncLock = activeLock(locks, new Date(), this.lockTtl, LockType.Sync, clientType, clientId);
|
||||
|
||||
if (syncLock) {
|
||||
output = {
|
||||
...syncLock,
|
||||
updatedTime: Date.now(),
|
||||
};
|
||||
|
||||
locks = locks.map(l => l.id === syncLock.id ? output : l);
|
||||
} else {
|
||||
output = {
|
||||
id: uuidgen(),
|
||||
type: LockType.Sync,
|
||||
clientId,
|
||||
clientType,
|
||||
updatedTime: Date.now(),
|
||||
};
|
||||
|
||||
locks.push(output);
|
||||
}
|
||||
|
||||
return JSON.stringify(locks);
|
||||
});
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
private async acquireExclusiveLock(userId: Uuid, clientType: LockClientType, clientId: string): Promise<Lock> {
|
||||
const userKey = `locks::${userId}`;
|
||||
let output: Lock = null;
|
||||
|
||||
await this.models().keyValue().readThenWrite(userKey, async (value: Value) => {
|
||||
let locks: Lock[] = value ? JSON.parse(value as string) : [];
|
||||
locks = this.expireLocks(locks);
|
||||
|
||||
const exclusiveLock = activeLock(locks, new Date(), this.lockTtl, LockType.Exclusive);
|
||||
|
||||
if (exclusiveLock) {
|
||||
if (exclusiveLock.clientId === clientId) {
|
||||
locks = locks.filter(l => l.id !== exclusiveLock.id);
|
||||
output = {
|
||||
...exclusiveLock,
|
||||
updatedTime: Date.now(),
|
||||
};
|
||||
|
||||
locks.push(output);
|
||||
|
||||
return JSON.stringify(locks);
|
||||
} else {
|
||||
throw new ErrorConflict(`Cannot acquire lock because there is already an exclusive lock for client: ${exclusiveLock.clientType} #${exclusiveLock.clientId}`, 'hasExclusiveLock');
|
||||
}
|
||||
}
|
||||
|
||||
const syncLock = activeLock(locks, new Date(), this.lockTtl, LockType.Sync);
|
||||
|
||||
if (syncLock) {
|
||||
if (syncLock.clientId === clientId) {
|
||||
locks = locks.filter(l => l.id !== syncLock.id);
|
||||
} else {
|
||||
throw new ErrorConflict(`Cannot acquire exclusive lock because there is an active sync lock for client: ${syncLock.clientType} #${syncLock.clientId}`, 'hasSyncLock');
|
||||
}
|
||||
}
|
||||
|
||||
output = {
|
||||
id: uuidgen(),
|
||||
type: LockType.Exclusive,
|
||||
clientId,
|
||||
clientType,
|
||||
updatedTime: Date.now(),
|
||||
};
|
||||
|
||||
locks.push(output);
|
||||
|
||||
return JSON.stringify(locks);
|
||||
});
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
public async acquireLock(userId: Uuid, type: LockType, clientType: LockClientType, clientId: string): Promise<Lock> {
|
||||
await this.validate({ type, clientType, clientId });
|
||||
|
||||
if (type === LockType.Sync) {
|
||||
return this.acquireSyncLock(userId, clientType, clientId);
|
||||
} else {
|
||||
return this.acquireExclusiveLock(userId, clientType, clientId);
|
||||
}
|
||||
}
|
||||
|
||||
public async releaseLock(userId: Uuid, type: LockType, clientType: LockClientType, clientId: string) {
|
||||
await this.validate({ type, clientType, clientId });
|
||||
|
||||
const userKey = `locks::${userId}`;
|
||||
|
||||
await this.models().keyValue().readThenWrite(userKey, async (value: Value) => {
|
||||
let locks: Lock[] = value ? JSON.parse(value as string) : [];
|
||||
locks = this.expireLocks(locks);
|
||||
|
||||
for (let i = locks.length - 1; i >= 0; i--) {
|
||||
const lock = locks[i];
|
||||
if (lock.type === type && lock.clientType === clientType && lock.clientId === clientId) {
|
||||
locks.splice(i, 1);
|
||||
}
|
||||
}
|
||||
|
||||
return JSON.stringify(locks);
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
@@ -72,7 +72,6 @@ import SubscriptionModel from './SubscriptionModel';
|
||||
import UserFlagModel from './UserFlagModel';
|
||||
import EventModel from './EventModel';
|
||||
import { Config } from '../utils/types';
|
||||
import LockModel from './LockModel';
|
||||
|
||||
export class Models {
|
||||
|
||||
@@ -148,10 +147,6 @@ export class Models {
|
||||
return new EventModel(this.db_, newModelFactory, this.config_);
|
||||
}
|
||||
|
||||
public lock() {
|
||||
return new LockModel(this.db_, newModelFactory, this.config_);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default function newModelFactory(db: DbConnection, config: Config): Models {
|
||||
|
||||
@@ -2,10 +2,9 @@ import config from '../../config';
|
||||
import { clearDatabase, createTestUsers, CreateTestUsersOptions } from '../../tools/debugTools';
|
||||
import { bodyFields } from '../../utils/requestUtils';
|
||||
import Router from '../../utils/Router';
|
||||
import { Env, RouteType } from '../../utils/types';
|
||||
import { RouteType } from '../../utils/types';
|
||||
import { SubPath } from '../../utils/routeUtils';
|
||||
import { AppContext } from '../../utils/types';
|
||||
import { ErrorForbidden } from '../../utils/errors';
|
||||
|
||||
const router = new Router(RouteType.Api);
|
||||
|
||||
@@ -18,10 +17,7 @@ interface Query {
|
||||
}
|
||||
|
||||
router.post('api/debug', async (_path: SubPath, ctx: AppContext) => {
|
||||
if (config().env !== Env.Dev) throw new ErrorForbidden();
|
||||
|
||||
const query: Query = (await bodyFields(ctx.req)) as Query;
|
||||
const models = ctx.joplin.models;
|
||||
|
||||
console.info(`Action: ${query.action}`);
|
||||
|
||||
@@ -37,10 +33,6 @@ router.post('api/debug', async (_path: SubPath, ctx: AppContext) => {
|
||||
if (query.action === 'clearDatabase') {
|
||||
await clearDatabase(ctx.joplin.db);
|
||||
}
|
||||
|
||||
if (query.action === 'clearKeyValues') {
|
||||
await models.keyValue().deleteAll();
|
||||
}
|
||||
});
|
||||
|
||||
export default router;
|
||||
@@ -1,32 +0,0 @@
|
||||
import { LockType, LockClientType, lockNameToObject } from '@joplin/lib/services/synchronizer/LockHandler';
|
||||
import { bodyFields } from '../../utils/requestUtils';
|
||||
import Router from '../../utils/Router';
|
||||
import { SubPath } from '../../utils/routeUtils';
|
||||
import { AppContext, RouteType } from '../../utils/types';
|
||||
|
||||
const router = new Router(RouteType.Api);
|
||||
|
||||
interface PostFields {
|
||||
type: LockType;
|
||||
clientType: LockClientType;
|
||||
clientId: string;
|
||||
}
|
||||
|
||||
router.post('api/locks', async (_path: SubPath, ctx: AppContext) => {
|
||||
const fields = await bodyFields<PostFields>(ctx.req);
|
||||
return ctx.joplin.models.lock().acquireLock(ctx.joplin.owner.id, fields.type, fields.clientType, fields.clientId);
|
||||
});
|
||||
|
||||
router.del('api/locks/:id', async (path: SubPath, ctx: AppContext) => {
|
||||
const lock = lockNameToObject(path.id);
|
||||
await ctx.joplin.models.lock().releaseLock(ctx.joplin.owner.id, lock.type, lock.clientType, lock.clientId);
|
||||
});
|
||||
|
||||
router.get('api/locks', async (_path: SubPath, ctx: AppContext) => {
|
||||
return {
|
||||
items: await ctx.joplin.models.lock().allLocks(ctx.joplin.owner.id),
|
||||
has_more: false,
|
||||
};
|
||||
});
|
||||
|
||||
export default router;
|
||||
@@ -10,7 +10,6 @@ import apiSessions from './api/sessions';
|
||||
import apiShares from './api/shares';
|
||||
import apiShareUsers from './api/share_users';
|
||||
import apiUsers from './api/users';
|
||||
import apiLocks from './api/locks';
|
||||
|
||||
import indexChanges from './index/changes';
|
||||
import indexHelp from './index/help';
|
||||
@@ -42,7 +41,6 @@ const routes: Routers = {
|
||||
'api/share_users': apiShareUsers,
|
||||
'api/shares': apiShares,
|
||||
'api/users': apiUsers,
|
||||
'api/locks': apiLocks,
|
||||
|
||||
'changes': indexChanges,
|
||||
'home': indexHome,
|
||||
|
||||
@@ -79,8 +79,8 @@ export class ErrorUnprocessableEntity extends ApiError {
|
||||
export class ErrorConflict extends ApiError {
|
||||
public static httpCode: number = 409;
|
||||
|
||||
public constructor(message: string = 'Conflict', code: string = undefined) {
|
||||
super(message, ErrorConflict.httpCode, code);
|
||||
public constructor(message: string = 'Conflict') {
|
||||
super(message, ErrorConflict.httpCode);
|
||||
Object.setPrototypeOf(this, ErrorConflict.prototype);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -86,7 +86,7 @@ export async function beforeAllDb(unitName: string, createDbOptions: CreateDbOpt
|
||||
await initConfig(Env.Dev, {
|
||||
SQLITE_DATABASE: createdDbPath_,
|
||||
SUPPORT_EMAIL: 'testing@localhost',
|
||||
}, {
|
||||
} as any, {
|
||||
tempDir: tempDir,
|
||||
});
|
||||
|
||||
|
||||
Generated
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@joplin/tools",
|
||||
"version": "2.5.0",
|
||||
"version": "2.6.2",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@joplin/tools",
|
||||
"version": "2.5.0",
|
||||
"version": "2.6.2",
|
||||
"description": "Various tools for Joplin",
|
||||
"main": "index.js",
|
||||
"author": "Laurent Cozic",
|
||||
@@ -20,8 +20,8 @@
|
||||
},
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@joplin/lib": "~2.5",
|
||||
"@joplin/renderer": "~2.5",
|
||||
"@joplin/lib": "^2.6.2",
|
||||
"@joplin/renderer": "^2.6.2",
|
||||
"execa": "^4.1.0",
|
||||
"fs-extra": "^4.0.3",
|
||||
"gettext-parser": "^1.3.0",
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@joplin/turndown-plugin-gfm",
|
||||
"version": "1.0.40",
|
||||
"version": "1.0.42",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
},
|
||||
"version": "1.0.40",
|
||||
"version": "1.0.42",
|
||||
"author": "Dom Christie",
|
||||
"main": "lib/turndown-plugin-gfm.cjs.js",
|
||||
"devDependencies": {
|
||||
|
||||
Generated
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@joplin/turndown",
|
||||
"version": "4.0.58",
|
||||
"version": "4.0.60",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@joplin/turndown",
|
||||
"description": "A library that converts HTML to Markdown",
|
||||
"version": "4.0.58",
|
||||
"version": "4.0.60",
|
||||
"author": "Dom Christie",
|
||||
"main": "lib/turndown.cjs.js",
|
||||
"publishConfig": {
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
# Joplin Android app changelog
|
||||
|
||||
## [android-v2.6.1](https://github.com/laurent22/joplin/releases/tag/android-v2.6.1) (Pre-release) - 2021-11-02T20:49:53Z
|
||||
|
||||
- Improved: Upgraded React Native from 0.64 to 0.66 (66e79cc)
|
||||
- Fixed: Fixed potential infinite loop when Joplin Server session is invalid (c5569ef)
|
||||
|
||||
## [android-v2.5.5](https://github.com/laurent22/joplin/releases/tag/android-v2.5.5) (Pre-release) - 2021-10-31T11:03:16Z
|
||||
|
||||
- New: Add padding around beta text editor (365e152)
|
||||
|
||||
@@ -1,5 +1,19 @@
|
||||
# Joplin terminal app changelog
|
||||
|
||||
## [cli-v2.6.1](https://github.com/laurent22/joplin/releases/tag/cli-v2.6.1) - 2021-11-03T11:33:18Z
|
||||
|
||||
- New: Add support for public-private key pairs and improved master password support (#5438)
|
||||
- New: Added mechanism to migrate default settings to new values (72db8e4)
|
||||
- Improved: Add Markdown + Front Matter exporter/importer (#5465) (#5224 by [@CalebJohn](https://github.com/CalebJohn))
|
||||
- Improved: Ensure that shared notebook children are not deleted when shared, unshared and shared again, and a conflict happens (ccf9882)
|
||||
- Improved: Improved Joplin Server configuration check to better handle disabled accounts (72c1235)
|
||||
- Improved: Improved handling of expired sessions when using Joplin Server (33249ca) (ace1118)
|
||||
- Fixed: Certain attachments were not being automatically deleted (#932)
|
||||
- Fixed: Fix default sync target (4b39d30)
|
||||
- Fixed: Fixed potential infinite loop when Joplin Server session is invalid (c5569ef)
|
||||
- Fixed: Fixed running out of memory when importing large ENEX files (#5543)
|
||||
- Fixed: Ignore newline between quotes while spliting batch (#5540) (#5341 by Kingsley Yung)
|
||||
|
||||
## [cli-v2.4.1](https://github.com/laurent22/joplin/releases/tag/cli-v2.4.1) - 2021-09-29T15:28:01Z
|
||||
|
||||
- New: Add a way to disable a master key (7faa58e)
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
# Joplin Server Changelog
|
||||
|
||||
## [server-v2.5.10](https://github.com/laurent22/joplin/releases/tag/server-v2.5.10) - 2021-11-02T14:45:54Z
|
||||
|
||||
- New: Add unique constraint on name and owner ID of items table (f7a18ba)
|
||||
- Fixed: Fixed issue that could cause server to return empty items in some rare cases (99ea4b7)
|
||||
|
||||
## [server-v2.5.9](https://github.com/laurent22/joplin/releases/tag/server-v2.5.9) - 2021-10-28T19:43:41Z
|
||||
|
||||
- Improved: Remove session expiration for now (4a2af32)
|
||||
|
||||
Reference in new issue
Block a user