Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 5 additions & 8 deletions apps/appstore/src/components/UpdateAllDialog.vue
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
import type { IAppstoreApp, IAppstoreExApp } from '../apps.d.ts'

import { mdiCheck, mdiInformationOutline, mdiUpdate, mdiWeb } from '@mdi/js'
import { showError } from '@nextcloud/dialogs'
import { getLanguage, t } from '@nextcloud/l10n'
import { NcButton, NcDialog, NcIconSvgWrapper, NcLoadingIcon, NcNoteCard } from '@nextcloud/vue'
import { computed, ref } from 'vue'
Expand Down Expand Up @@ -47,13 +46,11 @@ const changelogText = computed(() => {
*/
async function onUpdate() {
isUpdating.value = true
for (const app of props.apps) {
try {
await store.updateApp(app.id)
} catch (error) {
logger.error(`Failed to update app ${app.id}`, { error })
showError(t('appstore', 'Failed to update app {appName}', { appName: app.name }))
}
const appIds = props.apps.map((app) => app.id)
try {
await store.updateApps(appIds)
} catch (error) {
logger.error('Failed to update all apps', { error, appIds })
}
isUpdating.value = false
emit('close')
Expand Down
33 changes: 32 additions & 1 deletion apps/appstore/src/service/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ import type { OCSResponse } from '@nextcloud/typings/ocs'
import type { IAppstoreApp, IAppstoreCategory } from '../apps.d.ts'

import axios from '@nextcloud/axios'
import { addPasswordConfirmationInterceptors, PwdConfirmationMode } from '@nextcloud/password-confirmation'
import { n, t } from '@nextcloud/l10n'
import { addPasswordConfirmationInterceptors, PwdConfirmationMode, withStrictPasswordConfirmation } from '@nextcloud/password-confirmation'
import { generateOcsUrl } from '@nextcloud/router'
import PQueue from 'p-queue'
import { APPSTORE_CATEGORY_ICONS } from '../constants.ts'
Expand Down Expand Up @@ -40,6 +41,22 @@ export async function enableApp(appId: string, force = false, groups?: string[])
})
}

/**
* Enable multiple apps
*
* @param apps - The apps to enable
* @param force - Whether to force enable the app
* @param groups - The groups to enable the app for
*/
export async function enableApps(apps: IAppstoreApp[], force = false, groups?: string[]) {
return withStrictPasswordConfirmation((axios) => apps.map((app) => queue
.add(async () => {
await axios.post(Url.enable, { appId: app.id, groups, force: force || undefined })
})), {
text: t('appstore', 'Enabling following apps: {apps}', { apps: apps.map((app) => app.name).join(', ') }),
})
}

/**
* Disable app by its app id
*
Expand All @@ -62,6 +79,20 @@ export async function updateApp(appId: string) {
})
}

/**
* Update multiple apps
*
* @param appIds - The app ids to update
*/
export async function updateApps(appIds: string[]) {
return withStrictPasswordConfirmation((axios) => appIds.map((appId) => queue
.add(async () => {
await axios.post(Url.update, { appId })
})), {
text: n('appstore', 'Updating %n app.', 'Updating %n apps.', appIds.length),
})
}

/**
* Uninstall an app by its app id
*
Expand Down
66 changes: 48 additions & 18 deletions apps/appstore/src/store/updates.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
* SPDX-License-Identifier: AGPL-3.0-or-later
*/

import type { IAppstoreApp, IAppstoreExApp } from '../apps.d.ts'

import { showError } from '@nextcloud/dialogs'
import { loadState } from '@nextcloud/initial-state'
import { t } from '@nextcloud/l10n'
Expand All @@ -28,37 +30,65 @@ export const useUpdatesStore = defineStore('updates', () => {
const updateCount = computed(() => internalUpdateCount.value + exApps.updateCount)

/**
* Update the given app
* Update multiple apps by their app ids at the same time
*
* @param appId - The app id to update
* @throws {Error} if the app is not found
* @param appIds - Ids of apps to update
*/
async function updateApp(appId: string) {
async function updateApps(appIds: string[]) {
const store = useAppsStore()

const app = store.getAppById(appId)
if (!app) {
throw new Error(`App with id ${appId} not found`)
const apps = appIds.map((appId) => store.getAppById(appId))
if (apps.some((app) => !app)) {
throw new Error('One or more apps not found')
}

try {
if ('app_api' in app && app.app_api) {
await exApps.updateApp(appId)
} else {
await api.updateApp(appId)
internalUpdateCount.value = Math.max(internalUpdateCount.value - 1, 0)
const exAppsToUpdate = (apps as (IAppstoreApp | IAppstoreExApp)[]).filter((app): app is IAppstoreExApp => 'app_api' in app && app.app_api)
for (const app of exAppsToUpdate) {
try {
await exApps.updateApp(app.id)
app.update = undefined
rebuildNavigation()
} catch (error) {
logger.error('Failed to update ex app', { appId: app.id, error })
showError(t('appstore', 'Could not update an exApp. Please try again later.'))
}
}

app.update = undefined
rebuildNavigation()
} catch (error) {
logger.error('Failed to update app', { appId, error })
showError(t('appstore', 'Could not update the app. Please try again later.'))
const appsToUpdate = (apps as (IAppstoreApp | IAppstoreExApp)[]).filter((app): app is IAppstoreApp => !('app_api' in app && app.app_api))
if (appsToUpdate.length > 0) {
try {
const results = await Promise.allSettled(await api.updateApps(appsToUpdate.map((app) => app.id)))
for (const [index, app] of appsToUpdate.entries()) {
const result = results[index]
if (result?.status === 'rejected') {
logger.error('Failed to update app', { appId: app.id, error: result.reason })
showError(t('appstore', 'Could not update {app}. Please try again later.', { app: app.name }))
} else {
app.update = undefined
internalUpdateCount.value = Math.max(internalUpdateCount.value - 1, 0)
}
}
rebuildNavigation()
} catch (error) {
logger.error('Failed to update one or more apps', { appsToUpdate, error })
showError(t('appstore', 'Could not update an app. Please try again later.'))
}
}
}

/**
* Update the given app
*
* @param appId - The app id to update
* @throws {Error} if the app is not found
*/
async function updateApp(appId: string) {
updateApps([appId])
}

return {
updateCount,
updateApp,
updateApps,
}
})
2 changes: 1 addition & 1 deletion core/src/components/setup/RecommendedApps.vue
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ async function installApps() {
app.loading = true
}

const results = await Promise.allSettled(appsToInstall.map((app) => appstoreApi.enableApp(app.id)))
const results = await Promise.allSettled(await appstoreApi.enableApps(appsToInstall))
results.forEach((result, index) => {
const app = appsToInstall[index]!
app.loading = false
Expand Down
Loading
Loading