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
4 changes: 2 additions & 2 deletions js/user_migration-admin-settings.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion js/user_migration-admin-settings.js.map

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions js/user_migration-personal-settings.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion js/user_migration-personal-settings.js.map

Large diffs are not rendered by default.

8 changes: 6 additions & 2 deletions lib/Controller/ApiController.php
Original file line number Diff line number Diff line change
Expand Up @@ -221,13 +221,17 @@ public function exportable(?array $migrators): DataResponse {
$user = $this->checkJobAndGetUser();

if (!is_null($migrators)) {
if (count($migrators) === 1 && reset($migrators) === '') {
$migrators = [];
}

$this->checkMigrators($migrators);
}

try {
$size = $this->migrationService->estimateExportSize($user, $migrators);
// Convert to MiB and round to one significant digit after the decimal point
$roundedSize = round($size / 1024, 1);
// Round and convert to MiB
$roundedSize = max(1, round($size / 1024));
} catch (UserMigrationException $e) {
throw new OCSException($e->getMessage());
}
Expand Down
6 changes: 3 additions & 3 deletions lib/Service/UserMigrationService.php
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,7 @@ public function checkExportability(IUser $user, ?array $filteredMigratorList = n
$userFolder = $this->root->getUserFolder($user->getUID());
$freeSpace = (int)ceil($userFolder->getFreeSpace() / 1024);
} catch (Throwable $e) {
throw new NotExportableException('Error calculating amount of free space available');
throw new NotExportableException('Error calculating amount of free storage space available');
}

try {
Expand All @@ -171,9 +171,9 @@ public function checkExportability(IUser $user, ?array $filteredMigratorList = n
throw new NotExportableException('Error estimating export size');
}

$freeSpaceAfterExport = $freeSpace - $exportSize;
$freeSpaceAfterExport = ($freeSpace - $exportSize) / 1024;
if ($freeSpaceAfterExport < 0) {
throw new NotExportableException('Insufficient storage space available to export, please free up ' . (int)abs($freeSpaceAfterExport) . ' KiB or more to be able to export your data');
throw new NotExportableException('Insufficient storage space available, please free up ' . (int)abs($freeSpaceAfterExport) . ' MiB or more to be able to export your data without running out of space');
}
}

Expand Down
4 changes: 2 additions & 2 deletions src/admin-settings.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,8 @@ import Vue from 'vue'
import { translate as t, translatePlural as n } from '@nextcloud/l10n'
import '@nextcloud/dialogs/styles/toast.scss'

import logger from './logger'
import AdminSettings from './views/Admin/Settings'
import logger from './shared/logger.js'
import AdminSettings from './views/Admin/Settings.vue'

Vue.mixin({ props: { logger }, methods: { t, n } })

Expand Down
35 changes: 23 additions & 12 deletions src/components/ExportSection.vue
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@
</template>
{{ t('user_migration', 'Export') }}
</Button>
<span v-if="estimatedSizeWithUnits" class="settings-hint">{{ t('user_migration', 'Estimated size: {estimatedSizeWithUnits}', { estimatedSizeWithUnits: this.estimatedSizeWithUnits }) }}</span>
<div v-if="startingExport" class="icon-loading section__loading" />
</div>

Expand Down Expand Up @@ -120,8 +121,6 @@
</template>

<script>
import { showError } from '@nextcloud/dialogs'

import Button from '@nextcloud/vue/dist/Components/Button'
import CheckboxRadioSwitch from '@nextcloud/vue/dist/Components/CheckboxRadioSwitch'
import CheckCircleOutline from 'vue-material-design-icons/CheckCircleOutline'
Expand All @@ -130,7 +129,8 @@ import InformationOutline from 'vue-material-design-icons/InformationOutline'
import Modal from '@nextcloud/vue/dist/Components/Modal'
import PackageDown from 'vue-material-design-icons/PackageDown'

import { queueExportJob, cancelJob } from '../services/migrationService.js'
import { queueExportJob, cancelJob, checkExportability } from '../services/migrationService.js'
import { handleError, handleWarning } from '../shared/utils.js'

export default {
name: 'ExportSection',
Expand Down Expand Up @@ -166,10 +166,11 @@ export default {

data() {
return {
modalOpened: false,
startingExport: false,
cancellingExport: false,
estimatedSizeWithUnits: null,
modalOpened: false,
selectedMigrators: [],
startingExport: false,
}
},

Expand All @@ -192,12 +193,26 @@ export default {

watch: {
sortedMigrators: {
deep: true,
immediate: true,
handler(migrators, oldMigrators) {
this.selectedMigrators = migrators.map(({ id }) => id)
},
},

selectedMigrators: {
immediate: false,
async handler(migrators, oldMigrators) {
try {
const { estimatedSize, units, warning } = await checkExportability(migrators)
if (warning) {
handleWarning(warning)
}
this.estimatedSizeWithUnits = `${estimatedSize} ${units}`
} catch (error) {
handleError(error)
}
},
},
},

methods: {
Expand All @@ -211,9 +226,7 @@ export default {
})
} catch (error) {
this.startingExport = false
const errorMessage = error.message || 'Unknown error'
this.logger.error(`Error starting user export: ${errorMessage}`, { error })
showError(errorMessage)
handleError(error)
}
},

Expand All @@ -226,9 +239,7 @@ export default {
})
} catch (error) {
this.cancellingExport = false
const errorMessage = error.message || 'Unknown error'
this.logger.error(`Error cancelling user export: ${errorMessage}`, { error })
showError(errorMessage)
handleError(error)
}
},

Expand Down
12 changes: 4 additions & 8 deletions src/components/ImportSection.vue
Original file line number Diff line number Diff line change
Expand Up @@ -101,8 +101,7 @@
</template>

<script>
// import { getFilePickerBuilder, showError } from '@nextcloud/dialogs'
import { showError } from '@nextcloud/dialogs'
// import { getFilePickerBuilder } from '@nextcloud/dialogs'

import Button from '@nextcloud/vue/dist/Components/Button'
import CheckCircleOutline from 'vue-material-design-icons/CheckCircleOutline'
Expand All @@ -112,6 +111,7 @@ import Modal from '@nextcloud/vue/dist/Components/Modal'
import PackageUp from 'vue-material-design-icons/PackageUp'

import { queueImportJob, cancelJob } from '../services/migrationService.js'
import { handleError } from '../shared/utils.js'

/*
const picker = getFilePickerBuilder(t('user_migration', 'Choose a file to import'))
Expand Down Expand Up @@ -213,9 +213,7 @@ export default {
})
} catch (error) {
this.startingImport = false
const errorMessage = error.message || 'Unknown error'
this.logger.error(`Error starting user import: ${errorMessage}`, { error })
showError(errorMessage)
handleError(error)
}
} catch (error) {
const errorMessage = error.message || 'Unknown error'
Expand All @@ -233,9 +231,7 @@ export default {
})
} catch (error) {
this.cancellingImport = false
const errorMessage = error.message || 'Unknown error'
this.logger.error(`Error cancelling user import: ${errorMessage}`, { error })
showError(errorMessage)
handleError(error)
}
},

Expand Down
4 changes: 2 additions & 2 deletions src/personal-settings.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,8 @@ import Vue from 'vue'
import { translate as t, translatePlural as n } from '@nextcloud/l10n'
import '@nextcloud/dialogs/styles/toast.scss'

import logger from './logger'
import PersonalSettings from './views/Personal/Settings'
import logger from './shared/logger.js'
import PersonalSettings from './views/Personal/Settings.vue'

Vue.prototype.t = t
Vue.prototype.n = n
Expand Down
13 changes: 13 additions & 0 deletions src/services/migrationService.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import confirmPassword from '@nextcloud/password-confirmation'
import { generateOcsUrl } from '@nextcloud/router'

import { APP_ID, API_VERSION } from '../shared/constants.js'
import { formatQueryParamArray } from '../shared/utils.js'

/**
* @return {object}
Expand Down Expand Up @@ -59,6 +60,18 @@ export const cancelJob = async () => {
return response.data.ocs?.data
}

/**
* @param {string[]} migrators Array of migrators
*
* @return {object}
*/
export const checkExportability = async (migrators) => {
const url = generateOcsUrl('/apps/{appId}/api/v{apiVersion}/export', { appId: APP_ID, apiVersion: API_VERSION }) + formatQueryParamArray('migrators', migrators)
const response = await axios.get(url)

return response.data.ocs?.data
}

/**
* @param {string[]} migrators Array of migrators
*
Expand Down
3 changes: 2 additions & 1 deletion src/logger.js → src/shared/logger.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,10 @@
*
*/

import { APP_ID } from './shared/constants'
import { getLoggerBuilder } from '@nextcloud/logger'

import { APP_ID } from './constants.js'

export default getLoggerBuilder()
.setApp(APP_ID)
.detectUser()
Expand Down
75 changes: 75 additions & 0 deletions src/shared/utils.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
/**
* @copyright 2022 Christopher Ng <[email protected]>
*
* @author Christopher Ng <[email protected]>
*
* @license AGPL-3.0-or-later
*
* 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 { showWarning, showError } from '@nextcloud/dialogs'

import logger from './logger.js'

/**
* @param {AxiosError|string} error Error or message
*
* @return {string}
*/
const parseMessage = (error) => {
if (typeof error === 'string') {
return error || 'Unknown error'
}
return error.response.data.ocs?.meta?.message || 'Unknown error'
}

/**
* @param {AxiosError|string} error Error or message
* @param {import('@nextcloud/dialogs/dist/toast').ToastOptions} toastOptions Toast options
*
* @return {void}
*/
export const handleWarning = (error, toastOptions = {}) => {
const message = parseMessage(error)
logger.warn(message, { error })
showWarning(message, toastOptions)
}

/**
* @param {AxiosError|string} error Error or message
* @param {import('@nextcloud/dialogs/dist/toast').ToastOptions} toastOptions Toast options
*
* @return {void}
*/
export const handleError = (error, toastOptions = {}) => {
const message = parseMessage(error)
logger.error(message, { error })
showError(message, toastOptions)
}

/**
* @param {string} name Name of the query parameter
* @param {string[]} values Array of values
*
* @return {string}
*/
export const formatQueryParamArray = (name, values) => {
if (values.length === 0) {
return `?${name}[]=`
}

return `?${values.map(value => `${name}[]=${value}`).join('&')}`
}
10 changes: 3 additions & 7 deletions src/views/Personal/Settings.vue
Original file line number Diff line number Diff line change
Expand Up @@ -36,12 +36,12 @@

<script>
import { getCapabilities } from '@nextcloud/capabilities'
import { showError } from '@nextcloud/dialogs'

import { getMigrators, getStatus } from '../../services/migrationService.js'

import ExportSection from '../../components/ExportSection.vue'
import ImportSection from '../../components/ImportSection.vue'
import { handleError } from '../../shared/utils.js'

// Polling interval in seconds
const STATUS_POLLING_INTERVAL = 30
Expand Down Expand Up @@ -80,19 +80,15 @@ export default {
try {
this.migrators = await getMigrators()
} catch (error) {
const errorMessage = error.message || 'Unknown error'
this.logger.error(`Error getting available migrators: ${errorMessage}`, { error })
showError(errorMessage)
handleError(error)
}
},

async fetchStatus() {
try {
this.status = await getStatus()
} catch (error) {
const errorMessage = error.message || 'Unknown error'
this.logger.error(`Error polling server for export and import status: ${errorMessage}`, { error })
showError(errorMessage)
handleError(error)
}
},

Expand Down