Allow changing base folder (fix #9)

pull/37/head
Varun Patil 2022-08-20 00:18:04 +00:00
parent 36c9b0f79d
commit 6b8f53e89b
11 changed files with 227 additions and 13 deletions

View File

@ -1,6 +1,7 @@
<?php <?php
return [ return [
'routes' => [ 'routes' => [
// Days and album API
['name' => 'page#main', 'url' => '/', 'verb' => 'GET'], ['name' => 'page#main', 'url' => '/', 'verb' => 'GET'],
['name' => 'page#album', 'url' => '/albums/{path}', 'verb' => 'GET', ['name' => 'page#album', 'url' => '/albums/{path}', 'verb' => 'GET',
'requirements' => [ 'requirements' => [
@ -16,5 +17,8 @@ return [
['name' => 'api#day', 'url' => '/api/days/{id}', 'verb' => 'GET'], ['name' => 'api#day', 'url' => '/api/days/{id}', 'verb' => 'GET'],
['name' => 'api#folder', 'url' => '/api/folder/{folder}', 'verb' => 'GET'], ['name' => 'api#folder', 'url' => '/api/folder/{folder}', 'verb' => 'GET'],
['name' => 'api#folderDay', 'url' => '/api/folder/{folder}/{dayId}', 'verb' => 'GET'], ['name' => 'api#folderDay', 'url' => '/api/folder/{folder}/{dayId}', 'verb' => 'GET'],
// Config API
['name' => 'api#setUserConfig', 'url' => '/api/config/{key}', 'verb' => 'PUT'],
] ]
]; ];

File diff suppressed because one or more lines are too long

View File

@ -1,3 +1,9 @@
/*!
* vue-router v3.5.4
* (c) 2022 Evan You
* @license MIT
*/
/*! /*!
* Determine if an object is a Buffer * Determine if an object is a Buffer
* *
@ -25,10 +31,20 @@
* Released under the MIT License. * Released under the MIT License.
*/ */
/*!
* escape-html
* Copyright(c) 2012-2013 TJ Holowaychuk
* Copyright(c) 2015 Andreas Lubbe
* Copyright(c) 2015 Tiancheng "Timothy" Gu
* MIT Licensed
*/
/*! For license information please see AppNavigation.js.LICENSE.txt */ /*! For license information please see AppNavigation.js.LICENSE.txt */
/*! For license information please see AppNavigationItem.js.LICENSE.txt */ /*! For license information please see AppNavigationItem.js.LICENSE.txt */
/*! For license information please see AppNavigationSettings.js.LICENSE.txt */
/*! Hammer.JS - v2.0.7 - 2016-04-22 /*! Hammer.JS - v2.0.7 - 2016-04-22
* http://hammerjs.github.io/ * http://hammerjs.github.io/
* *
@ -111,6 +127,28 @@
* *
*/ */
/**
* @copyright Copyright (c) 2020 John Molakvoæ <skjnldsv@protonmail.com>
*
* @author John Molakvoæ <skjnldsv@protonmail.com>
*
* @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/>.
*
*/
/** /**
* @license nested-property https://github.com/cosmosio/nested-property * @license nested-property https://github.com/cosmosio/nested-property
* *

File diff suppressed because one or more lines are too long

View File

@ -75,7 +75,7 @@ class ApiController extends Controller {
return new JSONResponse([], Http::STATUS_PRECONDITION_FAILED); return new JSONResponse([], Http::STATUS_PRECONDITION_FAILED);
} }
$list = $this->util->getDays($user->getUID()); $list = $this->util->getDays($this->config, $user->getUID());
return new JSONResponse($list, Http::STATUS_OK); return new JSONResponse($list, Http::STATUS_OK);
} }
@ -90,7 +90,7 @@ class ApiController extends Controller {
return new JSONResponse([], Http::STATUS_PRECONDITION_FAILED); return new JSONResponse([], Http::STATUS_PRECONDITION_FAILED);
} }
$list = $this->util->getDay($user->getUID(), intval($id)); $list = $this->util->getDay($this->config, $user->getUID(), intval($id));
return new JSONResponse($list, Http::STATUS_OK); return new JSONResponse($list, Http::STATUS_OK);
} }

View File

@ -2,27 +2,40 @@
namespace OCA\Memories\Controller; namespace OCA\Memories\Controller;
use OCP\IRequest; use OCP\IRequest;
use OCP\AppFramework\Services\IInitialState;
use OCP\AppFramework\Http\TemplateResponse; use OCP\AppFramework\Http\TemplateResponse;
use OCA\Viewer\Event\LoadViewer; use OCA\Viewer\Event\LoadViewer;
use OCA\Files\Event\LoadSidebar;
use OCP\AppFramework\Controller; use OCP\AppFramework\Controller;
use OCP\EventDispatcher\IEventDispatcher; use OCP\EventDispatcher\IEventDispatcher;
use OCP\IConfig;
use OCP\IUserSession;
use OCP\Util; use OCP\Util;
class PageController extends Controller { class PageController extends Controller {
protected string $userId; protected string $userId;
protected $appName; protected $appName;
protected IEventDispatcher $eventDispatcher; protected IEventDispatcher $eventDispatcher;
private IInitialState $initialState;
private IUserSession $userSession;
private IConfig $config;
public function __construct( public function __construct(
string $AppName, string $AppName,
IRequest $request, IRequest $request,
string $UserId, string $UserId,
IEventDispatcher $eventDispatcher, IEventDispatcher $eventDispatcher,
IInitialState $initialState,
IUserSession $userSession,
IConfig $config,
){ ){
parent::__construct($AppName, $request); parent::__construct($AppName, $request);
$this->userId = $UserId; $this->userId = $UserId;
$this->appName = $AppName; $this->appName = $AppName;
$this->eventDispatcher = $eventDispatcher; $this->eventDispatcher = $eventDispatcher;
$this->initialState = $initialState;
$this->userSession = $userSession;
$this->config = $config;
} }
/** /**
@ -30,11 +43,21 @@ class PageController extends Controller {
* @NoCSRFRequired * @NoCSRFRequired
*/ */
public function main() { public function main() {
$user = $this->userSession->getUser();
if (is_null($user)) {
return null;
}
Util::addScript($this->appName, 'memories-main'); Util::addScript($this->appName, 'memories-main');
Util::addStyle($this->appName, 'custom-icons'); Util::addStyle($this->appName, 'custom-icons');
$this->eventDispatcher->dispatchTyped(new LoadSidebar());
$this->eventDispatcher->dispatchTyped(new LoadViewer()); $this->eventDispatcher->dispatchTyped(new LoadViewer());
$timelinePath = \OCA\Memories\Db\Util::getPhotosPath($this->config, $user->getUid());
$this->initialState->provideInitialState('timelinePath', $timelinePath);
$response = new TemplateResponse($this->appName, 'main'); $response = new TemplateResponse($this->appName, 'main');
return $response; return $response;
} }

View File

@ -5,6 +5,7 @@ namespace OCA\Memories\Db;
use OCA\Memories\AppInfo\Application; use OCA\Memories\AppInfo\Application;
use OCP\Files\File; use OCP\Files\File;
use OCP\IConfig;
use OCP\IDBConnection; use OCP\IDBConnection;
class Util { class Util {
@ -14,6 +15,14 @@ class Util {
$this->connection = $connection; $this->connection = $connection;
} }
public static function getPhotosPath(IConfig $config, string $userId) {
$p = $config->getUserValue($userId, Application::APPNAME, 'timelinePath', '');
if (empty($p)) {
return '/Photos/';
}
return $p;
}
private static function getExif(File $file) { private static function getExif(File $file) {
// Attempt to read exif data // Attempt to read exif data
try { try {
@ -146,18 +155,21 @@ class Util {
} }
public function getDays( public function getDays(
IConfig $config,
string $user, string $user,
): array { ): array {
$sql = 'SELECT day_id, COUNT(file_id) AS count $sql = 'SELECT day_id, COUNT(file_id) AS count
FROM `*PREFIX*memories` FROM `*PREFIX*memories`
INNER JOIN `*PREFIX*filecache` INNER JOIN `*PREFIX*filecache`
ON `*PREFIX*filecache`.`fileid` = `*PREFIX*memories`.`file_id` ON `*PREFIX*filecache`.`fileid` = `*PREFIX*memories`.`file_id`
AND `*PREFIX*filecache`.`path` LIKE "files/Photos/%" AND `*PREFIX*filecache`.`path` LIKE ?
WHERE user_id=? WHERE user_id=?
GROUP BY day_id GROUP BY day_id
ORDER BY day_id DESC'; ORDER BY day_id DESC';
$rows = $this->connection->executeQuery($sql, [$user], [
\PDO::PARAM_STR, $path = "files" . self::getPhotosPath($config, $user) . "%";
$rows = $this->connection->executeQuery($sql, [$path, $user], [
\PDO::PARAM_STR, \PDO::PARAM_STR,
])->fetchAll(); ])->fetchAll();
return $this->processDays($rows); return $this->processDays($rows);
} }
@ -188,6 +200,7 @@ class Util {
} }
public function getDay( public function getDay(
IConfig $config,
string $user, string $user,
int $dayId, int $dayId,
): array { ): array {
@ -195,11 +208,13 @@ class Util {
FROM *PREFIX*memories FROM *PREFIX*memories
INNER JOIN *PREFIX*filecache INNER JOIN *PREFIX*filecache
ON *PREFIX*filecache.fileid = *PREFIX*memories.file_id ON *PREFIX*filecache.fileid = *PREFIX*memories.file_id
AND `*PREFIX*filecache`.`path` LIKE "files/Photos/%" AND `*PREFIX*filecache`.`path` LIKE ?
WHERE user_id = ? AND day_id = ? WHERE user_id = ? AND day_id = ?
ORDER BY date_taken DESC'; ORDER BY date_taken DESC';
$rows = $this->connection->executeQuery($sql, [$user, $dayId], [
\PDO::PARAM_STR, \PDO::PARAM_INT, $path = "files" . self::getPhotosPath($config, $user) . "%";
$rows = $this->connection->executeQuery($sql, [$path, $user, $dayId], [
\PDO::PARAM_STR, \PDO::PARAM_STR, \PDO::PARAM_INT,
])->fetchAll(); ])->fetchAll();
return $this->processDay($rows); return $this->processDay($rows);
} }

View File

@ -12,6 +12,11 @@
icon="icon-files-dark"> icon="icon-files-dark">
</AppNavigationItem> </AppNavigationItem>
</template> </template>
<template #footer>
<AppNavigationSettings :title="t('memories', 'Settings')">
<Settings />
</AppNavigationSettings>
</template>
</AppNavigation> </AppNavigation>
<AppContent> <AppContent>
@ -34,7 +39,10 @@ import Content from '@nextcloud/vue/dist/Components/Content'
import AppContent from '@nextcloud/vue/dist/Components/AppContent' import AppContent from '@nextcloud/vue/dist/Components/AppContent'
import AppNavigation from '@nextcloud/vue/dist/Components/AppNavigation' import AppNavigation from '@nextcloud/vue/dist/Components/AppNavigation'
import AppNavigationItem from '@nextcloud/vue/dist/Components/AppNavigationItem' import AppNavigationItem from '@nextcloud/vue/dist/Components/AppNavigationItem'
import AppNavigationSettings from '@nextcloud/vue/dist/Components/AppNavigationSettings'
import Timeline from './components/Timeline.vue' import Timeline from './components/Timeline.vue'
import Settings from './components/Settings.vue'
export default { export default {
name: 'App', name: 'App',
@ -43,7 +51,10 @@ export default {
AppContent, AppContent,
AppNavigation, AppNavigation,
AppNavigationItem, AppNavigationItem,
AppNavigationSettings,
Timeline, Timeline,
Settings,
}, },
data() { data() {
return { return {

View File

@ -0,0 +1,62 @@
<!--
- @copyright Copyright (c) 2019 John Molakvoæ <skjnldsv@protonmail.com>
-
- @author John Molakvoæ <skjnldsv@protonmail.com>
-
- @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/>.
-
-->
<template>
<div>
<label for="timeline-path">{{ t('memories', 'Timeline Path') }}</label>
<input id="timeline-path"
v-model="timelinePath"
type="text">
<button @click="updateAll()">
{{ t('memories', 'Update') }}
</button>
</div>
</template>
<style>
input[type=text] {
width: 100%;
}
</style>
<script>
import UserConfig from '../mixins/UserConfig'
export default {
name: 'Settings',
mixins: [
UserConfig,
],
methods: {
async updateAll() {
const res = await this.updateSetting('timelinePath');
if (res.status === 200) {
window.location.reload();
} else {
alert('Error updating settings');
}
},
},
}
</script>

View File

@ -75,6 +75,7 @@
import * as dav from "../services/DavRequests"; import * as dav from "../services/DavRequests";
import axios from '@nextcloud/axios' import axios from '@nextcloud/axios'
import { generateUrl } from '@nextcloud/router'
export default { export default {
data() { data() {
@ -323,7 +324,7 @@ export default {
} }
const startState = this.state; const startState = this.state;
const res = await axios.get(url); const res = await axios.get(generateUrl(url));
const data = res.data; const data = res.data;
if (this.state !== startState) return; if (this.state !== startState) return;
@ -428,7 +429,7 @@ export default {
let data = []; let data = [];
try { try {
const startState = this.state; const startState = this.state;
const res = await axios.get(url); const res = await axios.get(generateUrl(url));
const data = res.data; const data = res.data;
if (this.state !== startState) return; if (this.state !== startState) return;

View File

@ -0,0 +1,60 @@
/**
* @copyright Copyright (c) 2020 John Molakvoæ <skjnldsv@protonmail.com>
*
* @author John Molakvoæ <skjnldsv@protonmail.com>
*
* @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 { emit, subscribe, unsubscribe } from '@nextcloud/event-bus'
import { generateUrl } from '@nextcloud/router'
import { loadState } from '@nextcloud/initial-state'
import axios from '@nextcloud/axios'
const eventName = 'memories:user-config-changed'
export default {
data() {
return {
timelinePath: loadState('memories', 'timelinePath') || '',
}
},
created() {
subscribe(eventName, this.updateLocalSetting)
},
beforeDestroy() {
unsubscribe(eventName, this.updateLocalSetting)
},
methods: {
updateLocalSetting({ setting, value }) {
this[setting] = value
},
async updateSetting(setting) {
const value = this[setting]
// Long time save setting
const res = await axios.put(generateUrl('apps/memories/api/config/' + setting), {
value: value.toString(),
})
// Visible elements update setting
emit(eventName, { setting, value })
return res;
},
},
}