Add basic folder share stuff
parent
c8727c4f28
commit
9209b8f55d
|
@ -24,6 +24,9 @@ return [
|
|||
'defaults' => [ 'name' => '' ]
|
||||
],
|
||||
|
||||
// Public pages
|
||||
['name' => 'page#sharedFolder', 'url' => '/s/{token}', 'verb' => 'GET'],
|
||||
|
||||
// API
|
||||
['name' => 'api#days', 'url' => '/api/days', 'verb' => 'GET'],
|
||||
['name' => 'api#dayPost', 'url' => '/api/days', 'verb' => 'POST'],
|
||||
|
|
|
@ -43,6 +43,7 @@ use OCP\IDBConnection;
|
|||
use OCP\IPreview;
|
||||
use OCP\IRequest;
|
||||
use OCP\IUserSession;
|
||||
use OCP\Share\IManager as IShareManager;
|
||||
|
||||
class ApiController extends Controller
|
||||
{
|
||||
|
@ -53,6 +54,7 @@ class ApiController extends Controller
|
|||
private IAppManager $appManager;
|
||||
private TimelineQuery $timelineQuery;
|
||||
private TimelineWrite $timelineWrite;
|
||||
private IShareManager $shareManager;
|
||||
private IPreview $preview;
|
||||
|
||||
public function __construct(
|
||||
|
@ -62,6 +64,7 @@ class ApiController extends Controller
|
|||
IDBConnection $connection,
|
||||
IRootFolder $rootFolder,
|
||||
IAppManager $appManager,
|
||||
IShareManager $shareManager,
|
||||
IPreview $preview
|
||||
) {
|
||||
parent::__construct(Application::APPNAME, $request);
|
||||
|
@ -71,6 +74,7 @@ class ApiController extends Controller
|
|||
$this->connection = $connection;
|
||||
$this->rootFolder = $rootFolder;
|
||||
$this->appManager = $appManager;
|
||||
$this->shareManager = $shareManager;
|
||||
$this->previewManager = $preview;
|
||||
$this->timelineQuery = new TimelineQuery($this->connection);
|
||||
$this->timelineWrite = new TimelineWrite($connection, $preview);
|
||||
|
@ -78,22 +82,28 @@ class ApiController extends Controller
|
|||
|
||||
/**
|
||||
* @NoAdminRequired
|
||||
*
|
||||
* @PublicPage
|
||||
*/
|
||||
public function days(): JSONResponse
|
||||
{
|
||||
$user = $this->userSession->getUser();
|
||||
if (null === $user) {
|
||||
if (null === $user && !$this->getShareToken()) {
|
||||
return new JSONResponse([], Http::STATUS_PRECONDITION_FAILED);
|
||||
}
|
||||
$uid = $user->getUID();
|
||||
$uid = $user ? $user->getUID() : '';
|
||||
|
||||
// Get the folder to show
|
||||
$folder = null;
|
||||
|
||||
try {
|
||||
$folder = $this->getRequestFolder();
|
||||
} catch (\Exception $e) {
|
||||
return new JSONResponse(['message' => $e->getMessage()], Http::STATUS_NOT_FOUND);
|
||||
}
|
||||
|
||||
$recursive = null === $this->request->getParam('folder');
|
||||
$archive = null !== $this->request->getParam('archive');
|
||||
if (null === $folder) {
|
||||
return new JSONResponse(['message' => 'Folder not found'], Http::STATUS_NOT_FOUND);
|
||||
}
|
||||
|
||||
// Remove folder if album
|
||||
// Permissions will be checked during the transform
|
||||
|
@ -127,6 +137,8 @@ class ApiController extends Controller
|
|||
|
||||
/**
|
||||
* @NoAdminRequired
|
||||
*
|
||||
* @PublicPage
|
||||
*/
|
||||
public function dayPost(): JSONResponse
|
||||
{
|
||||
|
@ -140,14 +152,16 @@ class ApiController extends Controller
|
|||
|
||||
/**
|
||||
* @NoAdminRequired
|
||||
*
|
||||
* @PublicPage
|
||||
*/
|
||||
public function day(string $id): JSONResponse
|
||||
{
|
||||
$user = $this->userSession->getUser();
|
||||
if (null === $user) {
|
||||
if (null === $user && !$this->getShareToken()) {
|
||||
return new JSONResponse([], Http::STATUS_PRECONDITION_FAILED);
|
||||
}
|
||||
$uid = $user->getUID();
|
||||
$uid = $user ? $user->getUID() : '';
|
||||
|
||||
// Check for wildcard
|
||||
$day_ids = [];
|
||||
|
@ -664,6 +678,8 @@ class ApiController extends Controller
|
|||
/**
|
||||
* @NoAdminRequired
|
||||
*
|
||||
* @PublicPage
|
||||
*
|
||||
* @NoCSRFRequired
|
||||
*/
|
||||
public function serviceWorker(): StreamResponse
|
||||
|
@ -697,6 +713,11 @@ class ApiController extends Controller
|
|||
$transforms[] = [$this->timelineQuery, 'transformExtraFields', $fields];
|
||||
}
|
||||
|
||||
// Other transforms not allowed for public shares
|
||||
if (null === $this->userSession->getUser()) {
|
||||
return $transforms;
|
||||
}
|
||||
|
||||
// Filter only favorites
|
||||
if ($this->request->getParam('fav')) {
|
||||
$transforms[] = [$this->timelineQuery, 'transformFavoriteFilter'];
|
||||
|
@ -753,7 +774,9 @@ class ApiController extends Controller
|
|||
*/
|
||||
private function preloadDays(array &$days, &$folder, bool $recursive, bool $archive)
|
||||
{
|
||||
$uid = $this->userSession->getUser()->getUID();
|
||||
$user = $this->userSession->getUser();
|
||||
$uid = $user ? $user->getUID() : '';
|
||||
|
||||
$transforms = $this->getTransformations(false);
|
||||
$preloaded = 0;
|
||||
$preloadDayIds = [];
|
||||
|
@ -799,9 +822,23 @@ class ApiController extends Controller
|
|||
/** Get the Folder object relevant to the request */
|
||||
private function getRequestFolder()
|
||||
{
|
||||
$uid = $this->userSession->getUser()->getUID();
|
||||
$user = $this->userSession->getUser();
|
||||
if (null === $user) {
|
||||
// Public shares only
|
||||
if ($token = $this->getShareToken()) {
|
||||
$share = $this->shareManager->getShareByToken($token)->getNode(); // throws exception if not found
|
||||
if (!$share instanceof Folder) {
|
||||
throw new \Exception('Share not found or invalid');
|
||||
}
|
||||
|
||||
return $share;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
$uid = $user->getUID();
|
||||
|
||||
try {
|
||||
$folder = null;
|
||||
$folderPath = $this->request->getParam('folder');
|
||||
$forcedTimelinePath = $this->request->getParam('timelinePath');
|
||||
|
@ -819,13 +856,15 @@ class ApiController extends Controller
|
|||
if (!$folder instanceof Folder) {
|
||||
throw new \Exception('Folder not found');
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $folder;
|
||||
}
|
||||
|
||||
private function getShareToken()
|
||||
{
|
||||
return $this->request->getParam('folder_share');
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if albums are enabled for this user.
|
||||
*/
|
||||
|
|
|
@ -8,6 +8,7 @@ use OCA\Viewer\Event\LoadViewer;
|
|||
use OCP\App\IAppManager;
|
||||
use OCP\AppFramework\Controller;
|
||||
use OCP\AppFramework\Http\ContentSecurityPolicy;
|
||||
use OCP\AppFramework\Http\Template\PublicTemplateResponse;
|
||||
use OCP\AppFramework\Http\TemplateResponse;
|
||||
use OCP\AppFramework\Services\IInitialState;
|
||||
use OCP\EventDispatcher\IEventDispatcher;
|
||||
|
@ -103,6 +104,31 @@ class PageController extends Controller
|
|||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* @PublicPage
|
||||
*
|
||||
* @NoCSRFRequired
|
||||
*/
|
||||
public function sharedFolder(string $token)
|
||||
{
|
||||
// Scripts
|
||||
Util::addScript($this->appName, 'memories-main');
|
||||
$this->eventDispatcher->dispatchTyped(new LoadSidebar());
|
||||
$this->eventDispatcher->dispatchTyped(new LoadViewer());
|
||||
|
||||
// App version
|
||||
$this->initialState->provideInitialState('version', $this->appManager->getAppInfo('memories')['version']);
|
||||
|
||||
$policy = new ContentSecurityPolicy();
|
||||
$policy->addAllowedWorkerSrcDomain("'self'");
|
||||
$policy->addAllowedScriptDomain("'self'");
|
||||
|
||||
$response = new PublicTemplateResponse($this->appName, 'main');
|
||||
$response->setContentSecurityPolicy($policy);
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* @NoAdminRequired
|
||||
*
|
||||
|
|
|
@ -102,7 +102,7 @@ trait TimelineQueryDays
|
|||
// We don't actually use m.datetaken here, but postgres
|
||||
// needs that all fields in ORDER BY are also in SELECT
|
||||
// when using DISTINCT on selected fields
|
||||
$query->select($fileid, 'f.etag', 'm.isvideo', 'vco.categoryid', 'm.datetaken', 'm.dayid', 'm.w', 'm.h')
|
||||
$query->select($fileid, 'f.etag', 'f.path', 'm.isvideo', 'vco.categoryid', 'm.datetaken', 'm.dayid', 'm.w', 'm.h')
|
||||
->from('memories', 'm')
|
||||
;
|
||||
$query = $this->joinFilecache($query, $folder, $recursive, $archive);
|
||||
|
@ -129,7 +129,7 @@ trait TimelineQueryDays
|
|||
$rows = $cursor->fetchAll();
|
||||
$cursor->closeCursor();
|
||||
|
||||
return $this->processDay($rows);
|
||||
return $this->processDay($rows, $folder);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -151,9 +151,12 @@ trait TimelineQueryDays
|
|||
* Process the single day response.
|
||||
*
|
||||
* @param array $day
|
||||
* @param null|Folder $folder
|
||||
*/
|
||||
private function processDay(&$day)
|
||||
private function processDay(&$day, $folder)
|
||||
{
|
||||
$basePath = null !== $folder ? $folder->getInternalPath() : '#__#__#';
|
||||
|
||||
foreach ($day as &$row) {
|
||||
// We don't need date taken (see query builder)
|
||||
unset($row['datetaken']);
|
||||
|
@ -172,6 +175,14 @@ trait TimelineQueryDays
|
|||
}
|
||||
unset($row['categoryid']);
|
||||
|
||||
// Check if path exists and starts with basePath and remove
|
||||
if (isset($row['path']) && !empty($row['path'])) {
|
||||
if (0 === strpos($row['path'], $basePath)) {
|
||||
$row['filename'] = substr($row['path'], \strlen($basePath));
|
||||
}
|
||||
unset($row['path']);
|
||||
}
|
||||
|
||||
// All transform processing
|
||||
$this->processFace($row);
|
||||
}
|
||||
|
|
|
@ -532,6 +532,11 @@ export default class Timeline extends Mixins(GlobalMixin, UserConfig) {
|
|||
query.set("fields", "basename,mimetype");
|
||||
}
|
||||
|
||||
// Favorites
|
||||
if (this.$route.name === "folder-share") {
|
||||
query.set("folder_share", this.$route.params.token);
|
||||
}
|
||||
|
||||
// Create query string and append to URL
|
||||
const queryStr = query.toString();
|
||||
if (queryStr) {
|
||||
|
|
|
@ -23,7 +23,7 @@
|
|||
class="fill-block"
|
||||
:class="{ error: info.flag & c.FLAG_LOAD_FAIL }"
|
||||
:key="'fpreview-' + info.fileid"
|
||||
:src="getPreviewUrl(info.fileid, info.etag, true, 256)"
|
||||
:src="getPreviewUrl(info, true, 256)"
|
||||
@error="info.flag |= c.FLAG_LOAD_FAIL"
|
||||
/>
|
||||
</div>
|
||||
|
|
|
@ -40,16 +40,14 @@
|
|||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { Component, Prop, Emit, Mixins, Watch } from "vue-property-decorator";
|
||||
import { IDay, IPhoto } from "../../types";
|
||||
|
||||
import { getPhotosPreviewUrl, getPreviewUrl } from "../../services/FileUtils";
|
||||
import Check from "vue-material-design-icons/Check.vue";
|
||||
import Star from "vue-material-design-icons/Star.vue";
|
||||
import Video from "vue-material-design-icons/Video.vue";
|
||||
import { Component, Emit, Mixins, Prop, Watch } from "vue-property-decorator";
|
||||
import errorsvg from "../../assets/error.svg";
|
||||
import GlobalMixin from "../../mixins/GlobalMixin";
|
||||
|
||||
import Check from "vue-material-design-icons/Check.vue";
|
||||
import Video from "vue-material-design-icons/Video.vue";
|
||||
import Star from "vue-material-design-icons/Star.vue";
|
||||
import { getPreviewUrl } from "../../services/FileUtils";
|
||||
import { IDay, IPhoto } from "../../types";
|
||||
|
||||
@Component({
|
||||
components: {
|
||||
|
@ -122,9 +120,7 @@ export default class Photo extends Mixins(GlobalMixin) {
|
|||
) - 1;
|
||||
}
|
||||
|
||||
const fun =
|
||||
this.$route.name === "albums" ? getPhotosPreviewUrl : getPreviewUrl;
|
||||
return fun(this.data.fileid, this.data.etag, false, size);
|
||||
return getPreviewUrl(this.data, false, size);
|
||||
}
|
||||
|
||||
/** Set src with overlay face rect */
|
||||
|
|
|
@ -24,7 +24,7 @@
|
|||
class="fill-block"
|
||||
:class="{ error: info.flag & c.FLAG_LOAD_FAIL }"
|
||||
:key="'fpreview-' + info.fileid"
|
||||
:src="getPreviewUrl(info.fileid, info.etag)"
|
||||
:src="getPreviewUrl(info)"
|
||||
@error="info.flag |= c.FLAG_LOAD_FAIL"
|
||||
/>
|
||||
</div>
|
||||
|
@ -80,7 +80,7 @@ export default class Tag extends Mixins(GlobalMixin) {
|
|||
this.refreshPreviews();
|
||||
}
|
||||
|
||||
getPreviewUrl(fileid: number, etag: string) {
|
||||
getPreviewUrl(photo: IPhoto) {
|
||||
if (this.isFace) {
|
||||
return generateUrl(
|
||||
"/apps/memories/api/faces/preview/" + this.data.fileid
|
||||
|
@ -88,10 +88,10 @@ export default class Tag extends Mixins(GlobalMixin) {
|
|||
}
|
||||
|
||||
if (this.isAlbum) {
|
||||
return getPhotosPreviewUrl(fileid, etag, true, 256);
|
||||
return getPhotosPreviewUrl(photo, true, 256);
|
||||
}
|
||||
|
||||
return getPreviewUrl(fileid, etag, true, 256);
|
||||
return getPreviewUrl(photo, true, 256);
|
||||
}
|
||||
|
||||
get isFace() {
|
||||
|
|
|
@ -89,7 +89,7 @@ import ImageMultiple from "vue-material-design-icons/ImageMultiple.vue";
|
|||
import { NcButton, NcListItem, NcLoadingIcon } from "@nextcloud/vue";
|
||||
import { generateUrl } from "@nextcloud/router";
|
||||
import { getPhotosPreviewUrl } from "../../services/FileUtils";
|
||||
import { IAlbum } from "../../types";
|
||||
import { IAlbum, IPhoto } from "../../types";
|
||||
import axios from "@nextcloud/axios";
|
||||
|
||||
@Component({
|
||||
|
@ -103,7 +103,13 @@ import axios from "@nextcloud/axios";
|
|||
},
|
||||
filters: {
|
||||
toCoverUrl(fileId: string) {
|
||||
return getPhotosPreviewUrl(Number(fileId), "unknown", true, 256);
|
||||
return getPhotosPreviewUrl(
|
||||
{
|
||||
fileid: Number(fileId),
|
||||
} as IPhoto,
|
||||
true,
|
||||
256
|
||||
);
|
||||
},
|
||||
},
|
||||
})
|
||||
|
|
|
@ -157,12 +157,7 @@ export default class OnThisDay extends Mixins(GlobalMixin) {
|
|||
|
||||
// Get random photo
|
||||
year.preview ||= utils.randomChoice(year.photos);
|
||||
year.url = getPreviewUrl(
|
||||
year.preview.fileid,
|
||||
year.preview.etag,
|
||||
false,
|
||||
512
|
||||
);
|
||||
year.url = getPreviewUrl(year.preview, false, 512);
|
||||
}
|
||||
|
||||
await this.$nextTick();
|
||||
|
|
|
@ -135,5 +135,14 @@ export default new Router({
|
|||
window.open(generateUrl("/apps/maps"), "_blank");
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
path: "/s/:token",
|
||||
component: Timeline,
|
||||
name: "folder-share",
|
||||
props: (route) => ({
|
||||
rootTitle: t("memories", "Shared Folder"),
|
||||
}),
|
||||
},
|
||||
],
|
||||
});
|
||||
|
|
|
@ -19,9 +19,10 @@
|
|||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
import camelcase from "camelcase";
|
||||
import { isNumber } from "./NumberUtils";
|
||||
import { generateUrl } from "@nextcloud/router";
|
||||
import camelcase from "camelcase";
|
||||
import { IExtendedPhoto, IFileInfo, IPhoto } from "../types";
|
||||
import { isNumber } from "./NumberUtils";
|
||||
|
||||
/**
|
||||
* Get an url encoded path
|
||||
|
@ -133,29 +134,41 @@ const genFileInfo = function (obj) {
|
|||
return fileInfo;
|
||||
};
|
||||
|
||||
/** Get preview URL from Nextcloud core */
|
||||
/** Get preview URL from photo object */
|
||||
const getPreviewUrl = function (
|
||||
fileid: number,
|
||||
etag: string,
|
||||
photo: IPhoto | IFileInfo,
|
||||
square: boolean,
|
||||
size: number
|
||||
): string {
|
||||
) {
|
||||
const a = square ? "0" : "1";
|
||||
|
||||
// Public preview
|
||||
if (vuerouter.currentRoute.name === "folder-share") {
|
||||
const token = vuerouter.currentRoute.params.token;
|
||||
return generateUrl(
|
||||
`/core/preview?fileId=${fileid}&c=${etag}&x=${size}&y=${size}&forceIcon=0&a=${a}`
|
||||
`/apps/files_sharing/publicpreview/${token}?file=${photo.filename}&fileId=${photo.fileid}&x=${size}&y=${size}&a=${a}`
|
||||
);
|
||||
}
|
||||
|
||||
// Albums from Photos
|
||||
if (vuerouter.currentRoute.name === "albums") {
|
||||
return getPhotosPreviewUrl(photo, square, size);
|
||||
}
|
||||
|
||||
return generateUrl(
|
||||
`/core/preview?fileId=${photo.fileid}&c=${photo.etag}&x=${size}&y=${size}&forceIcon=0&a=${a}`
|
||||
);
|
||||
};
|
||||
|
||||
/** Get the preview URL from the photos app */
|
||||
const getPhotosPreviewUrl = function (
|
||||
fileid: number,
|
||||
etag: string,
|
||||
photo: IPhoto | IFileInfo,
|
||||
square: boolean,
|
||||
size: number
|
||||
): string {
|
||||
const a = square ? "0" : "1";
|
||||
return generateUrl(
|
||||
`/apps/photos/api/v1/preview/${fileid}?c=${etag}&x=${size}&y=${size}&forceIcon=0&a=${a}`
|
||||
`/apps/photos/api/v1/preview/${photo.fileid}?c=${photo.etag}&x=${size}&y=${size}&forceIcon=0&a=${a}`
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
@ -37,6 +37,8 @@ export type IPhoto = {
|
|||
fileid: number;
|
||||
/** Etag from server */
|
||||
etag?: string;
|
||||
/** Path to file */
|
||||
filename?: string;
|
||||
/** Bit flags */
|
||||
flag: number;
|
||||
/** DayID from server */
|
||||
|
|
Loading…
Reference in New Issue