Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/nextcloud/mail.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorChristoph Wurst <ChristophWurst@users.noreply.github.com>2020-02-04 18:18:17 +0300
committerGitHub <noreply@github.com>2020-02-04 18:18:17 +0300
commitb641959fa8a792c70df69dd9f98eab39ad9cdb70 (patch)
tree12af87fabafb22f1b9ea8dca2b2ac7a6f8a07c60 /src
parentcd2375b5fd8d1defa8c5a2e55d8332aea5e2d556 (diff)
parentcda1eb7b4fdf0e8c5bdb1884f2605dc45b98bc71 (diff)
Merge pull request #2612 from nextcloud/enhancement/envelope-fetch-conflict-retry
Try to recover from server errors
Diffstat (limited to 'src')
-rw-r--r--src/components/FolderContent.vue89
-rw-r--r--src/errors/MailboxLockedError.js28
-rw-r--r--src/errors/MailboxNotCachedError.js28
-rw-r--r--src/errors/convert.js51
-rw-r--r--src/errors/match.js34
-rw-r--r--src/service/MessageService.js21
-rw-r--r--src/tests/unit/errors/convert.spec.js69
-rw-r--r--src/tests/unit/errors/match.spec.js56
-rw-r--r--src/tests/unit/util/wait.spec.js28
-rw-r--r--src/util/wait.js26
10 files changed, 375 insertions, 55 deletions
diff --git a/src/components/FolderContent.vue b/src/components/FolderContent.vue
index ead4bce5f..666682313 100644
--- a/src/components/FolderContent.vue
+++ b/src/components/FolderContent.vue
@@ -34,10 +34,13 @@ import EnvelopeList from './EnvelopeList'
import Error from './Error'
import Loading from './Loading'
import logger from '../logger'
-import {MailboxNotCachedException} from '../service/MessageService'
+import MailboxNotCachedError from '../errors/MailboxNotCachedError'
import Message from './Message'
import NewMessageDetail from './NewMessageDetail'
import NoMessageSelected from './NoMessageSelected'
+import {matchError} from '../errors/match'
+import MailboxLockedError from '../errors/MailboxLockedError'
+import {wait} from '../util/wait'
export default {
name: 'FolderContent',
@@ -132,52 +135,64 @@ export default {
return this.loadEnvelopes()
})
},
- loadEnvelopes() {
+ async loadEnvelopes() {
this.loadingEnvelopes = true
this.error = false
- this.$store
- .dispatch('fetchEnvelopes', {
+ try {
+ await this.$store.dispatch('fetchEnvelopes', {
accountId: this.account.id,
folderId: this.folder.id,
query: this.searchQuery,
})
- .then(() => {
- const envelopes = this.envelopes
- logger.debug('envelopes fetched', envelopes)
-
- this.loadingEnvelopes = false
-
- if (!this.isMobile && this.$route.name !== 'message' && envelopes.length > 0) {
- // Show first message
- let first = envelopes[0]
-
- // Keep the selected account-folder combination, but navigate to the message
- // (it's not a bug that we don't use first.accountId and first.folderId here)
- this.$router.replace({
- name: 'message',
- params: {
- accountId: this.account.id,
- folderId: this.folder.id,
- messageUid: first.uid,
- },
- })
- }
- })
- .catch(error => {
- if (error instanceof MailboxNotCachedException) {
+
+ const envelopes = this.envelopes
+ logger.debug('envelopes fetched', envelopes)
+
+ this.loadingEnvelopes = false
+
+ if (!this.isMobile && this.$route.name !== 'message' && envelopes.length > 0) {
+ // Show first message
+ let first = envelopes[0]
+
+ // Keep the selected account-folder combination, but navigate to the message
+ // (it's not a bug that we don't use first.accountId and first.folderId here)
+ this.$router.replace({
+ name: 'message',
+ params: {
+ accountId: this.account.id,
+ folderId: this.folder.id,
+ messageUid: first.uid,
+ },
+ })
+ }
+ } catch (error) {
+ await matchError(error, {
+ [MailboxLockedError.name]: async error => {
+ logger.info('Mailbox is locked', {error})
+
+ await wait(15 * 1000)
+ // Keep trying
+ await this.loadEnvelopes()
+ },
+ [MailboxNotCachedError.name]: async error => {
+ logger.info('Mailbox not cached. Triggering initialization', {error})
this.loadingEnvelopes = false
- logger.info('Mailbox not cached. Triggering initialization')
- return this.initializeCache()
- }
- logger.error('Could not fetch envelopes', {error})
- this.error = error
- })
- .catch(error => {
- logger.error('Could not fetch envelopes or initialize cache')
- this.error = error
+ try {
+ await this.initializeCache()
+ } catch (error) {
+ logger.error('Could not initialize cache', {error})
+ this.error = error
+ }
+ },
+ default: error => {
+ logger.error('Could not fetch envelopes', {error})
+ this.loadingEnvelopes = false
+ this.error = error
+ },
})
+ }
},
hideMessage() {
this.$router.replace({
diff --git a/src/errors/MailboxLockedError.js b/src/errors/MailboxLockedError.js
new file mode 100644
index 000000000..e9eb55351
--- /dev/null
+++ b/src/errors/MailboxLockedError.js
@@ -0,0 +1,28 @@
+/*
+ * @copyright 2020 Christoph Wurst <christoph@winzerhof-wurst.at>
+ *
+ * @author 2020 Christoph Wurst <christoph@winzerhof-wurst.at>
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+export default class MailboxLockedError extends Error {
+ constructor(message) {
+ super(message)
+ this.name = 'MailboxLockedError'
+ this.message = message
+ }
+}
diff --git a/src/errors/MailboxNotCachedError.js b/src/errors/MailboxNotCachedError.js
new file mode 100644
index 000000000..12fc4a391
--- /dev/null
+++ b/src/errors/MailboxNotCachedError.js
@@ -0,0 +1,28 @@
+/*
+ * @copyright 2020 Christoph Wurst <christoph@winzerhof-wurst.at>
+ *
+ * @author 2020 Christoph Wurst <christoph@winzerhof-wurst.at>
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+export default class MailboxNotCachedError extends Error {
+ constructor(message) {
+ super(message)
+ this.name = 'MailboxNotCachedError'
+ this.message = message
+ }
+}
diff --git a/src/errors/convert.js b/src/errors/convert.js
new file mode 100644
index 000000000..4d816fe15
--- /dev/null
+++ b/src/errors/convert.js
@@ -0,0 +1,51 @@
+/*
+ * @copyright 2020 Christoph Wurst <christoph@winzerhof-wurst.at>
+ *
+ * @author 2020 Christoph Wurst <christoph@winzerhof-wurst.at>
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+import MailboxLockedError from './MailboxLockedError'
+import MailboxNotCachedError from './MailboxNotCachedError'
+
+const map = {
+ 'OCA\\Mail\\Exception\\MailboxLockedException': MailboxLockedError,
+ 'OCA\\Mail\\Exception\\MailboxNotCachedException': MailboxNotCachedError,
+}
+
+/**
+ * @param {object} axiosError
+ */
+export const convertAxiosError = axiosError => {
+ if (!('response' in axiosError)) {
+ // No conversion
+ return axiosError
+ }
+
+ if (!('x-mail-response' in axiosError.response.headers)) {
+ // Not a structured response
+ return axiosError
+ }
+
+ const response = axiosError.response
+ if (!(response.data.data.type in map)) {
+ // No conversion possible
+ return axiosError
+ }
+
+ return new map[response.data.data.type]()
+}
diff --git a/src/errors/match.js b/src/errors/match.js
new file mode 100644
index 000000000..6167a3d8e
--- /dev/null
+++ b/src/errors/match.js
@@ -0,0 +1,34 @@
+/*
+ * @copyright 2020 Christoph Wurst <christoph@winzerhof-wurst.at>
+ *
+ * @author 2020 Christoph Wurst <christoph@winzerhof-wurst.at>
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/**
+ * @param {Error} error
+ * @param {object} matches
+ */
+export const matchError = async (error, matches) => {
+ if (error.name in matches) {
+ return await Promise.resolve(matches[error.name](error))
+ }
+ if ('default' in matches) {
+ return await Promise.resolve(matches['default'](error))
+ }
+ throw new Error('unhandled error in match: ' + error.name)
+}
diff --git a/src/service/MessageService.js b/src/service/MessageService.js
index 9e2fdbfac..e61bff436 100644
--- a/src/service/MessageService.js
+++ b/src/service/MessageService.js
@@ -2,15 +2,9 @@ import {generateUrl} from '@nextcloud/router'
import axios from '@nextcloud/axios'
import logger from '../logger'
+import MailboxNotCachedError from '../errors/MailboxNotCachedError'
import {parseErrorResponse} from '../http/ErrorResponseParser'
-
-export class MailboxNotCachedException extends Error {
- constructor(message) {
- super(message)
- this.name = 'MailboxNotCachedException'
- this.message = message
- }
-}
+import {convertAxiosError} from '../errors/convert'
export function fetchEnvelopes(accountId, folderId, query, cursor) {
const url = generateUrl('/apps/mail/api/accounts/{accountId}/folders/{folderId}/messages', {
@@ -32,16 +26,7 @@ export function fetchEnvelopes(accountId, folderId, query, cursor) {
})
.then(resp => resp.data)
.catch(error => {
- if (
- error.response &&
- error.response.status === 400 &&
- 'x-mail-response' in error.response.headers &&
- error.response.data.data.type === 'OCA\\Mail\\Exception\\MailboxNotCachedException'
- ) {
- logger.debug(`mailbox ${folderId} of account ${accountId} is not cached`)
- throw new MailboxNotCachedException()
- }
- throw error
+ throw convertAxiosError(error)
})
}
diff --git a/src/tests/unit/errors/convert.spec.js b/src/tests/unit/errors/convert.spec.js
new file mode 100644
index 000000000..a722e3baf
--- /dev/null
+++ b/src/tests/unit/errors/convert.spec.js
@@ -0,0 +1,69 @@
+/*
+ * @copyright 2020 Christoph Wurst <christoph@winzerhof-wurst.at>
+ *
+ * @author 2020 Christoph Wurst <christoph@winzerhof-wurst.at>
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+import {convertAxiosError} from '../../../errors/convert'
+
+describe('convert error', () => {
+ it('ignores errors without a response', () => {
+ const error = {} // no response
+
+ const result = convertAxiosError(error)
+
+ expect(result).to.not.be.an.instanceof(Error)
+ expect(result).to.equal(error)
+ })
+
+ it('ignores errors it does not know', () => {
+ const error = {
+ response: {
+ headers: {},
+ status: 400,
+ data: {},
+ },
+ }
+
+ const result = convertAxiosError(error)
+
+ expect(result).to.not.be.an.instanceof(Error)
+ expect(result).to.equal(error)
+ })
+
+ it('converts known exceptions to errors', () => {
+ const error = {
+ response: {
+ headers: {
+ 'x-mail-response': '1',
+ },
+ status: 400,
+ data: {
+ status: 'fail',
+ data: {
+ type: 'OCA\\Mail\\Exception\\MailboxLockedException',
+ },
+ },
+ },
+ }
+
+ const result = convertAxiosError(error)
+
+ expect(result).to.be.an.instanceof(Error)
+ })
+})
diff --git a/src/tests/unit/errors/match.spec.js b/src/tests/unit/errors/match.spec.js
new file mode 100644
index 000000000..bf0897161
--- /dev/null
+++ b/src/tests/unit/errors/match.spec.js
@@ -0,0 +1,56 @@
+/*
+ * @copyright 2020 Christoph Wurst <christoph@winzerhof-wurst.at>
+ *
+ * @author 2020 Christoph Wurst <christoph@winzerhof-wurst.at>
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+import {matchError} from '../../../errors/match'
+
+describe('match', () => {
+ it('throws an error when nothing matches', done => {
+ const error = new Error('henlo')
+
+ matchError(error, {}).catch(() => done())
+ })
+
+ it('uses the default', done => {
+ const map = {
+ default: error => 3,
+ }
+ const error = new Error('henlo')
+
+ matchError(error, map).then(result => {
+ expect(expect(result).to.equal(3))
+ done()
+ })
+ })
+
+ it('matches errors', done => {
+ const map = {
+ MyErr: error => 2,
+ default: error => 3,
+ }
+ const error = new Error('henlo')
+ error.name = 'MyErr'
+
+ matchError(error, map).then(result => {
+ expect(expect(result).to.equal(2))
+ done()
+ })
+ })
+})
diff --git a/src/tests/unit/util/wait.spec.js b/src/tests/unit/util/wait.spec.js
new file mode 100644
index 000000000..90a74ae3c
--- /dev/null
+++ b/src/tests/unit/util/wait.spec.js
@@ -0,0 +1,28 @@
+/*
+ * @copyright 2020 Christoph Wurst <christoph@winzerhof-wurst.at>
+ *
+ * @author 2020 Christoph Wurst <christoph@winzerhof-wurst.at>
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+import {wait} from '../../../util/wait'
+
+describe('wait', () => {
+ it('waits', done => {
+ wait(0).then(done)
+ })
+})
diff --git a/src/util/wait.js b/src/util/wait.js
new file mode 100644
index 000000000..7a9dff8be
--- /dev/null
+++ b/src/util/wait.js
@@ -0,0 +1,26 @@
+/*
+ * @copyright 2020 Christoph Wurst <christoph@winzerhof-wurst.at>
+ *
+ * @author 2020 Christoph Wurst <christoph@winzerhof-wurst.at>
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+export const wait = ms => {
+ return new Promise(res => {
+ setTimeout(res, ms)
+ })
+}