Skip to content

Commit 5b332d2

Browse files
artongenextcloud-command
authored andcommitted
feat: Allow multiple source folders
Signed-off-by: Louis Chemineau <[email protected]> Signed-off-by: nextcloud-command <[email protected]>
1 parent cad83bb commit 5b332d2

22 files changed

+133
-71
lines changed

js/photos-dashboard.js

+2-2
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

js/photos-dashboard.js.map

+1-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

js/photos-main.js

+2-2
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

js/photos-main.js.map

+1-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

js/photos-node_modules_blurhash_dist_esm_index_js-node_modules_vue-material-design-icons_PackageVariant-6bf9ec.js

+2-2
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

js/photos-node_modules_blurhash_dist_esm_index_js-node_modules_vue-material-design-icons_PackageVariant-6bf9ec.js.map

+1-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

js/photos-node_modules_vue-material-design-icons_ArrowLeft_vue-src_views_Timeline_vue.js

+2-2
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

js/photos-node_modules_vue-material-design-icons_ArrowLeft_vue-src_views_Timeline_vue.js.map

+1-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

js/photos-public.js

+2-2
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

js/photos-public.js.map

+1-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

js/photos-src_components_Collection_CollectionContent_vue-src_components_PhotosPicker_vue.js

+2-2
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

js/photos-src_components_Collection_CollectionContent_vue-src_components_PhotosPicker_vue.js.map

+1-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

js/photos-src_views_FaceContent_vue.js

+2-2
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

js/photos-src_views_FaceContent_vue.js.map

+1-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
/**
6+
* @copyright Copyright (c) 2024 Louis Chmn <[email protected]>
7+
*
8+
* @author Louis Chmn <[email protected]>
9+
*
10+
* @license GNU AGPL version 3 or any later version
11+
*
12+
* This program is free software: you can redistribute it and/or modify
13+
* it under the terms of the GNU Affero General Public License as
14+
* published by the Free Software Foundation, either version 3 of the
15+
* License, or (at your option) any later version.
16+
*
17+
* This program is distributed in the hope that it will be useful,
18+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
19+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20+
* GNU Affero General Public License for more details.
21+
*
22+
* You should have received a copy of the GNU Affero General Public License
23+
* along with this program. If not, see <http://www.gnu.org/licenses/>.
24+
*
25+
*/
26+
27+
namespace OCA\Photos\Migration;
28+
29+
use Closure;
30+
use OCP\DB\ISchemaWrapper;
31+
use OCP\IDBConnection;
32+
use OCP\Migration\IOutput;
33+
use OCP\Migration\SimpleMigrationStep;
34+
35+
/**
36+
* Migrate the photosSourceFolder user config to photosSourceFolders
37+
*/
38+
class Version3000Date20240417075404 extends SimpleMigrationStep {
39+
public function __construct(
40+
private IDBConnection $db,
41+
) {
42+
}
43+
44+
/**
45+
* @param IOutput $output
46+
* @param Closure(): ISchemaWrapper $schemaClosure
47+
* @param array $options
48+
*/
49+
public function postSchemaChange(IOutput $output, Closure $schemaClosure, array $options): void {
50+
$query = $this->db->getQueryBuilder();
51+
$query->update('preferences')
52+
->set('configvalue', $query->func()->concat($query->expr()->literal('["'), 'configvalue', $query->expr()->literal('"]')))
53+
->set('configkey', $query->expr()->literal('photosSourceFolders'))
54+
->where($query->expr()->eq('appid', $query->expr()->literal('photos')))
55+
->andWhere($query->expr()->eq('configkey', $query->expr()->literal('photosSourceFolder')))
56+
->executeStatement();
57+
}
58+
}

lib/Service/UserConfigService.php

+1-1
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ class UserConfigService {
3434
public const DEFAULT_CONFIGS = [
3535
'croppedLayout' => 'false',
3636
'photosLocation' => '/Photos',
37-
'photosSourceFolder' => '/Photos',
37+
'photosSourceFolders' => '["/Photos"]',
3838
];
3939

4040
private IConfig $config;

src/components/Settings/PhotosSourceLocationsSettings.vue

+23-20
Original file line numberDiff line numberDiff line change
@@ -23,24 +23,22 @@
2323
<template>
2424
<div class="photos-locations-container">
2525
<div class="photos-locations">
26-
<PhotosFolder :path="photosSourceFolder" :root-folder-label="t('photos', 'All folders')" :root-folder-icon="FolderMultiple" />
27-
<!-- TODO: uncomment when SEARCH on multiple folders is implemented. -->
28-
<!-- <li v-for="(source, index) in photosSourceFolder"
26+
<li v-for="(source, index) in photosSourceFolders"
2927
:key="index">
3028
<PhotosFolder :path="source"
31-
:can-delete="photosSourceFolder.length !== 1"
29+
:can-delete="photosSourceFolders.length !== 1"
3230
:root-folder-label="t('photos', 'All folders')"
31+
:root-folder-icon="FolderMultiple"
3332
@remove-folder="removeSourceFolder(index)" />
34-
</li> -->
33+
</li>
3534
</div>
3635

37-
<NcButton :aria-label="t('photos', 'Choose a Photos source for the timelines')"
36+
<NcButton :aria-label="t('photos', 'Add a Photos source for the timelines')"
3837
@click="debounceAddSourceFolder">
39-
<!-- TODO: uncomment when SEARCH on multiple folders is implemented. -->
40-
<!-- <template #icon>
38+
<template #icon>
4139
<Plus :size="20" />
42-
</template> -->
43-
{{ t('photos', 'Choose a different folder') }}
40+
</template>
41+
{{ t('photos', 'Add folder') }}
4442
</NcButton>
4543
</div>
4644
</template>
@@ -50,6 +48,7 @@ import debounce from 'debounce'
5048
import { defineComponent } from 'vue'
5149

5250
import FolderMultiple from 'vue-material-design-icons/FolderMultiple.vue'
51+
import Plus from 'vue-material-design-icons/Plus.vue'
5352

5453
import { NcButton } from '@nextcloud/vue'
5554
import { getFilePickerBuilder } from '@nextcloud/dialogs'
@@ -63,6 +62,7 @@ export default defineComponent({
6362
components: {
6463
NcButton,
6564
PhotosFolder,
65+
Plus,
6666
},
6767

6868
data() {
@@ -72,9 +72,9 @@ export default defineComponent({
7272
},
7373

7474
computed: {
75-
/** @return {string} */
76-
photosSourceFolder() {
77-
return this.$store.state.userConfig.photosSourceFolder
75+
/** @return {string[]} */
76+
photosSourceFolders() {
77+
return this.$store.state.userConfig.photosSourceFolders
7878
},
7979
},
8080

@@ -97,17 +97,16 @@ export default defineComponent({
9797

9898
async addSourceFolder() {
9999
const pickedFolder = await this.openFilePicker(t('photos', 'Select a source folder for your media'))
100-
// TODO: uncomment when SEARCH on multiple folders is implemented.
101-
// if (this.photosSourceFolder.includes(pickedFolder)) {
102-
// return
103-
// }
104-
this.$store.dispatch('updateUserConfig', { key: 'photosSourceFolder', value: pickedFolder })
100+
if (this.photosSourceFolders.includes(pickedFolder)) {
101+
return
102+
}
103+
this.$store.dispatch('updateUserConfig', { key: 'photosSourceFolders', value: [...this.photosSourceFolders, pickedFolder] })
105104
},
106105

107106
removeSourceFolder(index) {
108-
const folders = [...this.photosSourceFolder]
107+
const folders = [...this.photosSourceFolders]
109108
folders.splice(index, 1)
110-
this.$store.dispatch('updateUserConfig', { key: 'photosSourceFolder', value: folders })
109+
this.$store.dispatch('updateUserConfig', { key: 'photosSourceFolders', value: folders })
111110
},
112111

113112
t,
@@ -123,6 +122,10 @@ export default defineComponent({
123122

124123
.photos-locations {
125124
margin-bottom: 16px;
125+
126+
li {
127+
list-style: none;
128+
}
126129
}
127130
}
128131
</style>

src/components/Settings/SettingsDialog.vue

+1-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
<CroppedLayoutSettings />
88
</NcAppSettingsSection>
99

10-
<NcAppSettingsSection id="source-directories-settings" :name="t('photos', 'Media folder')">
10+
<NcAppSettingsSection id="source-directories-settings" :name="t('photos', 'Media folders')">
1111
<div class="setting-section-subline">
1212
{{ t('photos', 'Choose the folders from where photos and videos are shown.') }}
1313
</div>

src/mixins/FetchFilesMixin.js

+14-8
Original file line numberDiff line numberDiff line change
@@ -101,13 +101,20 @@ export default {
101101
return fileIds
102102
} catch (error) {
103103
if (error.response?.status === 404) {
104-
this.errorFetchingFiles = 404
105-
const source = joinPaths(davRootPath, store.state.userConfig.photosSourceFolder ?? '/Photos') + '/'
106-
logger.debug('Photo source does not exist, creating it.')
107-
try {
108-
await davGetClient().createDirectory(source)
109-
} catch (error) {
110-
logger.error('Fail to create source directory', { error })
104+
const sources = store.state.userConfig.photosSourceFolders
105+
for (const source of sources) {
106+
if (error.response?.data?.match(`File with name /${source} could not be located`) === null) {
107+
continue
108+
}
109+
logger.debug(`The ${source} folder does not exist, creating it.`)
110+
try {
111+
await davGetClient().createDirectory(joinPaths(davRootPath, source))
112+
this.resetFetchFilesState()
113+
return []
114+
} catch (error) {
115+
this.errorFetchingFiles = 404
116+
logger.error('Fail to create source directory', { error })
117+
}
111118
}
112119
} else if (error.code === 'ERR_CANCELED') {
113120
return []
@@ -117,7 +124,6 @@ export default {
117124

118125
// cancelled request, moving on...
119126
logger.error('Error fetching files', { error })
120-
console.error(error)
121127
} finally {
122128
this.loadingFiles = false
123129
this.fetchSemaphore.release(fetchSemaphoreSymbol)

src/services/PhotoSearch.js

+11-16
Original file line numberDiff line numberDiff line change
@@ -21,12 +21,13 @@
2121
*/
2222

2323
import { genFileInfo } from '../utils/fileUtils.js'
24-
import { getCurrentUser } from '@nextcloud/auth'
2524
import { allMimes } from './AllowedMimes.js'
2625
import client from './DavClient.js'
2726
import { props } from './DavRequest.js'
2827
import moment from '@nextcloud/moment'
2928
import store from '../store/index.js'
29+
import { davRootPath } from '@nextcloud/files'
30+
import { joinPaths } from '@nextcloud/paths'
3031

3132
/**
3233
* List files from a folder and filter out unwanted mimes
@@ -51,8 +52,6 @@ export default async function(options = {}) {
5152
...options,
5253
}
5354

54-
const prefixPath = `/files/${getCurrentUser().uid}`
55-
5655
// generating the search or condition
5756
// based on the allowed mimetypes
5857
const orMime = options.mimesType.reduce((str, mime) => `${str}
@@ -95,15 +94,14 @@ export default async function(options = {}) {
9594
}).join('\n')}</d:or>`
9695
: ''
9796

98-
// TODO: uncomment when SEARCH on multiple folders is implemented.
99-
// const sourceFolders = store.state.userConfig.photosSourceFolder
100-
// .map(folder => `
101-
// <d:scope>
102-
// <d:href>${davRootPath}/${folder}</d:href>
103-
// <d:depth>infinity</d:depth>
104-
// </d:scope>
105-
// `)
106-
// .join('\n')
97+
const sourceFolders = store.state.userConfig.photosSourceFolders
98+
.map(folder => `
99+
<d:scope>
100+
<d:href>${joinPaths(davRootPath, folder)}</d:href>
101+
<d:depth>infinity</d:depth>
102+
</d:scope>`
103+
)
104+
.join('\n')
107105

108106
options = Object.assign({
109107
method: 'SEARCH',
@@ -123,10 +121,7 @@ export default async function(options = {}) {
123121
</d:prop>
124122
</d:select>
125123
<d:from>
126-
<d:scope>
127-
<d:href>${prefixPath}/${store.state.userConfig.photosSourceFolder ?? '/Photos'}</d:href>
128-
<d:depth>infinity</d:depth>
129-
</d:scope>
124+
${sourceFolders}
130125
</d:from>
131126
<d:where>
132127
<d:and>

src/store/userConfig.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ export async function getFolder(path) {
5858
/**
5959
* @typedef {object} UserConfigState
6060
* @property {boolean} croppedLayout
61-
* @property {string} photosSourceFolder
61+
* @property {string[]} photosSourceFolders
6262
* @property {string} photosLocation
6363
* @property {import('@nextcloud/files').Folder} [photosLocationFolder]
6464
*/
@@ -68,7 +68,7 @@ const module = {
6868
state() {
6969
return {
7070
croppedLayout: loadState('photos', 'croppedLayout', 'false') === 'true',
71-
photosSourceFolder: loadState('photos', 'photosSourceFolder', ''),
71+
photosSourceFolders: JSON.parse(loadState('photos', 'photosSourceFolders', '["/Photos"]')),
7272
photosLocation: loadState('photos', 'photosLocation', ''),
7373
photosLocationFolder: undefined,
7474
}

src/views/Timeline.vue

+2-2
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525
<template>
2626
<!-- Errors handlers -->
2727
<div v-if="errorFetchingFiles" class="timeline__empty-content">
28-
<NcEmptyContent v-if="errorFetchingFiles === 404" :name="t('photos', 'The source folder does not exists')">
28+
<NcEmptyContent v-if="errorFetchingFiles === 404" :name="t('photos', 'One of the source folders does not exists')">
2929
<FolderAlertOutline slot="icon" />
3030
<PhotosSourceLocationsSettings slot="action" class="timeline__update_source_directory" />
3131
</NcEmptyContent>
@@ -314,7 +314,7 @@ export default {
314314
},
315315

316316
handleUserConfigChange({ key }) {
317-
if (key === 'photosSourceFolder') {
317+
if (key === 'photosSourceFolders') {
318318
this.resetFetchFilesState()
319319
}
320320
},

0 commit comments

Comments
 (0)