Merge pull request #1442 from nextcloud/refactor/longPoll

Refactor/long poll
This commit is contained in:
René Gieling 2021-02-28 16:11:07 +01:00 коммит произвёл GitHub
Родитель 0982cbe1f9 c7c2096226
Коммит 32cc8e6557
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
5 изменённых файлов: 121 добавлений и 107 удалений

Просмотреть файл

@ -44,12 +44,11 @@ class WatchMapper extends QBMapper {
$qb->select('*') $qb->select('*')
->from($this->getTableName()) ->from($this->getTableName())
->where( ->where($qb->expr()->gt('updated', $qb->createNamedParameter($offset)))
$qb->expr()->eq('poll_id', $qb->createNamedParameter($pollId)) ->andWhere($qb->expr()->orX(
) $qb->expr()->eq('poll_id', $qb->createNamedParameter($pollId)),
->andWhere( $qb->expr()->eq('table', $qb->createNamedParameter('polls'))
$qb->expr()->gt('updated', $qb->createNamedParameter($offset)) ));
);
return $this->findEntities($qb); return $this->findEntities($qb);
} }

Просмотреть файл

@ -51,6 +51,7 @@ import './assets/scss/colors.scss'
// import './assets/scss/colors-dark.scss' // import './assets/scss/colors-dark.scss'
import './assets/scss/transitions.scss' import './assets/scss/transitions.scss'
import './assets/scss/experimental.scss' import './assets/scss/experimental.scss'
import { watchPolls } from './mixins/watchPolls'
export default { export default {
name: 'App', name: 'App',
@ -62,13 +63,14 @@ export default {
SideBar, SideBar,
}, },
mixins: [watchPolls],
data() { data() {
return { return {
sideBarOpen: (window.innerWidth > 920), sideBarOpen: (window.innerWidth > 920),
activeTab: 'comments', activeTab: 'comments',
transitionClass: 'transitions-active', transitionClass: 'transitions-active',
loading: false, loading: false,
reloadInterval: 30000,
} }
}, },
@ -95,6 +97,7 @@ export default {
watch: { watch: {
$route(to, from) { $route(to, from) {
this.watchPollsRestart()
this.loadPoll() this.loadPoll()
}, },
}, },
@ -105,11 +108,13 @@ export default {
if (this.$route.name !== 'publicVote') { if (this.$route.name !== 'publicVote') {
this.updatePolls() this.updatePolls()
} }
if (this.$route.params.id && !this.$oute.params.token) { if (this.$route.params.id && !this.$route.params.token) {
this.loadPoll(true) this.loadPoll(true)
} }
} }
this.watchPolls()
subscribe('transitions-off', (delay) => { subscribe('transitions-off', (delay) => {
this.transitionsOff(delay) this.transitionsOff(delay)
}) })
@ -141,12 +146,10 @@ export default {
} }
}) })
this.timedReload()
}, },
beforeDestroy() { beforeDestroy() {
window.clearInterval(this.reloadTimer) this.cancelToken.cancel()
unsubscribe('load-poll') unsubscribe('load-poll')
unsubscribe('update-polls') unsubscribe('update-polls')
unsubscribe('toggle-sidebar') unsubscribe('toggle-sidebar')
@ -159,12 +162,6 @@ export default {
this.transitionClass = 'transitions-active' this.transitionClass = 'transitions-active'
}, },
timedReload() {
this.reloadTimer = window.setInterval(() => {
this.updatePolls()
}, this.reloadInterval)
},
transitionsOff(delay) { transitionsOff(delay) {
this.transitionClass = '' this.transitionClass = ''
if (delay) { if (delay) {

Просмотреть файл

@ -72,7 +72,6 @@ export default {
return { return {
showSettingsDlg: false, showSettingsDlg: false,
createDlg: false, createDlg: false,
reloadInterval: 30000,
pollCategories: [ pollCategories: [
{ {
id: 'relevant', id: 'relevant',
@ -124,10 +123,6 @@ export default {
}, },
}, },
created() {
this.timedReload()
},
beforeDestroy() { beforeDestroy() {
window.clearInterval(this.reloadTimer) window.clearInterval(this.reloadTimer)
}, },
@ -137,13 +132,6 @@ export default {
this.createDlg = false this.createDlg = false
}, },
timedReload() {
// reload poll list periodically
this.reloadTimer = window.setInterval(() => {
emit('update-polls')
}, this.reloadInterval)
},
toggleCreateDlg() { toggleCreateDlg() {
this.createDlg = !this.createDlg this.createDlg = !this.createDlg
if (this.createDlg) { if (this.createDlg) {

108
src/js/mixins/watchPolls.js Normal file
Просмотреть файл

@ -0,0 +1,108 @@
import axios from '@nextcloud/axios'
import { generateUrl } from '@nextcloud/router'
export const watchPolls = {
data() {
return {
cancelToken: null,
restart: false,
watching: true,
lastUpdated: Math.round(Date.now() / 1000),
}
},
methods: {
watchPollsRestart() {
this.restart = true
this.cancelToken.cancel()
},
async watchPolls() {
console.debug('polls', 'Watch for updates')
this.cancelToken = axios.CancelToken.source()
while (this.watching) {
let endPoint = 'apps/polls'
if (this.$route.name === 'publicVote') {
endPoint = endPoint + '/s/' + this.$route.params.token
} else {
endPoint = endPoint + '/poll/' + (this.$route.params.id ?? 0)
}
try {
const response = await axios.get(generateUrl(endPoint + '/watch'), {
params: { offset: this.lastUpdated },
cancelToken: this.cancelToken.token,
})
let dispatches = []
console.debug('polls', 'update detected', response.data.updates)
response.data.updates.forEach((item) => {
this.lastUpdated = (item.updated > this.lastUpdated) ? item.updated : this.lastUpdated
// an updated poll table is reported
if (item.table === 'polls') {
if (this.$route.name !== 'publicVote') {
// load poll list only, when not in public poll
dispatches.push('polls/load')
}
if (item.pollId === parseInt(this.$route.params.id)) {
// if current poll is affected, load current poll configuration
dispatches.push('poll/get')
}
} else {
// a table of the current poll was reported, load
// corresponding stores
dispatches.push(item.table + '/list')
}
})
// remove duplicates
dispatches = [...new Set(dispatches)]
// execute all loads within one promise
const requests = dispatches.map(dispatches => this.$store.dispatch(dispatches))
await Promise.all(requests)
this.watching = true
} catch (e) {
this.watching = false
if (axios.isCancel(e)) {
if (this.restart) {
console.debug('restart watch')
this.watching = true
this.restart = false
this.cancelToken = axios.CancelToken.source()
} else {
console.debug('Watch canceled')
this.watching = true
this.restart = false
return
}
} else if (e.response) {
if (e.response.status === 304) {
this.watching = true
continue
} else if (e.response.status === 503) {
console.debug('Server not available, reconnect watch in 30 sec')
await new Promise(resolve => setTimeout(resolve, 30000))
this.watching = true
continue
} else {
console.error('Unhandled error watching polls', e)
return
}
} else if (e.request) {
console.debug('Watch aborted')
this.watching = true
return
}
}
}
},
},
}

Просмотреть файл

@ -95,8 +95,6 @@
</template> </template>
<script> <script>
import axios from '@nextcloud/axios'
import { generateUrl } from '@nextcloud/router'
import { showError, showSuccess } from '@nextcloud/dialogs' import { showError, showSuccess } from '@nextcloud/dialogs'
import linkifyUrls from 'linkify-urls' import linkifyUrls from 'linkify-urls'
import { mapState, mapGetters } from 'vuex' import { mapState, mapGetters } from 'vuex'
@ -134,15 +132,12 @@ export default {
data() { data() {
return { return {
cancelToken: null,
delay: 50, delay: 50,
isLoading: false, isLoading: false,
manualViewDatePoll: '', manualViewDatePoll: '',
manualViewTextPoll: '', manualViewTextPoll: '',
ranked: false, ranked: false,
voteSaved: false, voteSaved: false,
watching: true,
lastUpdated: Math.round(Date.now() / 1000),
} }
}, },
@ -302,12 +297,9 @@ export default {
} else { } else {
emit('toggle-sidebar', { open: (window.innerWidth > 920) }) emit('toggle-sidebar', { open: (window.innerWidth > 920) })
} }
this.watchPoll()
}, },
beforeDestroy() { beforeDestroy() {
console.debug('destroy votes')
this.cancelToken.cancel()
this.$store.dispatch({ type: 'poll/reset' }) this.$store.dispatch({ type: 'poll/reset' })
}, },
@ -353,76 +345,6 @@ export default {
showError(t('polls', 'Error saving email address {emailAddress}', { emailAddress: emailAddress })) showError(t('polls', 'Error saving email address {emailAddress}', { emailAddress: emailAddress }))
} }
}, },
async watchPoll() {
console.debug('polls', 'Watch for updates')
this.cancelToken = axios.CancelToken.source()
let endPoint = 'apps/polls'
if (this.$route.name === 'publicVote') {
endPoint = endPoint + '/s/' + this.$route.params.token
} else if (this.$route.name === 'vote') {
endPoint = endPoint + '/poll/' + this.$route.params.id
} else {
this.watching = false
}
while (this.watching) {
try {
const response = await axios.get(generateUrl(endPoint + '/watch'), {
params: { offset: this.lastUpdated },
cancelToken: this.cancelToken.token,
})
const dispatches = []
console.debug('polls', 'update detected', response.data.updates)
response.data.updates.forEach((item) => {
this.lastUpdated = (item.updated > this.lastUpdated) ? item.updated : this.lastUpdated
if (item.table === 'polls') {
dispatches.push('poll/get')
} else {
dispatches.push(item.table + '/list')
}
})
const requests = dispatches.map(dispatches => this.$store.dispatch(dispatches))
await Promise.all(requests)
this.watching = true
} catch (error) {
this.watching = false
if (axios.isCancel(error)) {
console.debug('Watch canceld')
} else if (error.response) {
if (error.response.status === 304) {
this.watching = true
} else if (error.response.status === 503) {
console.debug('Server not available, reconnect watch in 30 sec')
await new Promise(resolve => setTimeout(resolve, 30000))
this.watching = true
} else {
console.error('Unhandled error watching polls', error)
}
} else if (error.request) {
console.debug('Watch aborted')
this.watching = true
}
}
}
},
}, },
} }