big: remove filename from IPhoto
Signed-off-by: Varun Patil <varunpatil@ucla.edu>pull/504/head
parent
1851c463c5
commit
b1df9215f9
|
@ -209,6 +209,11 @@ class ImageController extends ApiBase
|
|||
// Inject permissions and convert to string
|
||||
$info['permissions'] = \OCA\Memories\Util::permissionsToStr($file->getPermissions());
|
||||
|
||||
// Inject other file parameters that are cheap to get now
|
||||
$info['mimetype'] = $file->getMimeType();
|
||||
$info['size'] = $file->getSize();
|
||||
$info['basename'] = $file->getName();
|
||||
|
||||
return new JSONResponse($info, Http::STATUS_OK);
|
||||
}
|
||||
|
||||
|
|
|
@ -23,7 +23,7 @@ class TimelineQuery
|
|||
|
||||
public const TIMELINE_SELECT = [
|
||||
'm.isvideo', 'm.video_duration', 'm.datetaken', 'm.dayid', 'm.w', 'm.h', 'm.liveid',
|
||||
'f.etag', 'f.path', 'f.name AS basename', 'mimetypes.mimetype',
|
||||
'f.etag', 'f.name AS basename', 'mimetypes.mimetype',
|
||||
];
|
||||
|
||||
protected IDBConnection $connection;
|
||||
|
|
|
@ -4,23 +4,20 @@ declare(strict_types=1);
|
|||
|
||||
namespace OCA\Memories\Db;
|
||||
|
||||
use OCA\Memories\Exif;
|
||||
use OCP\DB\QueryBuilder\IQueryBuilder;
|
||||
use OCP\IDBConnection;
|
||||
|
||||
const CTE_FOLDERS = // CTE to get all folders recursively in the given top folders excluding archive
|
||||
'WITH RECURSIVE *PREFIX*cte_folders_all(fileid, rootid) AS (
|
||||
'WITH RECURSIVE *PREFIX*cte_folders_all(fileid) AS (
|
||||
SELECT
|
||||
f.fileid,
|
||||
f.fileid AS rootid
|
||||
f.fileid
|
||||
FROM
|
||||
*PREFIX*filecache f
|
||||
WHERE
|
||||
f.fileid IN (:topFolderIds)
|
||||
UNION ALL
|
||||
SELECT
|
||||
f.fileid,
|
||||
c.rootid
|
||||
f.fileid
|
||||
FROM
|
||||
*PREFIX*filecache f
|
||||
INNER JOIN *PREFIX*cte_folders_all c
|
||||
|
@ -30,8 +27,7 @@ const CTE_FOLDERS = // CTE to get all folders recursively in the given top folde
|
|||
)
|
||||
), *PREFIX*cte_folders AS (
|
||||
SELECT
|
||||
fileid,
|
||||
MIN(rootid) AS rootid
|
||||
fileid
|
||||
FROM
|
||||
*PREFIX*cte_folders_all
|
||||
GROUP BY
|
||||
|
@ -39,11 +35,10 @@ const CTE_FOLDERS = // CTE to get all folders recursively in the given top folde
|
|||
)';
|
||||
|
||||
const CTE_FOLDERS_ARCHIVE = // CTE to get all archive folders recursively in the given top folders
|
||||
'WITH RECURSIVE *PREFIX*cte_folders_all(fileid, name, rootid) AS (
|
||||
'WITH RECURSIVE *PREFIX*cte_folders_all(fileid, name) AS (
|
||||
SELECT
|
||||
f.fileid,
|
||||
f.name,
|
||||
f.fileid AS rootid
|
||||
f.name
|
||||
FROM
|
||||
*PREFIX*filecache f
|
||||
WHERE
|
||||
|
@ -51,18 +46,16 @@ const CTE_FOLDERS_ARCHIVE = // CTE to get all archive folders recursively in the
|
|||
UNION ALL
|
||||
SELECT
|
||||
f.fileid,
|
||||
f.name,
|
||||
c.rootid
|
||||
f.name
|
||||
FROM
|
||||
*PREFIX*filecache f
|
||||
INNER JOIN *PREFIX*cte_folders_all c
|
||||
ON (f.parent = c.fileid
|
||||
AND f.mimetype = (SELECT `id` FROM `*PREFIX*mimetypes` WHERE `mimetype` = \'httpd/unix-directory\')
|
||||
)
|
||||
), *PREFIX*cte_folders(fileid, rootid) AS (
|
||||
), *PREFIX*cte_folders(fileid) AS (
|
||||
SELECT
|
||||
cfa.fileid,
|
||||
MIN(cfa.rootid) AS rootid
|
||||
cfa.fileid
|
||||
FROM
|
||||
*PREFIX*cte_folders_all cfa
|
||||
WHERE
|
||||
|
@ -71,8 +64,7 @@ const CTE_FOLDERS_ARCHIVE = // CTE to get all archive folders recursively in the
|
|||
cfa.fileid
|
||||
UNION ALL
|
||||
SELECT
|
||||
f.fileid,
|
||||
c.rootid
|
||||
f.fileid
|
||||
FROM
|
||||
*PREFIX*filecache f
|
||||
INNER JOIN *PREFIX*cte_folders c
|
||||
|
@ -160,11 +152,6 @@ trait TimelineQueryDays
|
|||
// JOIN with filecache for existing files
|
||||
$query = $this->joinFilecache($query, $root, $recursive, $archive);
|
||||
|
||||
// SELECT rootid if not a single folder
|
||||
if ($recursive && !$root->isEmpty()) {
|
||||
$query->addSelect('cte_f.rootid');
|
||||
}
|
||||
|
||||
// JOIN with mimetypes to get the mimetype
|
||||
$query->join('f', 'mimetypes', 'mimetypes', $query->expr()->eq('f.mimetype', 'mimetypes.id'));
|
||||
|
||||
|
@ -213,56 +200,6 @@ trait TimelineQueryDays
|
|||
*/
|
||||
private function processDay(array &$day, string $uid, TimelineRoot &$root)
|
||||
{
|
||||
/**
|
||||
* Path entry in database for folder.
|
||||
* We need to splice this from the start of the file path.
|
||||
*/
|
||||
$internalPaths = [];
|
||||
|
||||
/**
|
||||
* DAV paths for the folders.
|
||||
* We need to prefix this to the start of the file path.
|
||||
*/
|
||||
$davPaths = [];
|
||||
|
||||
/**
|
||||
* The root folder id for the folder.
|
||||
* We fallback to this if rootid is not found.
|
||||
*/
|
||||
$defaultRootId = 0;
|
||||
|
||||
if (!$root->isEmpty()) {
|
||||
// Get root id of the top folder
|
||||
$defaultRootId = $root->getOneId();
|
||||
|
||||
// No way to get the internal path from the folder
|
||||
$query = $this->connection->getQueryBuilder();
|
||||
$query->select('fileid', 'path')
|
||||
->from('filecache')
|
||||
->where($query->expr()->in('fileid', $query->createNamedParameter($root->getIds(), IQueryBuilder::PARAM_INT_ARRAY)))
|
||||
;
|
||||
$paths = $query->executeQuery()->fetchAll();
|
||||
foreach ($paths as &$path) {
|
||||
$fileid = (int) $path['fileid'];
|
||||
$internalPaths[$fileid] = $path['path'];
|
||||
|
||||
// Get DAV path.
|
||||
// getPath looks like /user/files/... but we want /files/user/...
|
||||
// Split at / and swap these
|
||||
// For public shares, we just give the relative path
|
||||
if (!empty($uid) && ($actualPath = $root->getFolderPath($fileid))) {
|
||||
$actualPath = explode('/', $actualPath);
|
||||
if (\count($actualPath) >= 3) {
|
||||
$tmp = $actualPath[1];
|
||||
$actualPath[1] = $actualPath[2];
|
||||
$actualPath[2] = $tmp;
|
||||
$davPath = implode('/', $actualPath);
|
||||
$davPaths[$fileid] = Exif::removeExtraSlash('/'.$davPath.'/');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($day as &$row) {
|
||||
// Convert field types
|
||||
$row['fileid'] = (int) $row['fileid'];
|
||||
|
@ -282,26 +219,12 @@ trait TimelineQueryDays
|
|||
unset($row['liveid']);
|
||||
}
|
||||
|
||||
// Check if path exists and starts with basePath and remove
|
||||
if (isset($row['path']) && !empty($row['path'])) {
|
||||
$rootId = \array_key_exists('rootid', $row) ? $row['rootid'] : $defaultRootId;
|
||||
$basePath = $internalPaths[$rootId] ?? '#__#';
|
||||
$davPath = (\array_key_exists($rootId, $davPaths) ? $davPaths[$rootId] : null) ?: '';
|
||||
|
||||
if (0 === strpos($row['path'], $basePath)) {
|
||||
$rpath = substr($row['path'], \strlen($basePath));
|
||||
$row['filename'] = Exif::removeExtraSlash($davPath.$rpath);
|
||||
}
|
||||
|
||||
unset($row['path']);
|
||||
}
|
||||
|
||||
// All transform processing
|
||||
$this->processPeopleRecognizeDetection($row);
|
||||
$this->processFaceRecognitionDetection($row);
|
||||
|
||||
// We don't need these fields
|
||||
unset($row['datetaken'], $row['rootid']);
|
||||
unset($row['datetaken']);
|
||||
}
|
||||
|
||||
return $day;
|
||||
|
|
|
@ -25,8 +25,6 @@ trait TimelineQuerySingleItem
|
|||
// JOIN with mimetypes to get the mimetype
|
||||
$query->join('f', 'mimetypes', 'mimetypes', $query->expr()->eq('f.mimetype', 'mimetypes.id'));
|
||||
|
||||
unset($row['datetaken'], $row['path']);
|
||||
|
||||
return $query->executeQuery()->fetch();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -200,11 +200,6 @@ export default defineComponent({
|
|||
// Register sidebar metadata tab
|
||||
const OCA = globalThis.OCA;
|
||||
if (OCA.Files && OCA.Files.Sidebar) {
|
||||
const pf = (fileInfo) => {
|
||||
fileInfo.fileid = Number(fileInfo.id);
|
||||
return fileInfo;
|
||||
};
|
||||
|
||||
OCA.Files.Sidebar.registerTab(
|
||||
new OCA.Files.Sidebar.Tab({
|
||||
id: "memories-metadata",
|
||||
|
@ -215,10 +210,10 @@ export default defineComponent({
|
|||
this.metadataComponent?.$destroy?.();
|
||||
this.metadataComponent = new Vue(Metadata as any);
|
||||
this.metadataComponent.$mount(el);
|
||||
this.metadataComponent.update(pf(fileInfo));
|
||||
this.metadataComponent.update(Number(fileInfo.id));
|
||||
},
|
||||
update(fileInfo) {
|
||||
this.metadataComponent.update(pf(fileInfo));
|
||||
this.metadataComponent.update(Number(fileInfo.id));
|
||||
},
|
||||
destroy() {
|
||||
this.metadataComponent?.$destroy?.();
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<template>
|
||||
<div class="outer" v-if="fileInfo">
|
||||
<div class="outer" v-if="fileid">
|
||||
<div class="top-field" v-for="field of topFields" :key="field.title">
|
||||
<div class="icon">
|
||||
<component :is="field.icon" :size="24" />
|
||||
|
@ -62,8 +62,6 @@ import moment from "moment";
|
|||
|
||||
import * as utils from "../services/Utils";
|
||||
|
||||
import { IFileInfo } from "../types";
|
||||
|
||||
import EditIcon from "vue-material-design-icons/Pencil.vue";
|
||||
import CalendarIcon from "vue-material-design-icons/Calendar.vue";
|
||||
import CameraIrisIcon from "vue-material-design-icons/CameraIris.vue";
|
||||
|
@ -72,6 +70,7 @@ import InfoIcon from "vue-material-design-icons/InformationOutline.vue";
|
|||
import LocationIcon from "vue-material-design-icons/MapMarker.vue";
|
||||
import TagIcon from "vue-material-design-icons/Tag.vue";
|
||||
import { API } from "../services/API";
|
||||
import { IImageInfo } from "../types";
|
||||
|
||||
interface TopField {
|
||||
title: string;
|
||||
|
@ -91,9 +90,9 @@ export default defineComponent({
|
|||
},
|
||||
|
||||
data: () => ({
|
||||
fileInfo: null as IFileInfo,
|
||||
fileid: null as number | null,
|
||||
exif: {} as { [prop: string]: any },
|
||||
baseInfo: {} as any,
|
||||
baseInfo: {} as IImageInfo,
|
||||
state: 0,
|
||||
}),
|
||||
|
||||
|
@ -246,11 +245,7 @@ export default defineComponent({
|
|||
|
||||
/** Image info */
|
||||
imageInfo(): string | null {
|
||||
return (
|
||||
this.fileInfo?.originalBasename ||
|
||||
this.fileInfo?.basename ||
|
||||
(<any>this.fileInfo)?.name
|
||||
);
|
||||
return this.baseInfo.basename;
|
||||
},
|
||||
|
||||
imageInfoSub(): string[] | null {
|
||||
|
@ -310,24 +305,25 @@ export default defineComponent({
|
|||
},
|
||||
|
||||
methods: {
|
||||
async update(fileInfo: IFileInfo) {
|
||||
async update(fileid: number): Promise<IImageInfo> {
|
||||
this.state = Math.random();
|
||||
this.fileInfo = null;
|
||||
this.fileid = null;
|
||||
this.exif = {};
|
||||
|
||||
const state = this.state;
|
||||
const url = API.Q(API.IMAGE_INFO(fileInfo.fileid), { tags: 1 });
|
||||
const url = API.Q(API.IMAGE_INFO(fileid), { tags: 1 });
|
||||
const res = await axios.get<any>(url);
|
||||
if (state !== this.state) return;
|
||||
if (state !== this.state) return res.data;
|
||||
|
||||
this.fileInfo = fileInfo;
|
||||
this.fileid = fileid;
|
||||
this.exif = res.data.exif || {};
|
||||
this.baseInfo = res.data;
|
||||
return this.baseInfo;
|
||||
},
|
||||
|
||||
handleFileUpdated({ fileid }) {
|
||||
if (fileid && this.fileInfo?.id === fileid) {
|
||||
this.update(this.fileInfo);
|
||||
if (fileid && this.fileid === fileid) {
|
||||
this.update(this.fileid);
|
||||
}
|
||||
},
|
||||
},
|
||||
|
|
|
@ -23,7 +23,7 @@ import NcActions from "@nextcloud/vue/dist/Components/NcActions";
|
|||
import NcActionButton from "@nextcloud/vue/dist/Components/NcActionButton";
|
||||
|
||||
import Metadata from "./Metadata.vue";
|
||||
import { IFileInfo } from "../types";
|
||||
import { IImageInfo } from "../types";
|
||||
|
||||
import CloseIcon from "vue-material-design-icons/Close.vue";
|
||||
|
||||
|
@ -61,20 +61,17 @@ export default defineComponent({
|
|||
},
|
||||
|
||||
methods: {
|
||||
async open(file: IFileInfo) {
|
||||
if (
|
||||
!this.reducedOpen &&
|
||||
this.native() &&
|
||||
(!file.fileid || file.originalFilename?.startsWith("/files/"))
|
||||
) {
|
||||
async open(fileid: number, filename?: string, forceNative = false) {
|
||||
if (!this.reducedOpen && this.native() && (!fileid || forceNative)) {
|
||||
this.native()?.setFullScreenMode?.(true);
|
||||
this.native()?.open(file.filename);
|
||||
this.native()?.open(filename);
|
||||
} else {
|
||||
this.reducedOpen = true;
|
||||
await this.$nextTick();
|
||||
this.basename = file.originalBasename || file.basename;
|
||||
|
||||
(<any>this.$refs.metadata)?.update(file);
|
||||
const info: IImageInfo = await (<any>this.$refs.metadata)?.update(
|
||||
fileid
|
||||
);
|
||||
this.basename = info.basename;
|
||||
emit("memories:sidebar:opened", null);
|
||||
}
|
||||
},
|
||||
|
|
|
@ -996,9 +996,7 @@ export default defineComponent({
|
|||
head.day.detail.length === photos.length &&
|
||||
head.day.detail.every(
|
||||
(p, i) =>
|
||||
p.fileid === photos[i].fileid &&
|
||||
p.etag === photos[i].etag &&
|
||||
p.filename === photos[i].filename
|
||||
p.fileid === photos[i].fileid && p.etag === photos[i].etag
|
||||
)
|
||||
) {
|
||||
continue;
|
||||
|
|
|
@ -193,7 +193,7 @@ img {
|
|||
font-size: 1em;
|
||||
word-wrap: break-word;
|
||||
text-overflow: ellipsis;
|
||||
line-height: 1.2em;
|
||||
line-height: 1.1em;
|
||||
|
||||
> .subtitle {
|
||||
font-size: 0.7em;
|
||||
|
|
|
@ -114,10 +114,8 @@ export default defineComponent({
|
|||
photoMap.set(photo.fileid, photo);
|
||||
}
|
||||
|
||||
let data = await dav.getFiles(this.photos);
|
||||
|
||||
// Create move calls
|
||||
const calls = data.map((p) => async () => {
|
||||
const calls = this.photos.map((p) => async () => {
|
||||
try {
|
||||
await client.moveFile(
|
||||
`/recognize/${user}/faces/${name}/${p.fileid}-${p.basename}`,
|
||||
|
|
|
@ -68,7 +68,7 @@ export default defineComponent({
|
|||
|
||||
mounted() {
|
||||
if (this.sidebar) {
|
||||
globalThis.mSidebar.open({ filename: this.sidebar } as any);
|
||||
globalThis.mSidebar.open(0, this.sidebar, true);
|
||||
|
||||
// Adjust width anyway in case the sidebar is already open
|
||||
this.handleAppSidebarOpen();
|
||||
|
|
|
@ -250,7 +250,7 @@ export default defineComponent({
|
|||
refreshSidebar() {
|
||||
if (this.isMobile) return;
|
||||
globalThis.mSidebar.close();
|
||||
globalThis.mSidebar.open({ filename: this.filename } as any);
|
||||
globalThis.mSidebar.open(0, this.filename, true);
|
||||
},
|
||||
},
|
||||
});
|
||||
|
|
|
@ -83,7 +83,7 @@ import NcListItem from "@nextcloud/vue/dist/Components/NcListItem";
|
|||
import NcLoadingIcon from "@nextcloud/vue/dist/Components/NcLoadingIcon";
|
||||
import Modal from "./Modal.vue";
|
||||
|
||||
import { IFileInfo, IPhoto } from "../../types";
|
||||
import { IPhoto } from "../../types";
|
||||
import { getPreviewUrl } from "../../services/FileUtils";
|
||||
import { API } from "../../services/API";
|
||||
import * as dav from "../../services/DavRequests";
|
||||
|
@ -159,14 +159,6 @@ export default defineComponent({
|
|||
}
|
||||
},
|
||||
|
||||
async getFileInfo() {
|
||||
if (this.$route.name.endsWith("-share")) {
|
||||
return this.photo as IFileInfo;
|
||||
}
|
||||
|
||||
return (await dav.getFiles([this.photo]))[0];
|
||||
},
|
||||
|
||||
async sharePreview() {
|
||||
const src = getPreviewUrl(this.photo, false, 2048);
|
||||
this.shareWithHref(src, true);
|
||||
|
@ -185,26 +177,26 @@ export default defineComponent({
|
|||
},
|
||||
|
||||
async shareLink() {
|
||||
this.l(async () =>
|
||||
globalThis.shareNodeLink((await this.getFileInfo()).filename, true)
|
||||
);
|
||||
this.l(async () => {
|
||||
const fileInfo = (await dav.getFiles([this.photo]))[0];
|
||||
globalThis.shareNodeLink(fileInfo.filename, true);
|
||||
});
|
||||
this.close();
|
||||
},
|
||||
|
||||
async shareWithHref(href: string, replaceExt = false) {
|
||||
let fileInfo: IFileInfo, blob: Blob;
|
||||
let blob: Blob;
|
||||
await this.l(async () => {
|
||||
const res = await axios.get(href, { responseType: "blob" });
|
||||
blob = res.data;
|
||||
fileInfo = await this.getFileInfo();
|
||||
});
|
||||
|
||||
if (!blob || !fileInfo) {
|
||||
showError(this.t("memories", "Failed to download and share file"));
|
||||
if (!blob) {
|
||||
showError(this.t("memories", "Failed to download file"));
|
||||
return;
|
||||
}
|
||||
|
||||
let basename = fileInfo.originalBasename || fileInfo.basename;
|
||||
let basename = this.photo.basename;
|
||||
|
||||
if (replaceExt) {
|
||||
// Fix basename extension
|
||||
|
|
|
@ -10,7 +10,7 @@
|
|||
<ImageEditor
|
||||
v-if="editorOpen"
|
||||
:etag="currentPhoto.etag"
|
||||
:src="editorDownloadLink"
|
||||
:src="editorSrc"
|
||||
:fileid="currentPhoto.fileid"
|
||||
@close="editorOpen = false"
|
||||
/>
|
||||
|
@ -178,7 +178,7 @@
|
|||
<script lang="ts">
|
||||
import { defineComponent } from "vue";
|
||||
|
||||
import { IDay, IFileInfo, IPhoto, IRow, IRowType } from "../../types";
|
||||
import { IDay, IPhoto, IRow, IRowType } from "../../types";
|
||||
|
||||
import UserConfig from "../../mixins/UserConfig";
|
||||
import NcActions from "@nextcloud/vue/dist/Components/NcActions";
|
||||
|
@ -241,6 +241,7 @@ export default defineComponent({
|
|||
isOpen: false,
|
||||
originalTitle: null,
|
||||
editorOpen: false,
|
||||
editorSrc: "",
|
||||
|
||||
show: false,
|
||||
showControls: false,
|
||||
|
@ -351,14 +352,6 @@ export default defineComponent({
|
|||
return utils.getLongDateStr(new Date(date * 1000), false, true);
|
||||
},
|
||||
|
||||
/** Get DAV download link for current photo */
|
||||
editorDownloadLink(): string | null {
|
||||
const filename = this.currentPhoto?.filename;
|
||||
return filename
|
||||
? window.location.origin + getRootUrl() + `/remote.php/dav${filename}`
|
||||
: null;
|
||||
},
|
||||
|
||||
/** Show edit buttons */
|
||||
canEdit(): boolean {
|
||||
return this.currentPhoto?.imageInfo?.permissions?.includes("U");
|
||||
|
@ -897,7 +890,7 @@ export default defineComponent({
|
|||
}
|
||||
},
|
||||
|
||||
openEditor() {
|
||||
async openEditor() {
|
||||
// Only for JPEG for now
|
||||
if (!this.canEdit) return;
|
||||
|
||||
|
@ -909,6 +902,18 @@ export default defineComponent({
|
|||
return;
|
||||
}
|
||||
|
||||
// Get DAV path
|
||||
const fileInfo = (await dav.getFiles([this.currentPhoto]))[0];
|
||||
if (!fileInfo) {
|
||||
alert(this.t("memories", "Cannot edit this file"));
|
||||
return;
|
||||
}
|
||||
|
||||
this.editorSrc =
|
||||
window.location.origin +
|
||||
getRootUrl() +
|
||||
"/remote.php/dav" +
|
||||
fileInfo.originalFilename;
|
||||
this.editorOpen = true;
|
||||
},
|
||||
|
||||
|
@ -1019,14 +1024,15 @@ export default defineComponent({
|
|||
/** Open the sidebar */
|
||||
async openSidebar(photo?: IPhoto) {
|
||||
globalThis.mSidebar.setTab("memories-metadata");
|
||||
globalThis.mSidebar.open(await this.getFileInfo(photo));
|
||||
},
|
||||
photo ||= this.currentPhoto;
|
||||
|
||||
/** Get fileInfo for a photo */
|
||||
async getFileInfo(photo?: IPhoto): Promise<IFileInfo> {
|
||||
photo = photo || this.currentPhoto;
|
||||
if (this.routeIsPublic) return photo as IFileInfo;
|
||||
return (await dav.getFiles([photo]))[0];
|
||||
if (this.routeIsPublic) {
|
||||
globalThis.mSidebar.open(photo.fileid);
|
||||
} else {
|
||||
const fileInfo = (await dav.getFiles([photo]))[0];
|
||||
const forceNative = fileInfo?.originalFilename?.startsWith("/files/");
|
||||
globalThis.mSidebar.open(photo.fileid, fileInfo?.filename, forceNative);
|
||||
}
|
||||
},
|
||||
|
||||
async updateSizeWithoutAnim() {
|
||||
|
|
|
@ -12,7 +12,7 @@ import router from "./router";
|
|||
import { Route } from "vue-router";
|
||||
import { generateFilePath } from "@nextcloud/router";
|
||||
import { getRequestToken } from "@nextcloud/auth";
|
||||
import { IFileInfo, IPhoto } from "./types";
|
||||
import { IPhoto } from "./types";
|
||||
|
||||
import "./global.scss";
|
||||
|
||||
|
@ -27,7 +27,7 @@ declare global {
|
|||
var shareNodeLink: (path: string, immediate?: boolean) => Promise<void>;
|
||||
|
||||
var mSidebar: {
|
||||
open: (filename: IFileInfo) => void;
|
||||
open: (fileid: number, filename?: string, forceNative?: boolean) => void;
|
||||
close: () => void;
|
||||
setTab: (tab: string) => void;
|
||||
};
|
||||
|
|
|
@ -20,7 +20,7 @@
|
|||
*
|
||||
*/
|
||||
import camelcase from "camelcase";
|
||||
import { IFileInfo, IPhoto } from "../types";
|
||||
import { IPhoto } from "../types";
|
||||
import { API } from "./API";
|
||||
import { isNumber } from "./NumberUtils";
|
||||
|
||||
|
@ -136,7 +136,7 @@ const genFileInfo = function (obj) {
|
|||
|
||||
/** Get preview URL from photo object */
|
||||
const getPreviewUrl = function (
|
||||
photo: IPhoto | IFileInfo,
|
||||
photo: IPhoto,
|
||||
square: boolean,
|
||||
size: number | [number, number]
|
||||
) {
|
||||
|
|
|
@ -118,11 +118,8 @@ export async function* removeFromAlbum(
|
|||
name: string,
|
||||
photos: IPhoto[]
|
||||
) {
|
||||
// Get files data
|
||||
let fileInfos = await base.getFiles(photos);
|
||||
|
||||
// Add each file
|
||||
const calls = fileInfos.map((f) => async () => {
|
||||
const calls = photos.map((f) => async () => {
|
||||
try {
|
||||
await client.deleteFile(
|
||||
`/photos/${user}/albums/${name}/${f.fileid}-${f.basename}`
|
||||
|
@ -131,7 +128,7 @@ export async function* removeFromAlbum(
|
|||
} catch (e) {
|
||||
showError(
|
||||
t("memories", "Failed to remove {filename}.", {
|
||||
filename: f.filename,
|
||||
filename: f.basename,
|
||||
})
|
||||
);
|
||||
return 0;
|
||||
|
@ -285,10 +282,6 @@ export function getAlbumFileInfos(
|
|||
filename: `${collection}/${basename}`,
|
||||
originalFilename: `${collection}/${basename}`,
|
||||
basename: basename,
|
||||
originalBasename: photo.basename,
|
||||
mime: photo.mimetype,
|
||||
hasPreview: true,
|
||||
etag: photo.etag,
|
||||
} as IFileInfo;
|
||||
});
|
||||
}
|
||||
|
|
|
@ -56,33 +56,8 @@ export async function getFiles(photos: IPhoto[]): Promise<IFileInfo[]> {
|
|||
// Get file infos
|
||||
let fileInfos: IFileInfo[] = [];
|
||||
|
||||
// Get all photos that already have and don't have a filename
|
||||
const photosWithFilename = photos.filter((photo) => photo.filename);
|
||||
fileInfos = fileInfos.concat(
|
||||
photosWithFilename.map((photo) => {
|
||||
const prefixPath = `/files/${getCurrentUser()?.uid}`;
|
||||
return {
|
||||
id: photo.fileid,
|
||||
fileid: photo.fileid,
|
||||
filename: photo.filename.replace(prefixPath, ""),
|
||||
originalFilename: photo.filename,
|
||||
basename: photo.basename,
|
||||
mime: photo.mimetype,
|
||||
hasPreview: true,
|
||||
etag: photo.etag,
|
||||
permissions: "RWD",
|
||||
} as IFileInfo;
|
||||
})
|
||||
);
|
||||
|
||||
// Next: get all photos that have no filename using ID
|
||||
if (photosWithFilename.length === photos.length) {
|
||||
return fileInfos;
|
||||
}
|
||||
const photosWithoutFilename = photos.filter((photo) => !photo.filename);
|
||||
|
||||
// Get file IDs array
|
||||
const fileIds = photosWithoutFilename.map((photo) => photo.fileid);
|
||||
const fileIds = photos.map((photo) => photo.fileid);
|
||||
|
||||
// Divide fileIds into chunks of GET_FILE_CHUNK_SIZE
|
||||
const chunks = [];
|
||||
|
|
|
@ -93,11 +93,8 @@ export async function* removeFaceImages(
|
|||
name: string,
|
||||
photos: IPhoto[]
|
||||
) {
|
||||
// Get files data
|
||||
let fileInfos = await base.getFiles(photos);
|
||||
|
||||
// Remove each file
|
||||
const calls = fileInfos.map((f) => async () => {
|
||||
const calls = photos.map((f) => async () => {
|
||||
try {
|
||||
await client.deleteFile(
|
||||
`/recognize/${user}/faces/${name}/${f.fileid}-${f.basename}`
|
||||
|
@ -107,7 +104,7 @@ export async function* removeFaceImages(
|
|||
console.error(e);
|
||||
showError(
|
||||
t("memories", "Failed to remove {filename} from face.", {
|
||||
filename: f.filename,
|
||||
filename: f.basename,
|
||||
})
|
||||
);
|
||||
return 0;
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { showError } from "@nextcloud/dialogs";
|
||||
import { translate as t } from "@nextcloud/l10n";
|
||||
import { IPhoto } from "../../types";
|
||||
import { IFileInfo, IPhoto } from "../../types";
|
||||
import client from "../DavClient";
|
||||
import * as base from "./base";
|
||||
import * as utils from "../Utils";
|
||||
|
@ -45,7 +45,7 @@ export async function* favoritePhotos(
|
|||
}
|
||||
|
||||
// Get files data
|
||||
let fileInfos: any[] = [];
|
||||
let fileInfos: IFileInfo[] = [];
|
||||
try {
|
||||
fileInfos = await base.getFiles(photos);
|
||||
} catch (e) {
|
||||
|
|
57
src/types.ts
57
src/types.ts
|
@ -9,20 +9,6 @@ export type IFileInfo = {
|
|||
originalFilename?: string;
|
||||
/** Base name of file e.g. Qx0dq7dvEXA.jpg */
|
||||
basename: string;
|
||||
/** Original base name, e.g. in albums without the file id */
|
||||
originalBasename?: string;
|
||||
/** Etag identifier */
|
||||
etag: string;
|
||||
/** File has preview available */
|
||||
hasPreview: boolean;
|
||||
/** File is marked favorite */
|
||||
favorite?: boolean;
|
||||
/** Vue flags */
|
||||
flag?: number;
|
||||
/** MIME type of file */
|
||||
mime?: string;
|
||||
/** WebDAV permissions string */
|
||||
permissions?: string;
|
||||
};
|
||||
|
||||
export type IDay = {
|
||||
|
@ -43,8 +29,6 @@ export type IPhoto = {
|
|||
key?: string;
|
||||
/** Etag from server */
|
||||
etag?: string;
|
||||
/** Path to file */
|
||||
filename?: string;
|
||||
/** Base name of file */
|
||||
basename?: string;
|
||||
/** Mime type of file */
|
||||
|
@ -74,23 +58,7 @@ export type IPhoto = {
|
|||
/** Reference to day object */
|
||||
d?: IDay;
|
||||
/** Reference to exif object */
|
||||
imageInfo?: {
|
||||
h: number;
|
||||
w: number;
|
||||
datetaken: number;
|
||||
address?: string;
|
||||
tags: { [id: string]: string };
|
||||
permissions: string;
|
||||
exif?: {
|
||||
Rotation?: number;
|
||||
Orientation?: number;
|
||||
ImageWidth?: number;
|
||||
ImageHeight?: number;
|
||||
Title?: string;
|
||||
Description?: string;
|
||||
[other: string]: unknown;
|
||||
};
|
||||
};
|
||||
imageInfo?: IImageInfo;
|
||||
|
||||
/** Face detection ID */
|
||||
faceid?: number;
|
||||
|
@ -117,6 +85,29 @@ export type IPhoto = {
|
|||
datetaken?: number;
|
||||
};
|
||||
|
||||
export interface IImageInfo {
|
||||
h: number;
|
||||
w: number;
|
||||
datetaken: number;
|
||||
address?: string;
|
||||
tags: { [id: string]: string };
|
||||
|
||||
permissions: string;
|
||||
basename: string;
|
||||
mimetype: string;
|
||||
size: number;
|
||||
|
||||
exif?: {
|
||||
Rotation?: number;
|
||||
Orientation?: number;
|
||||
ImageWidth?: number;
|
||||
ImageHeight?: number;
|
||||
Title?: string;
|
||||
Description?: string;
|
||||
[other: string]: unknown;
|
||||
};
|
||||
}
|
||||
|
||||
export interface IFolder extends IPhoto {
|
||||
/** Path to folder */
|
||||
path: string;
|
||||
|
|
Loading…
Reference in New Issue