WebDAV: Improve update and reset of remote connection errors #1781
This commit is contained in:
parent
45922f8db0
commit
736b03f87f
22 changed files with 392 additions and 137 deletions
24
frontend/package-lock.json
generated
24
frontend/package-lock.json
generated
|
@ -3755,9 +3755,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/css-declaration-sorter": {
|
||||
"version": "6.2.1",
|
||||
"resolved": "https://registry.npmjs.org/css-declaration-sorter/-/css-declaration-sorter-6.2.1.tgz",
|
||||
"integrity": "sha512-4qvWVSwnc5f1ZxCe80LccU5aenhZdhuCCyaOSMhr3dDAOTXtJNfAvthW+5x0UV4k1pWzE1EwNk4ztSDCk6WzKw==",
|
||||
"version": "6.2.2",
|
||||
"resolved": "https://registry.npmjs.org/css-declaration-sorter/-/css-declaration-sorter-6.2.2.tgz",
|
||||
"integrity": "sha512-Ufadglr88ZLsrvS11gjeu/40Lw74D9Am/Jpr3LlYm5Q4ZP5KdlUhG+6u2EjyXeZcxmZ2h1ebCKngDjolpeLHpg==",
|
||||
"engines": {
|
||||
"node": "^10 || ^12 || >=14"
|
||||
},
|
||||
|
@ -4321,9 +4321,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/electron-to-chromium": {
|
||||
"version": "1.4.95",
|
||||
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.95.tgz",
|
||||
"integrity": "sha512-h2VAMV/hPtmAeiDkwA8c5sjS+cWt6GlQL4ERdrOUWu7cRIG5IRk9uwR9f0utP+hPJ9ZZsADTq9HpbuT46eBYAg=="
|
||||
"version": "1.4.96",
|
||||
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.96.tgz",
|
||||
"integrity": "sha512-DPNjvNGPabv6FcyjzLAN4C0psN/GgD9rSGvMTuv81SeXG/EX3mCz0wiw9N1tUEnfQXYCJi3H8M0oFPRziZh7rw=="
|
||||
},
|
||||
"node_modules/emoji-regex": {
|
||||
"version": "8.0.0",
|
||||
|
@ -15331,9 +15331,9 @@
|
|||
}
|
||||
},
|
||||
"css-declaration-sorter": {
|
||||
"version": "6.2.1",
|
||||
"resolved": "https://registry.npmjs.org/css-declaration-sorter/-/css-declaration-sorter-6.2.1.tgz",
|
||||
"integrity": "sha512-4qvWVSwnc5f1ZxCe80LccU5aenhZdhuCCyaOSMhr3dDAOTXtJNfAvthW+5x0UV4k1pWzE1EwNk4ztSDCk6WzKw==",
|
||||
"version": "6.2.2",
|
||||
"resolved": "https://registry.npmjs.org/css-declaration-sorter/-/css-declaration-sorter-6.2.2.tgz",
|
||||
"integrity": "sha512-Ufadglr88ZLsrvS11gjeu/40Lw74D9Am/Jpr3LlYm5Q4ZP5KdlUhG+6u2EjyXeZcxmZ2h1ebCKngDjolpeLHpg==",
|
||||
"requires": {}
|
||||
},
|
||||
"css-has-pseudo": {
|
||||
|
@ -15730,9 +15730,9 @@
|
|||
}
|
||||
},
|
||||
"electron-to-chromium": {
|
||||
"version": "1.4.95",
|
||||
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.95.tgz",
|
||||
"integrity": "sha512-h2VAMV/hPtmAeiDkwA8c5sjS+cWt6GlQL4ERdrOUWu7cRIG5IRk9uwR9f0utP+hPJ9ZZsADTq9HpbuT46eBYAg=="
|
||||
"version": "1.4.96",
|
||||
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.96.tgz",
|
||||
"integrity": "sha512-DPNjvNGPabv6FcyjzLAN4C0psN/GgD9rSGvMTuv81SeXG/EX3mCz0wiw9N1tUEnfQXYCJi3H8M0oFPRziZh7rw=="
|
||||
},
|
||||
"emoji-regex": {
|
||||
"version": "8.0.0",
|
||||
|
|
|
@ -67,6 +67,7 @@
|
|||
</template>
|
||||
<script>
|
||||
import Account from "model/account";
|
||||
import * as options from "options/options";
|
||||
|
||||
export default {
|
||||
name: 'PAccountCreateDialog',
|
||||
|
@ -75,6 +76,7 @@ export default {
|
|||
},
|
||||
data() {
|
||||
return {
|
||||
options: options,
|
||||
showPassword: false,
|
||||
loading: false,
|
||||
search: null,
|
||||
|
|
|
@ -255,6 +255,30 @@
|
|||
:items="items.types">
|
||||
</v-select>
|
||||
</v-flex>
|
||||
<v-flex xs12 sm6 class="px-2">
|
||||
<v-select
|
||||
v-model="model.AccTimeout"
|
||||
:label="$gettext('Timeout')"
|
||||
browser-autocomplete="off"
|
||||
hide-details
|
||||
color="secondary-dark"
|
||||
item-text="text"
|
||||
item-value="value"
|
||||
:items="options.Timeouts()">
|
||||
</v-select>
|
||||
</v-flex>
|
||||
<v-flex xs12 sm6 class="px-2">
|
||||
<v-select
|
||||
v-model="model.RetryLimit"
|
||||
:label="$gettext('Retry Limit')"
|
||||
browser-autocomplete="off"
|
||||
hide-details
|
||||
color="secondary-dark"
|
||||
item-text="text"
|
||||
item-value="value"
|
||||
:items="options.RetryLimits()">
|
||||
</v-select>
|
||||
</v-flex>
|
||||
</v-layout>
|
||||
<v-layout row wrap>
|
||||
<v-flex xs12 text-xs-right class="pt-3 pb-0">
|
||||
|
|
|
@ -41,15 +41,15 @@ msgstr ""
|
|||
msgid "%{n} pictures found"
|
||||
msgstr ""
|
||||
|
||||
#: src/options/options.js:325
|
||||
#: src/options/options.js:371
|
||||
msgid "1 hour"
|
||||
msgstr ""
|
||||
|
||||
#: src/options/options.js:327
|
||||
#: src/options/options.js:373
|
||||
msgid "12 hours"
|
||||
msgstr ""
|
||||
|
||||
#: src/options/options.js:326
|
||||
#: src/options/options.js:372
|
||||
msgid "4 hours"
|
||||
msgstr ""
|
||||
|
||||
|
@ -71,7 +71,7 @@ msgstr ""
|
|||
|
||||
#: src/component/navigation.vue:58
|
||||
#: src/dialog/share/upload.vue:112
|
||||
#: src/model/account.js:97
|
||||
#: src/model/account.js:98
|
||||
#: src/pages/settings.vue:74
|
||||
msgid "Account"
|
||||
msgstr ""
|
||||
|
@ -107,7 +107,7 @@ msgid "Add pictures from search results by selecting them."
|
|||
msgstr ""
|
||||
|
||||
#: src/dialog/account/add.vue:6
|
||||
#: src/pages/settings/sync.vue:46
|
||||
#: src/pages/settings/sync.vue:47
|
||||
msgid "Add Server"
|
||||
msgstr ""
|
||||
|
||||
|
@ -129,23 +129,23 @@ msgstr ""
|
|||
msgid "Advanced"
|
||||
msgstr ""
|
||||
|
||||
#: src/options/options.js:335
|
||||
#: src/options/options.js:381
|
||||
msgid "After 1 day"
|
||||
msgstr ""
|
||||
|
||||
#: src/options/options.js:336
|
||||
#: src/options/options.js:382
|
||||
msgid "After 3 days"
|
||||
msgstr ""
|
||||
|
||||
#: src/options/options.js:337
|
||||
#: src/options/options.js:383
|
||||
msgid "After 7 days"
|
||||
msgstr ""
|
||||
|
||||
#: src/options/options.js:339
|
||||
#: src/options/options.js:385
|
||||
msgid "After one month"
|
||||
msgstr ""
|
||||
|
||||
#: src/options/options.js:341
|
||||
#: src/options/options.js:387
|
||||
msgid "After one year"
|
||||
msgstr ""
|
||||
|
||||
|
@ -154,11 +154,11 @@ msgstr ""
|
|||
msgid "After selecting pictures from search results, you can add them to an album using the context menu."
|
||||
msgstr ""
|
||||
|
||||
#: src/options/options.js:340
|
||||
#: src/options/options.js:386
|
||||
msgid "After two months"
|
||||
msgstr ""
|
||||
|
||||
#: src/options/options.js:338
|
||||
#: src/options/options.js:384
|
||||
msgid "After two weeks"
|
||||
msgstr ""
|
||||
|
||||
|
@ -272,7 +272,7 @@ msgstr ""
|
|||
msgid "Any private photos and videos remain private and won't be shared."
|
||||
msgstr ""
|
||||
|
||||
#: src/dialog/account/edit.vue:471
|
||||
#: src/dialog/account/edit.vue:477
|
||||
msgid "API Key"
|
||||
msgstr ""
|
||||
|
||||
|
@ -358,19 +358,19 @@ msgstr ""
|
|||
msgid "Bio"
|
||||
msgstr ""
|
||||
|
||||
#: src/options/options.js:360
|
||||
#: src/options/options.js:406
|
||||
msgid "Black"
|
||||
msgstr ""
|
||||
|
||||
#: src/options/options.js:373
|
||||
#: src/options/options.js:419
|
||||
msgid "Blackman: Lanczos Modification, Less Ringing Artifacts"
|
||||
msgstr ""
|
||||
|
||||
#: src/options/options.js:356
|
||||
#: src/options/options.js:402
|
||||
msgid "Blue"
|
||||
msgstr ""
|
||||
|
||||
#: src/options/options.js:357
|
||||
#: src/options/options.js:403
|
||||
msgid "Brown"
|
||||
msgstr ""
|
||||
|
||||
|
@ -382,7 +382,7 @@ msgstr ""
|
|||
msgid "Browse indexed files and folders in Library."
|
||||
msgstr ""
|
||||
|
||||
#: src/options/options.js:367
|
||||
#: src/options/options.js:413
|
||||
msgid "Bug Report"
|
||||
msgstr ""
|
||||
|
||||
|
@ -433,8 +433,8 @@ msgstr ""
|
|||
msgid "Can't select more items"
|
||||
msgstr ""
|
||||
|
||||
#: src/dialog/account/add.vue:15
|
||||
#: src/dialog/account/edit.vue:94
|
||||
#: src/dialog/account/add.vue:17
|
||||
#: src/dialog/account/edit.vue:102
|
||||
#: src/dialog/account/remove.vue:15
|
||||
#: src/dialog/album/delete.vue:15
|
||||
#: src/dialog/album/edit.vue:42
|
||||
|
@ -516,12 +516,12 @@ msgstr ""
|
|||
msgid "Confidence"
|
||||
msgstr ""
|
||||
|
||||
#: src/dialog/account/add.vue:16
|
||||
#: src/dialog/account/add.vue:18
|
||||
msgid "Connect"
|
||||
msgstr ""
|
||||
|
||||
#: src/dialog/webdav.vue:4
|
||||
#: src/pages/settings/sync.vue:42
|
||||
#: src/pages/settings/sync.vue:43
|
||||
msgid "Connect via WebDAV"
|
||||
msgstr ""
|
||||
|
||||
|
@ -580,7 +580,7 @@ msgstr ""
|
|||
msgid "Creating thumbnails for"
|
||||
msgstr ""
|
||||
|
||||
#: src/options/options.js:375
|
||||
#: src/options/options.js:421
|
||||
msgid "Cubic: Moderate Quality, Good Performance"
|
||||
msgstr ""
|
||||
|
||||
|
@ -588,11 +588,11 @@ msgstr ""
|
|||
msgid "Current Password"
|
||||
msgstr ""
|
||||
|
||||
#: src/options/options.js:364
|
||||
#: src/options/options.js:410
|
||||
msgid "Customer Support"
|
||||
msgstr ""
|
||||
|
||||
#: src/options/options.js:355
|
||||
#: src/options/options.js:401
|
||||
msgid "Cyan"
|
||||
msgstr ""
|
||||
|
||||
|
@ -600,7 +600,7 @@ msgstr ""
|
|||
msgid "Cyano"
|
||||
msgstr ""
|
||||
|
||||
#: src/options/options.js:328
|
||||
#: src/options/options.js:374
|
||||
msgid "Daily"
|
||||
msgstr ""
|
||||
|
||||
|
@ -613,6 +613,7 @@ msgid "Debug Logs"
|
|||
msgstr ""
|
||||
|
||||
#: src/options/options.js:194
|
||||
#: src/options/options.js:325
|
||||
msgid "Default"
|
||||
msgstr ""
|
||||
|
||||
|
@ -738,7 +739,7 @@ msgstr ""
|
|||
msgid "Don't use TensorFlow for image classification."
|
||||
msgstr ""
|
||||
|
||||
#: src/options/options.js:368
|
||||
#: src/options/options.js:414
|
||||
msgid "Donations"
|
||||
msgstr ""
|
||||
|
||||
|
@ -768,7 +769,7 @@ msgstr ""
|
|||
msgid "Download"
|
||||
msgstr ""
|
||||
|
||||
#: src/dialog/account/edit.vue:320
|
||||
#: src/dialog/account/edit.vue:322
|
||||
msgid "Download remote files"
|
||||
msgstr ""
|
||||
|
||||
|
@ -777,7 +778,7 @@ msgid "Download single files and zip archives."
|
|||
msgstr ""
|
||||
|
||||
#: src/component/album/clipboard.vue:86
|
||||
#: src/component/album/toolbar.vue:106
|
||||
#: src/component/album/toolbar.vue:105
|
||||
#: src/component/file/clipboard.vue:46
|
||||
#: src/component/label/clipboard.vue:59
|
||||
#: src/component/photo/cards.vue:63
|
||||
|
@ -868,7 +869,7 @@ msgstr ""
|
|||
msgid "Estimates"
|
||||
msgstr ""
|
||||
|
||||
#: src/options/options.js:329
|
||||
#: src/options/options.js:375
|
||||
msgid "Every two days"
|
||||
msgstr ""
|
||||
|
||||
|
@ -945,7 +946,7 @@ msgstr ""
|
|||
msgid "Favorites"
|
||||
msgstr ""
|
||||
|
||||
#: src/options/options.js:366
|
||||
#: src/options/options.js:412
|
||||
msgid "Feature Request"
|
||||
msgstr ""
|
||||
|
||||
|
@ -1024,7 +1025,7 @@ msgstr ""
|
|||
msgid "Getting Support"
|
||||
msgstr ""
|
||||
|
||||
#: src/options/options.js:350
|
||||
#: src/options/options.js:396
|
||||
msgid "Gold"
|
||||
msgstr ""
|
||||
|
||||
|
@ -1032,11 +1033,11 @@ msgstr ""
|
|||
msgid "Grayscale"
|
||||
msgstr ""
|
||||
|
||||
#: src/options/options.js:353
|
||||
#: src/options/options.js:399
|
||||
msgid "Green"
|
||||
msgstr ""
|
||||
|
||||
#: src/options/options.js:359
|
||||
#: src/options/options.js:405
|
||||
msgid "Grey"
|
||||
msgstr ""
|
||||
|
||||
|
@ -1072,6 +1073,10 @@ msgstr ""
|
|||
msgid "Hide photos that have been moved to archive."
|
||||
msgstr ""
|
||||
|
||||
#: src/options/options.js:329
|
||||
msgid "High"
|
||||
msgstr ""
|
||||
|
||||
#: src/dialog/photo/files.vue:109
|
||||
#: src/dialog/photo/files.vue:106
|
||||
msgid "High Dynamic Range (HDR)"
|
||||
|
@ -1224,7 +1229,7 @@ msgstr ""
|
|||
msgid "Labels deleted"
|
||||
msgstr ""
|
||||
|
||||
#: src/options/options.js:374
|
||||
#: src/options/options.js:420
|
||||
msgid "Lanczos: Detail Preservation, Minimal Artifacts"
|
||||
msgstr ""
|
||||
|
||||
|
@ -1233,7 +1238,7 @@ msgid "Language"
|
|||
msgstr ""
|
||||
|
||||
#: src/pages/settings/sync.vue:30
|
||||
msgid "Last Backup"
|
||||
msgid "Last Sync"
|
||||
msgstr ""
|
||||
|
||||
#: src/dialog/photo/details.vue:291
|
||||
|
@ -1272,7 +1277,7 @@ msgstr ""
|
|||
msgid "Like"
|
||||
msgstr ""
|
||||
|
||||
#: src/options/options.js:352
|
||||
#: src/options/options.js:398
|
||||
msgid "Lime"
|
||||
msgstr ""
|
||||
|
||||
|
@ -1280,7 +1285,7 @@ msgstr ""
|
|||
msgid "Limit reached, showing first %{n} files"
|
||||
msgstr ""
|
||||
|
||||
#: src/options/options.js:376
|
||||
#: src/options/options.js:422
|
||||
msgid "Linear: Very Smooth, Best Performance"
|
||||
msgstr ""
|
||||
|
||||
|
@ -1348,7 +1353,11 @@ msgstr ""
|
|||
msgid "Longitude"
|
||||
msgstr ""
|
||||
|
||||
#: src/options/options.js:346
|
||||
#: src/options/options.js:333
|
||||
msgid "Low"
|
||||
msgstr ""
|
||||
|
||||
#: src/options/options.js:392
|
||||
msgid "Magenta"
|
||||
msgstr ""
|
||||
|
||||
|
@ -1448,7 +1457,7 @@ msgstr ""
|
|||
#: src/component/photo/cards.vue:39
|
||||
#: src/component/photo/list.vue:47
|
||||
#: src/component/photo/list.vue:235
|
||||
#: src/dialog/account/edit.vue:396
|
||||
#: src/dialog/account/edit.vue:402
|
||||
#: src/dialog/album/edit.vue:106
|
||||
#: src/dialog/photo/files.vue:71
|
||||
#: src/dialog/photo/files.vue:68
|
||||
|
@ -1481,8 +1490,8 @@ msgstr ""
|
|||
msgid "Name too long"
|
||||
msgstr ""
|
||||
|
||||
#: src/options/options.js:324
|
||||
#: src/options/options.js:334
|
||||
#: src/options/options.js:370
|
||||
#: src/options/options.js:380
|
||||
#: src/pages/settings/sync.vue:50
|
||||
msgid "Never"
|
||||
msgstr ""
|
||||
|
@ -1557,7 +1566,7 @@ msgstr ""
|
|||
msgid "No recently edited pictures"
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/settings/sync.vue:47
|
||||
#: src/pages/settings/sync.vue:48
|
||||
msgid "No servers configured."
|
||||
msgstr ""
|
||||
|
||||
|
@ -1588,6 +1597,7 @@ msgid "Non-photographic and low-quality images require a review before they appe
|
|||
msgstr ""
|
||||
|
||||
#: src/options/options.js:261
|
||||
#: src/options/options.js:337
|
||||
msgid "None"
|
||||
msgstr ""
|
||||
|
||||
|
@ -1599,7 +1609,7 @@ msgstr ""
|
|||
msgid "Note you may manually manage your originals folder and importing is optional."
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/settings/sync.vue:34
|
||||
#: src/pages/settings/sync.vue:35
|
||||
msgid "Note:"
|
||||
msgstr ""
|
||||
|
||||
|
@ -1631,7 +1641,7 @@ msgstr ""
|
|||
msgid "Oldest first"
|
||||
msgstr ""
|
||||
|
||||
#: src/options/options.js:330
|
||||
#: src/options/options.js:376
|
||||
msgid "Once a week"
|
||||
msgstr ""
|
||||
|
||||
|
@ -1680,7 +1690,7 @@ msgstr ""
|
|||
msgid "or ask in our Community Chat"
|
||||
msgstr ""
|
||||
|
||||
#: src/options/options.js:349
|
||||
#: src/options/options.js:395
|
||||
msgid "Orange"
|
||||
msgstr ""
|
||||
|
||||
|
@ -1706,7 +1716,7 @@ msgstr ""
|
|||
msgid "Originals"
|
||||
msgstr ""
|
||||
|
||||
#: src/options/options.js:369
|
||||
#: src/options/options.js:415
|
||||
msgid "Other"
|
||||
msgstr ""
|
||||
|
||||
|
@ -1727,7 +1737,7 @@ msgid "Panoramas"
|
|||
msgstr ""
|
||||
|
||||
#: src/dialog/account/add.vue:105
|
||||
#: src/dialog/account/edit.vue:450
|
||||
#: src/dialog/account/edit.vue:456
|
||||
#: src/dialog/share.vue:24
|
||||
#: src/pages/auth/login.vue:96
|
||||
#: src/pages/auth/login.vue:98
|
||||
|
@ -1777,7 +1787,7 @@ msgstr ""
|
|||
msgid "Photos"
|
||||
msgstr ""
|
||||
|
||||
#: src/options/options.js:347
|
||||
#: src/options/options.js:393
|
||||
msgid "Pink"
|
||||
msgstr ""
|
||||
|
||||
|
@ -1826,7 +1836,7 @@ msgstr ""
|
|||
msgid "post your question in GitHub Discussions"
|
||||
msgstr ""
|
||||
|
||||
#: src/dialog/account/edit.vue:337
|
||||
#: src/dialog/account/edit.vue:340
|
||||
msgid "Preserve filenames"
|
||||
msgstr ""
|
||||
|
||||
|
@ -1864,7 +1874,7 @@ msgstr ""
|
|||
msgid "Private"
|
||||
msgstr ""
|
||||
|
||||
#: src/options/options.js:365
|
||||
#: src/options/options.js:411
|
||||
msgid "Product Feedback"
|
||||
msgstr ""
|
||||
|
||||
|
@ -1873,7 +1883,7 @@ msgstr ""
|
|||
msgid "Projection"
|
||||
msgstr ""
|
||||
|
||||
#: src/options/options.js:345
|
||||
#: src/options/options.js:391
|
||||
msgid "Purple"
|
||||
msgstr ""
|
||||
|
||||
|
@ -1942,7 +1952,7 @@ msgstr ""
|
|||
msgid "Recognizes faces so that specific people can be found."
|
||||
msgstr ""
|
||||
|
||||
#: src/options/options.js:348
|
||||
#: src/options/options.js:394
|
||||
msgid "Red"
|
||||
msgstr ""
|
||||
|
||||
|
@ -2002,6 +2012,10 @@ msgstr ""
|
|||
msgid "Restore"
|
||||
msgstr ""
|
||||
|
||||
#: src/dialog/account/edit.vue:535
|
||||
msgid "Retry Limit"
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/settings/account.vue:106
|
||||
msgid "Retype Password"
|
||||
msgstr ""
|
||||
|
@ -2011,7 +2025,7 @@ msgstr ""
|
|||
msgid "Review"
|
||||
msgstr ""
|
||||
|
||||
#: src/dialog/account/edit.vue:97
|
||||
#: src/dialog/account/edit.vue:105
|
||||
#: src/dialog/album/edit.vue:45
|
||||
#: src/dialog/share.vue:61
|
||||
msgid "Save"
|
||||
|
@ -2088,7 +2102,7 @@ msgid "Server"
|
|||
msgstr ""
|
||||
|
||||
#: src/dialog/account/add.vue:65
|
||||
#: src/dialog/account/edit.vue:414
|
||||
#: src/dialog/account/edit.vue:420
|
||||
#: src/dialog/share.vue:22
|
||||
msgid "Service URL"
|
||||
msgstr ""
|
||||
|
@ -2291,7 +2305,7 @@ msgstr ""
|
|||
msgid "Sync"
|
||||
msgstr ""
|
||||
|
||||
#: src/dialog/account/edit.vue:371
|
||||
#: src/dialog/account/edit.vue:377
|
||||
msgid "Sync raw and video files"
|
||||
msgstr ""
|
||||
|
||||
|
@ -2303,7 +2317,7 @@ msgstr ""
|
|||
msgid "Taken"
|
||||
msgstr ""
|
||||
|
||||
#: src/options/options.js:354
|
||||
#: src/options/options.js:400
|
||||
msgid "Teal"
|
||||
msgstr ""
|
||||
|
||||
|
@ -2338,7 +2352,7 @@ msgid "This currently is a sponsor feature to thank everyone who supports the de
|
|||
msgstr ""
|
||||
|
||||
#: src/dialog/webdav.vue:17
|
||||
#: src/pages/settings/sync.vue:36
|
||||
#: src/pages/settings/sync.vue:37
|
||||
msgid "This mounts the originals folder as a network drive and allows you to open, edit, and delete files from your computer or smartphone as if they were local."
|
||||
msgstr ""
|
||||
|
||||
|
@ -2354,6 +2368,10 @@ msgstr ""
|
|||
msgid "Time Zone"
|
||||
msgstr ""
|
||||
|
||||
#: src/dialog/account/edit.vue:515
|
||||
msgid "Timeout"
|
||||
msgstr ""
|
||||
|
||||
#: src/component/photo/list.vue:43
|
||||
#: src/dialog/photo/details.vue:97
|
||||
#: src/dialog/photo/info.vue:45
|
||||
|
@ -2407,7 +2425,7 @@ msgstr ""
|
|||
msgid "Try again using other filters or keywords."
|
||||
msgstr ""
|
||||
|
||||
#: src/dialog/account/edit.vue:489
|
||||
#: src/dialog/account/edit.vue:495
|
||||
#: src/dialog/photo/files.vue:89
|
||||
#: src/dialog/photo/files.vue:86
|
||||
#: src/dialog/photo/files.vue:37
|
||||
|
@ -2504,7 +2522,7 @@ msgstr ""
|
|||
msgid "Upload failed"
|
||||
msgstr ""
|
||||
|
||||
#: src/dialog/account/edit.vue:354
|
||||
#: src/dialog/account/edit.vue:359
|
||||
msgid "Upload local files"
|
||||
msgstr ""
|
||||
|
||||
|
@ -2543,7 +2561,7 @@ msgid "User Interface"
|
|||
msgstr ""
|
||||
|
||||
#: src/dialog/account/add.vue:84
|
||||
#: src/dialog/account/edit.vue:432
|
||||
#: src/dialog/account/edit.vue:438
|
||||
#: src/dialog/share.vue:23
|
||||
msgid "Username"
|
||||
msgstr ""
|
||||
|
@ -2595,7 +2613,7 @@ msgstr ""
|
|||
msgid "WebDAV clients can connect to PhotoPrism using the following URL:"
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/settings/sync.vue:35
|
||||
#: src/pages/settings/sync.vue:36
|
||||
msgid "WebDAV clients, like Microsoft’s Windows Explorer or Apple's Finder, can connect directly to PhotoPrism."
|
||||
msgstr ""
|
||||
|
||||
|
@ -2604,7 +2622,7 @@ msgstr ""
|
|||
msgid "WebDAV Upload"
|
||||
msgstr ""
|
||||
|
||||
#: src/options/options.js:358
|
||||
#: src/options/options.js:404
|
||||
msgid "White"
|
||||
msgstr ""
|
||||
|
||||
|
@ -2613,7 +2631,7 @@ msgstr ""
|
|||
msgid "Year"
|
||||
msgstr ""
|
||||
|
||||
#: src/options/options.js:351
|
||||
#: src/options/options.js:397
|
||||
msgid "Yellow"
|
||||
msgstr ""
|
||||
|
||||
|
|
|
@ -39,6 +39,7 @@ export class Account extends RestModel {
|
|||
AccKey: "",
|
||||
AccUser: "",
|
||||
AccPass: "",
|
||||
AccTimeout: "",
|
||||
AccError: "",
|
||||
AccErrors: 0,
|
||||
AccShare: true,
|
||||
|
|
|
@ -320,6 +320,52 @@ export const PhotoTypes = () => [
|
|||
},
|
||||
];
|
||||
|
||||
export const Timeouts = () => [
|
||||
{
|
||||
text: $gettext("Default"),
|
||||
value: "",
|
||||
},
|
||||
{
|
||||
text: $gettext("High"),
|
||||
value: "high",
|
||||
},
|
||||
{
|
||||
text: $gettext("Low"),
|
||||
value: "low",
|
||||
},
|
||||
{
|
||||
text: $gettext("None"),
|
||||
value: "none",
|
||||
},
|
||||
];
|
||||
|
||||
export const RetryLimits = () => [
|
||||
{
|
||||
text: "None",
|
||||
value: -1,
|
||||
},
|
||||
{
|
||||
text: "1",
|
||||
value: 1,
|
||||
},
|
||||
{
|
||||
text: "2",
|
||||
value: 2,
|
||||
},
|
||||
{
|
||||
text: "3",
|
||||
value: 3,
|
||||
},
|
||||
{
|
||||
text: "4",
|
||||
value: 4,
|
||||
},
|
||||
{
|
||||
text: "5",
|
||||
value: 5,
|
||||
},
|
||||
];
|
||||
|
||||
export const Intervals = () => [
|
||||
{ value: 0, text: $gettext("Never") },
|
||||
{ value: 3600, text: $gettext("1 hour") },
|
||||
|
|
|
@ -28,7 +28,8 @@
|
|||
<v-btn icon small flat :ripple="false"
|
||||
class="action-toggle-sync"
|
||||
@click.stop.prevent="editSync(props.item)">
|
||||
<v-icon v-if="props.item.AccSync" color="secondary-dark">sync</v-icon>
|
||||
<v-icon v-if="props.item.AccErrors" color="secondary-dark" :title="props.item.AccError">report_problem</v-icon>
|
||||
<v-icon v-else-if="props.item.AccSync" color="secondary-dark">sync</v-icon>
|
||||
<v-icon v-else color="secondary-dark">sync_disabled</v-icon>
|
||||
</v-btn>
|
||||
</td>
|
||||
|
@ -114,7 +115,7 @@ export default {
|
|||
{text: this.$gettext('Upload'), value: 'AccShare', sortable: false, align: 'center'},
|
||||
{text: this.$gettext('Sync'), value: 'AccSync', sortable: false, align: 'center'},
|
||||
{
|
||||
text: this.$gettext('Last Backup'),
|
||||
text: this.$gettext('Last Sync'),
|
||||
value: 'SyncDate',
|
||||
sortable: false,
|
||||
class: 'hidden-sm-and-down',
|
||||
|
|
|
@ -22,6 +22,14 @@ var StatusCommand = cli.Command{
|
|||
// statusAction checks if the web server is running.
|
||||
func statusAction(ctx *cli.Context) error {
|
||||
conf := config.NewConfig(ctx)
|
||||
|
||||
// Create new http.Client instance.
|
||||
//
|
||||
// NOTE: Timeout specifies a time limit for requests made by
|
||||
// this Client. The timeout includes connection time, any
|
||||
// redirects, and reading the response body. The timer remains
|
||||
// running after Get, Head, Post, or Do return and will
|
||||
// interrupt reading of the Response.Body.
|
||||
client := &http.Client{Timeout: 10 * time.Second}
|
||||
|
||||
url := fmt.Sprintf("http://%s:%d/api/v1/status", conf.HttpHost(), conf.HttpPort())
|
||||
|
|
|
@ -2,15 +2,17 @@ package entity
|
|||
|
||||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"sort"
|
||||
"time"
|
||||
|
||||
"github.com/ulule/deepcopier"
|
||||
|
||||
"github.com/photoprism/photoprism/internal/form"
|
||||
"github.com/photoprism/photoprism/internal/remote"
|
||||
"github.com/photoprism/photoprism/internal/remote/webdav"
|
||||
"github.com/photoprism/photoprism/pkg/fs"
|
||||
"github.com/photoprism/photoprism/pkg/txt"
|
||||
"github.com/ulule/deepcopier"
|
||||
)
|
||||
|
||||
const (
|
||||
|
@ -32,11 +34,12 @@ type Account struct {
|
|||
AccKey string `gorm:"type:VARBINARY(255);"`
|
||||
AccUser string `gorm:"type:VARBINARY(255);"`
|
||||
AccPass string `gorm:"type:VARBINARY(255);"`
|
||||
AccTimeout string `gorm:"type:VARBINARY(16);"` // Request timeout: default, high, medium, low, none
|
||||
AccError string `gorm:"type:VARBINARY(512);"`
|
||||
AccErrors int
|
||||
AccShare bool
|
||||
AccSync bool
|
||||
RetryLimit int
|
||||
AccErrors int // Number of general account errors, there are counters for individual files too.
|
||||
AccShare bool // Manual upload enabled, see SharePath, ShareSize, and ShareExpires.
|
||||
AccSync bool // Background sync enabled, see SyncDownload and SyncUpload.
|
||||
RetryLimit int // Number of remote request retry attempts.
|
||||
SharePath string `gorm:"type:VARBINARY(500);"`
|
||||
ShareSize string `gorm:"type:VARBINARY(16);"`
|
||||
ShareExpires int
|
||||
|
@ -59,6 +62,7 @@ func CreateAccount(form form.Account) (model *Account, err error) {
|
|||
ShareSize: "",
|
||||
ShareExpires: 0,
|
||||
RetryLimit: 3,
|
||||
AccTimeout: string(webdav.TimeoutDefault),
|
||||
SyncStatus: AccountSyncStatusRefresh,
|
||||
}
|
||||
|
||||
|
@ -67,18 +71,66 @@ func CreateAccount(form form.Account) (model *Account, err error) {
|
|||
return model, err
|
||||
}
|
||||
|
||||
// LogError updates the account error count and message.
|
||||
func (m *Account) LogError(err error) error {
|
||||
if err == nil {
|
||||
return m.ResetErrors(true, true)
|
||||
}
|
||||
|
||||
// Update error message and increase count.
|
||||
m.AccError = err.Error()
|
||||
m.AccErrors++
|
||||
|
||||
// Disable sharing when retry limit is reached.
|
||||
if m.RetryLimit > 0 && m.AccErrors > m.RetryLimit {
|
||||
m.AccShare = false
|
||||
}
|
||||
|
||||
// Update fields in database.
|
||||
return m.Updates(Account{AccError: m.AccError, AccErrors: m.AccErrors, AccShare: m.AccShare})
|
||||
}
|
||||
|
||||
// ResetErrors resets the account and related file error messages and counters.
|
||||
func (m *Account) ResetErrors(share, sync bool) error {
|
||||
if !share && !sync {
|
||||
return nil
|
||||
}
|
||||
|
||||
if m.ID == 0 {
|
||||
return fmt.Errorf("invalid account id")
|
||||
}
|
||||
|
||||
if share {
|
||||
if err := Db().Model(FileShare{}).Where("account_id = ?", m.ID).Updates(Values{"error": "", "errors": 0}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if sync {
|
||||
if err := Db().Model(FileSync{}).Where("account_id = ?", m.ID).Updates(Values{"error": "", "errors": 0}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
m.AccError = ""
|
||||
m.AccErrors = 0
|
||||
|
||||
return m.Updates(Values{"acc_error": m.AccError, "acc_errors": m.AccErrors})
|
||||
}
|
||||
|
||||
// SaveForm saves the entity using form data and stores it in the database.
|
||||
func (m *Account) SaveForm(form form.Account) error {
|
||||
db := Db()
|
||||
|
||||
// Copy model values from form.
|
||||
if err := deepcopier.Copy(m).From(form); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// TODO: Support for other remote services in addition to WebDAV.
|
||||
if m.AccType != remote.ServiceWebDAV {
|
||||
m.AccShare = false
|
||||
m.AccSync = false
|
||||
m.AccShare = false // Disable manual upload.
|
||||
m.AccSync = false // Disable background sync.
|
||||
}
|
||||
|
||||
// Prevent two-way sync, see https://github.com/photoprism/photoprism/issues/1785
|
||||
|
@ -86,23 +138,39 @@ func (m *Account) SaveForm(form form.Account) error {
|
|||
m.SyncUpload = false
|
||||
}
|
||||
|
||||
// Set defaults.
|
||||
// Set default manual upload folder if empty.
|
||||
if m.SharePath == "" {
|
||||
m.SharePath = "/"
|
||||
}
|
||||
|
||||
// Set default background sync folder if empty.
|
||||
if m.SyncPath == "" {
|
||||
m.SyncPath = "/"
|
||||
}
|
||||
|
||||
// Number of remote request retry attempts.
|
||||
if m.RetryLimit < -1 {
|
||||
m.RetryLimit = -1 // Disabled.
|
||||
} else if m.RetryLimit > 999 {
|
||||
m.RetryLimit = 999 // 999 retries max.
|
||||
}
|
||||
|
||||
// Refresh after performing changes.
|
||||
if m.AccSync && m.SyncStatus == AccountSyncStatusSynced {
|
||||
m.SyncStatus = AccountSyncStatusRefresh
|
||||
}
|
||||
|
||||
// Reset share/sync errors.
|
||||
if err := m.ResetErrors(m.AccShare, m.AccSync); err != nil {
|
||||
log.Debugf("account: %s", err)
|
||||
log.Errorf("account: failed to reset errors")
|
||||
}
|
||||
|
||||
// Ensure account name and owner are not too long.
|
||||
m.AccName = txt.Clip(m.AccName, txt.ClipName)
|
||||
m.AccOwner = txt.Clip(m.AccOwner, txt.ClipName)
|
||||
|
||||
// Save changes.
|
||||
return db.Save(m).Error
|
||||
}
|
||||
|
||||
|
@ -114,12 +182,18 @@ func (m *Account) Delete() error {
|
|||
// Directories returns a list of directories or albums in an account.
|
||||
func (m *Account) Directories() (result fs.FileInfos, err error) {
|
||||
if m.AccType == remote.ServiceWebDAV {
|
||||
c := webdav.New(m.AccURL, m.AccUser, m.AccPass)
|
||||
result, err = c.Directories("/", true, webdav.SyncTimeout)
|
||||
client := webdav.New(m.AccURL, m.AccUser, m.AccPass, webdav.Timeout(m.AccTimeout))
|
||||
result, err = client.Directories("/", true, 0)
|
||||
}
|
||||
|
||||
// Sort directory list.
|
||||
sort.Sort(result)
|
||||
|
||||
// Update error count and message.
|
||||
if err := m.LogError(err); err != nil {
|
||||
log.Warnf("account: %s", err)
|
||||
}
|
||||
|
||||
return result, err
|
||||
}
|
||||
|
||||
|
|
|
@ -49,7 +49,7 @@ func (m *FileSync) Updates(values interface{}) error {
|
|||
return UnscopedDb().Model(m).UpdateColumns(values).Error
|
||||
}
|
||||
|
||||
// Updates a column in the database.
|
||||
// Update a column in the database.
|
||||
func (m *FileSync) Update(attr string, value interface{}) error {
|
||||
return UnscopedDb().Model(m).UpdateColumn(attr, value).Error
|
||||
}
|
||||
|
|
|
@ -15,10 +15,11 @@ type Account struct {
|
|||
AccKey string `json:"AccKey"`
|
||||
AccUser string `json:"AccUser"`
|
||||
AccPass string `json:"AccPass"`
|
||||
AccTimeout string `json:"AccTimeout"` // Request timeout: default, high, medium, low, none
|
||||
AccError string `json:"AccError"`
|
||||
AccShare bool `json:"AccShare"`
|
||||
AccSync bool `json:"AccSync"`
|
||||
RetryLimit int `json:"RetryLimit"`
|
||||
AccShare bool `json:"AccShare"` // Manual upload enabled, see SharePath, ShareSize, and ShareExpires.
|
||||
AccSync bool `json:"AccSync"` // Background sync enabled, see SyncDownload and SyncUpload.
|
||||
RetryLimit int `json:"RetryLimit"` // Number of remote request retry attempts.
|
||||
SharePath string `json:"SharePath"`
|
||||
ShareSize string `json:"ShareSize"`
|
||||
ShareExpires int `json:"ShareExpires"`
|
||||
|
|
|
@ -138,7 +138,15 @@ func (c *Config) Refresh() (err error) {
|
|||
|
||||
c.Sanitize()
|
||||
|
||||
// Create new http.Client instance.
|
||||
//
|
||||
// NOTE: Timeout specifies a time limit for requests made by
|
||||
// this Client. The timeout includes connection time, any
|
||||
// redirects, and reading the response body. The timer remains
|
||||
// running after Get, Head, Post, or Do return and will
|
||||
// interrupt reading of the Response.Body.
|
||||
client := &http.Client{Timeout: 60 * time.Second}
|
||||
|
||||
url := ServiceURL
|
||||
method := http.MethodPost
|
||||
|
||||
|
|
|
@ -55,7 +55,15 @@ func (c *Config) SendFeedback(f form.Feedback) (err error) {
|
|||
feedback.UserAgent = f.UserAgent
|
||||
feedback.ApiKey = c.Key
|
||||
|
||||
// Create new http.Client instance.
|
||||
//
|
||||
// NOTE: Timeout specifies a time limit for requests made by
|
||||
// this Client. The timeout includes connection time, any
|
||||
// redirects, and reading the response body. The timer remains
|
||||
// running after Get, Head, Post, or Do return and will
|
||||
// interrupt reading of the Response.Body.
|
||||
client := &http.Client{Timeout: 60 * time.Second}
|
||||
|
||||
url := fmt.Sprintf(FeedbackURL, c.Key)
|
||||
method := http.MethodPost
|
||||
|
||||
|
|
|
@ -36,10 +36,10 @@ var ReverseLookupURL = "https://places.photoprism.app/v1/location/%s"
|
|||
|
||||
var Retries = 3
|
||||
var RetryDelay = 33 * time.Millisecond
|
||||
var client = &http.Client{Timeout: 60 * time.Second}
|
||||
|
||||
// FindLocation retrieves location details from the backend API.
|
||||
func FindLocation(id string) (result Location, err error) {
|
||||
|
||||
// Normalize S2 Cell ID.
|
||||
id = s2.NormalizeToken(id)
|
||||
|
||||
|
@ -99,6 +99,15 @@ func FindLocation(id string) (result Location, err error) {
|
|||
|
||||
var r *http.Response
|
||||
|
||||
// Create new http.Client.
|
||||
//
|
||||
// NOTE: Timeout specifies a time limit for requests made by
|
||||
// this Client. The timeout includes connection time, any
|
||||
// redirects, and reading the response body. The timer remains
|
||||
// running after Get, Head, Post, or Do return and will
|
||||
// interrupt reading of the Response.Body.
|
||||
client := &http.Client{Timeout: 60 * time.Second}
|
||||
|
||||
// Perform request.
|
||||
for i := 0; i < Retries; i++ {
|
||||
r, err = client.Do(req)
|
||||
|
|
|
@ -34,8 +34,6 @@ import (
|
|||
"time"
|
||||
)
|
||||
|
||||
var client = &http.Client{Timeout: 30 * time.Second} // TODO: Change timeout if needed
|
||||
|
||||
const (
|
||||
ServiceWebDAV = "webdav"
|
||||
ServiceFacebook = "facebook"
|
||||
|
@ -57,6 +55,16 @@ func HttpOk(method, rawUrl string) bool {
|
|||
return false
|
||||
}
|
||||
|
||||
// Create new http.Client instance.
|
||||
//
|
||||
// NOTE: Timeout specifies a time limit for requests made by
|
||||
// this Client. The timeout includes connection time, any
|
||||
// redirects, and reading the response body. The timer remains
|
||||
// running after Get, Head, Post, or Do return and will
|
||||
// interrupt reading of the Response.Body.
|
||||
client := &http.Client{Timeout: 30 * time.Second}
|
||||
|
||||
// Send request to see if it fails.
|
||||
if resp, err := client.Do(req); err != nil {
|
||||
return false
|
||||
} else if resp.StatusCode < 400 {
|
||||
|
|
|
@ -33,28 +33,57 @@ import (
|
|||
"runtime/debug"
|
||||
"time"
|
||||
|
||||
"github.com/studio-b12/gowebdav"
|
||||
|
||||
"github.com/photoprism/photoprism/internal/event"
|
||||
"github.com/photoprism/photoprism/pkg/fs"
|
||||
"github.com/studio-b12/gowebdav"
|
||||
"github.com/photoprism/photoprism/pkg/sanitize"
|
||||
)
|
||||
|
||||
// Global log instance.
|
||||
var log = event.Log
|
||||
|
||||
const SyncTimeout = time.Second * 45
|
||||
const AsyncTimeout = time.Minute * 20
|
||||
type Timeout string
|
||||
|
||||
// Request Timeout options.
|
||||
const (
|
||||
TimeoutHigh Timeout = "high" // 120 * Second
|
||||
TimeoutDefault Timeout = "" // 60 * Second
|
||||
TimeoutMedium Timeout = "medium" // 60 * Second
|
||||
TimeoutLow Timeout = "low" // 30 * Second
|
||||
TimeoutNone Timeout = "none" // 0
|
||||
)
|
||||
|
||||
// Second represents a second on which other timeouts are based.
|
||||
const Second = time.Second
|
||||
|
||||
// MaxRequestDuration is the maximum request duration e.g. for recursive retrieval of large remote directory structures.
|
||||
const MaxRequestDuration = 30 * time.Minute
|
||||
|
||||
// Durations maps Timeout options to specific time durations.
|
||||
var Durations = map[Timeout]time.Duration{
|
||||
TimeoutHigh: 120 * Second,
|
||||
TimeoutDefault: 60 * Second,
|
||||
TimeoutMedium: 60 * Second,
|
||||
TimeoutLow: 30 * Second,
|
||||
TimeoutNone: 0,
|
||||
}
|
||||
|
||||
// Client represents a gowebdav.Client wrapper.
|
||||
type Client struct {
|
||||
client *gowebdav.Client
|
||||
client *gowebdav.Client
|
||||
timeout Timeout
|
||||
}
|
||||
|
||||
// New creates a new WebDAV client.
|
||||
func New(url, user, pass string) Client {
|
||||
clt := gowebdav.NewClient(url, user, pass)
|
||||
|
||||
clt.SetTimeout(10 * time.Minute) // TODO: Change timeout if needed
|
||||
func New(url, user, pass string, timeout Timeout) Client {
|
||||
// Create a new gowebdav.Client instance.
|
||||
client := gowebdav.NewClient(url, user, pass)
|
||||
|
||||
// Create a new gowebdav.Client wrapper.
|
||||
result := Client{
|
||||
client: clt,
|
||||
client: client,
|
||||
timeout: timeout,
|
||||
}
|
||||
|
||||
return result
|
||||
|
@ -95,10 +124,14 @@ func (c Client) Files(dir string) (result fs.FileInfos, err error) {
|
|||
return result, nil
|
||||
}
|
||||
|
||||
// Directories returns all sub directories in path as string slice.
|
||||
// Directories returns all subdirectories in a path as string slice.
|
||||
func (c Client) Directories(root string, recursive bool, timeout time.Duration) (result fs.FileInfos, err error) {
|
||||
start := time.Now()
|
||||
|
||||
if timeout == 0 {
|
||||
timeout = Durations[c.timeout]
|
||||
}
|
||||
|
||||
result, err = c.fetchDirs(root, recursive, start, timeout)
|
||||
|
||||
if time.Now().Sub(start) >= timeout {
|
||||
|
@ -129,7 +162,7 @@ func (c Client) fetchDirs(root string, recursive bool, start time.Time, timeout
|
|||
|
||||
result = append(result, info)
|
||||
|
||||
if recursive && time.Now().Sub(start) < timeout {
|
||||
if recursive && (timeout < time.Second || time.Now().Sub(start) < timeout) {
|
||||
subDirs, err := c.fetchDirs(info.Abs, true, start, timeout)
|
||||
|
||||
if err != nil {
|
||||
|
@ -147,35 +180,41 @@ func (c Client) fetchDirs(root string, recursive bool, start time.Time, timeout
|
|||
func (c Client) Download(from, to string, force bool) (err error) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
err = fmt.Errorf("webdav: %s (panic while downloading)\nstack: %s", r, debug.Stack())
|
||||
log.Errorf("webdav: %s (panic)\nstack: %s", r, sanitize.Log(from))
|
||||
err = fmt.Errorf("webdav: unexpected error while downloading %s", sanitize.Log(from))
|
||||
}
|
||||
}()
|
||||
|
||||
// Skip if file already exists.
|
||||
if _, err := os.Stat(to); err == nil && !force {
|
||||
return fmt.Errorf("webdav: download skipped, %s already exists", to)
|
||||
return fmt.Errorf("webdav: download skipped, %s already exists", sanitize.Log(to))
|
||||
}
|
||||
|
||||
dir := path.Dir(to)
|
||||
dirInfo, err := os.Stat(dir)
|
||||
|
||||
if err != nil {
|
||||
// Create directory
|
||||
// Create local storage path.
|
||||
if err := os.MkdirAll(dir, os.ModePerm); err != nil {
|
||||
return fmt.Errorf("webdav: cannot create %s (%s)", dir, err)
|
||||
return fmt.Errorf("webdav: cannot create folder %s (%s)", sanitize.Log(dir), err)
|
||||
}
|
||||
} else if !dirInfo.IsDir() {
|
||||
return fmt.Errorf("webdav: %s is not a folder", dir)
|
||||
return fmt.Errorf("webdav: %s is not a folder", sanitize.Log(dir))
|
||||
}
|
||||
|
||||
var bytes []byte
|
||||
|
||||
// Start download.
|
||||
bytes, err = c.client.Read(from)
|
||||
|
||||
// Error?
|
||||
if err != nil {
|
||||
return err
|
||||
log.Errorf("webdav: %s", sanitize.Log(err.Error()))
|
||||
return fmt.Errorf("webdav: failed downloading %s", sanitize.Log(from))
|
||||
}
|
||||
|
||||
return os.WriteFile(to, bytes, 0644)
|
||||
// Write data to file and return.
|
||||
return os.WriteFile(to, bytes, os.ModePerm)
|
||||
}
|
||||
|
||||
// DownloadDir downloads all files from a remote to a local directory.
|
||||
|
@ -191,9 +230,9 @@ func (c Client) DownloadDir(from, to string, recursive, force bool) (errs []erro
|
|||
|
||||
if _, err = os.Stat(dest); err == nil {
|
||||
// File already exists.
|
||||
msg := fmt.Errorf("webdav: %s exists", dest)
|
||||
errs = append(errs, msg)
|
||||
msg := fmt.Errorf("webdav: %s already exists", sanitize.Log(dest))
|
||||
log.Warn(msg)
|
||||
errs = append(errs, msg)
|
||||
continue
|
||||
}
|
||||
|
||||
|
@ -209,7 +248,7 @@ func (c Client) DownloadDir(from, to string, recursive, force bool) (errs []erro
|
|||
return errs
|
||||
}
|
||||
|
||||
dirs, err := c.Directories(from, false, AsyncTimeout)
|
||||
dirs, err := c.Directories(from, false, MaxRequestDuration)
|
||||
|
||||
for _, dir := range dirs {
|
||||
errs = append(errs, c.DownloadDir(dir.Abs, to, true, force)...)
|
||||
|
@ -245,7 +284,7 @@ func (c Client) Upload(from, to string) (err error) {
|
|||
_ = file.Close()
|
||||
}(file)
|
||||
|
||||
return c.client.WriteStream(to, file, 0644)
|
||||
return c.client.WriteStream(to, file, os.ModePerm)
|
||||
}
|
||||
|
||||
// Delete deletes a single file or directory on a remote server.
|
||||
|
|
|
@ -5,9 +5,10 @@ import (
|
|||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/photoprism/photoprism/pkg/fs"
|
||||
"github.com/photoprism/photoprism/pkg/rnd"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
const (
|
||||
|
@ -17,13 +18,13 @@ const (
|
|||
)
|
||||
|
||||
func TestConnect(t *testing.T) {
|
||||
c := New(testUrl, testUser, testPass)
|
||||
c := New(testUrl, testUser, testPass, TimeoutLow)
|
||||
|
||||
assert.IsType(t, Client{}, c)
|
||||
}
|
||||
|
||||
func TestClient_Files(t *testing.T) {
|
||||
c := New(testUrl, testUser, testPass)
|
||||
c := New(testUrl, testUser, testPass, TimeoutLow)
|
||||
|
||||
assert.IsType(t, Client{}, c)
|
||||
|
||||
|
@ -39,12 +40,12 @@ func TestClient_Files(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestClient_Directories(t *testing.T) {
|
||||
c := New(testUrl, testUser, testPass)
|
||||
c := New(testUrl, testUser, testPass, TimeoutLow)
|
||||
|
||||
assert.IsType(t, Client{}, c)
|
||||
|
||||
t.Run("non-recursive", func(t *testing.T) {
|
||||
dirs, err := c.Directories("", false, SyncTimeout)
|
||||
dirs, err := c.Directories("", false, MaxRequestDuration)
|
||||
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
|
@ -62,7 +63,7 @@ func TestClient_Directories(t *testing.T) {
|
|||
})
|
||||
|
||||
t.Run("recursive", func(t *testing.T) {
|
||||
dirs, err := c.Directories("", true, SyncTimeout)
|
||||
dirs, err := c.Directories("", true, 0)
|
||||
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
|
@ -75,7 +76,7 @@ func TestClient_Directories(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestClient_Download(t *testing.T) {
|
||||
c := New(testUrl, testUser, testPass)
|
||||
c := New(testUrl, testUser, testPass, TimeoutDefault)
|
||||
|
||||
assert.IsType(t, Client{}, c)
|
||||
|
||||
|
@ -106,7 +107,7 @@ func TestClient_Download(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestClient_DownloadDir(t *testing.T) {
|
||||
c := New(testUrl, testUser, testPass)
|
||||
c := New(testUrl, testUser, testPass, TimeoutLow)
|
||||
|
||||
assert.IsType(t, Client{}, c)
|
||||
|
||||
|
@ -136,7 +137,7 @@ func TestClient_DownloadDir(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestClient_UploadAndDelete(t *testing.T) {
|
||||
c := New(testUrl, testUser, testPass)
|
||||
c := New(testUrl, testUser, testPass, TimeoutLow)
|
||||
|
||||
assert.IsType(t, Client{}, c)
|
||||
|
||||
|
|
|
@ -78,7 +78,7 @@ func (worker *Share) Start() (err error) {
|
|||
continue
|
||||
}
|
||||
|
||||
client := webdav.New(a.AccURL, a.AccUser, a.AccPass)
|
||||
client := webdav.New(a.AccURL, a.AccUser, a.AccPass, webdav.Timeout(a.AccTimeout))
|
||||
existingDirs := make(map[string]string)
|
||||
|
||||
for _, file := range files {
|
||||
|
@ -124,7 +124,8 @@ func (worker *Share) Start() (err error) {
|
|||
file.Status = entity.FileShareShared
|
||||
}
|
||||
|
||||
if a.RetryLimit >= 0 && file.Errors > a.RetryLimit {
|
||||
// Failed too often?
|
||||
if a.RetryLimit > 0 && file.Errors > a.RetryLimit {
|
||||
file.Status = entity.FileShareError
|
||||
}
|
||||
|
||||
|
@ -158,7 +159,7 @@ func (worker *Share) Start() (err error) {
|
|||
continue
|
||||
}
|
||||
|
||||
client := webdav.New(a.AccURL, a.AccUser, a.AccPass)
|
||||
client := webdav.New(a.AccURL, a.AccUser, a.AccPass, webdav.Timeout(a.AccTimeout))
|
||||
|
||||
for _, file := range files {
|
||||
if mutex.ShareWorker.Canceled() {
|
||||
|
|
|
@ -66,8 +66,8 @@ func (worker *Sync) Start() (err error) {
|
|||
continue
|
||||
}
|
||||
|
||||
if a.AccErrors > a.RetryLimit {
|
||||
a.AccErrors = 0
|
||||
// Failed too often?
|
||||
if a.RetryLimit > 0 && a.AccErrors > a.RetryLimit {
|
||||
a.AccSync = false
|
||||
|
||||
if err := entity.Db().Save(&a).Error; err != nil {
|
||||
|
@ -109,6 +109,7 @@ func (worker *Sync) Start() (err error) {
|
|||
if complete, err := worker.download(a); err != nil {
|
||||
accErrors++
|
||||
accError = err.Error()
|
||||
syncStatus = entity.AccountSyncStatusRefresh
|
||||
} else if complete {
|
||||
if a.SyncUpload {
|
||||
syncStatus = entity.AccountSyncStatusUpload
|
||||
|
@ -123,6 +124,7 @@ func (worker *Sync) Start() (err error) {
|
|||
if complete, err := worker.upload(a); err != nil {
|
||||
accErrors++
|
||||
accError = err.Error()
|
||||
syncStatus = entity.AccountSyncStatusRefresh
|
||||
} else if complete {
|
||||
synced = true
|
||||
syncStatus = entity.AccountSyncStatusSynced
|
||||
|
|
|
@ -76,7 +76,7 @@ func (worker *Sync) download(a entity.Account) (complete bool, err error) {
|
|||
|
||||
log.Infof("sync: downloading from %s", a.AccName)
|
||||
|
||||
client := webdav.New(a.AccURL, a.AccUser, a.AccPass)
|
||||
client := webdav.New(a.AccURL, a.AccUser, a.AccPass, webdav.Timeout(a.AccTimeout))
|
||||
|
||||
var baseDir string
|
||||
|
||||
|
@ -94,7 +94,8 @@ func (worker *Sync) download(a entity.Account) (complete bool, err error) {
|
|||
return false, nil
|
||||
}
|
||||
|
||||
if file.Errors > a.RetryLimit {
|
||||
// Failed too often?
|
||||
if a.RetryLimit > 0 && file.Errors > a.RetryLimit {
|
||||
log.Debugf("sync: downloading %s failed more than %d times", file.RemoteName, a.RetryLimit)
|
||||
continue
|
||||
}
|
||||
|
@ -104,14 +105,17 @@ func (worker *Sync) download(a entity.Account) (complete bool, err error) {
|
|||
if _, err := os.Stat(localName); err == nil {
|
||||
log.Warnf("sync: download skipped, %s already exists", localName)
|
||||
file.Status = entity.FileSyncExists
|
||||
file.Error = ""
|
||||
file.Errors = 0
|
||||
} else {
|
||||
if err := client.Download(file.RemoteName, localName, false); err != nil {
|
||||
worker.logError(err)
|
||||
file.Errors++
|
||||
file.Error = err.Error()
|
||||
} else {
|
||||
log.Infof("sync: downloaded %s from %s", file.RemoteName, a.AccName)
|
||||
file.Status = entity.FileSyncDownloaded
|
||||
file.Error = ""
|
||||
file.Errors = 0
|
||||
}
|
||||
|
||||
if mutex.SyncWorker.Canceled() {
|
||||
|
|
|
@ -14,9 +14,9 @@ func (worker *Sync) refresh(a entity.Account) (complete bool, err error) {
|
|||
return false, nil
|
||||
}
|
||||
|
||||
client := webdav.New(a.AccURL, a.AccUser, a.AccPass)
|
||||
client := webdav.New(a.AccURL, a.AccUser, a.AccPass, webdav.Timeout(a.AccTimeout))
|
||||
|
||||
subDirs, err := client.Directories(a.SyncPath, true, webdav.AsyncTimeout)
|
||||
subDirs, err := client.Directories(a.SyncPath, true, webdav.MaxRequestDuration)
|
||||
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
|
|
|
@ -31,7 +31,7 @@ func (worker *Sync) upload(a entity.Account) (complete bool, err error) {
|
|||
return true, nil
|
||||
}
|
||||
|
||||
client := webdav.New(a.AccURL, a.AccUser, a.AccPass)
|
||||
client := webdav.New(a.AccURL, a.AccUser, a.AccPass, webdav.Timeout(a.AccTimeout))
|
||||
existingDirs := make(map[string]string)
|
||||
|
||||
for _, file := range files {
|
||||
|
|
Loading…
Reference in a new issue