big: remove filename from IPhoto

Signed-off-by: Varun Patil <varunpatil@ucla.edu>
pull/504/head
Varun Patil 2023-03-16 09:58:43 -07:00
parent 1851c463c5
commit b1df9215f9
21 changed files with 113 additions and 249 deletions

View File

@ -209,6 +209,11 @@ class ImageController extends ApiBase
// Inject permissions and convert to string // Inject permissions and convert to string
$info['permissions'] = \OCA\Memories\Util::permissionsToStr($file->getPermissions()); $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); return new JSONResponse($info, Http::STATUS_OK);
} }

View File

@ -23,7 +23,7 @@ class TimelineQuery
public const TIMELINE_SELECT = [ public const TIMELINE_SELECT = [
'm.isvideo', 'm.video_duration', 'm.datetaken', 'm.dayid', 'm.w', 'm.h', 'm.liveid', '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; protected IDBConnection $connection;

View File

@ -4,23 +4,20 @@ declare(strict_types=1);
namespace OCA\Memories\Db; namespace OCA\Memories\Db;
use OCA\Memories\Exif;
use OCP\DB\QueryBuilder\IQueryBuilder; use OCP\DB\QueryBuilder\IQueryBuilder;
use OCP\IDBConnection; use OCP\IDBConnection;
const CTE_FOLDERS = // CTE to get all folders recursively in the given top folders excluding archive 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 SELECT
f.fileid, f.fileid
f.fileid AS rootid
FROM FROM
*PREFIX*filecache f *PREFIX*filecache f
WHERE WHERE
f.fileid IN (:topFolderIds) f.fileid IN (:topFolderIds)
UNION ALL UNION ALL
SELECT SELECT
f.fileid, f.fileid
c.rootid
FROM FROM
*PREFIX*filecache f *PREFIX*filecache f
INNER JOIN *PREFIX*cte_folders_all c 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 ( ), *PREFIX*cte_folders AS (
SELECT SELECT
fileid, fileid
MIN(rootid) AS rootid
FROM FROM
*PREFIX*cte_folders_all *PREFIX*cte_folders_all
GROUP BY 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 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 SELECT
f.fileid, f.fileid,
f.name, f.name
f.fileid AS rootid
FROM FROM
*PREFIX*filecache f *PREFIX*filecache f
WHERE WHERE
@ -51,18 +46,16 @@ const CTE_FOLDERS_ARCHIVE = // CTE to get all archive folders recursively in the
UNION ALL UNION ALL
SELECT SELECT
f.fileid, f.fileid,
f.name, f.name
c.rootid
FROM FROM
*PREFIX*filecache f *PREFIX*filecache f
INNER JOIN *PREFIX*cte_folders_all c INNER JOIN *PREFIX*cte_folders_all c
ON (f.parent = c.fileid ON (f.parent = c.fileid
AND f.mimetype = (SELECT `id` FROM `*PREFIX*mimetypes` WHERE `mimetype` = \'httpd/unix-directory\') 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 SELECT
cfa.fileid, cfa.fileid
MIN(cfa.rootid) AS rootid
FROM FROM
*PREFIX*cte_folders_all cfa *PREFIX*cte_folders_all cfa
WHERE WHERE
@ -71,8 +64,7 @@ const CTE_FOLDERS_ARCHIVE = // CTE to get all archive folders recursively in the
cfa.fileid cfa.fileid
UNION ALL UNION ALL
SELECT SELECT
f.fileid, f.fileid
c.rootid
FROM FROM
*PREFIX*filecache f *PREFIX*filecache f
INNER JOIN *PREFIX*cte_folders c INNER JOIN *PREFIX*cte_folders c
@ -160,11 +152,6 @@ trait TimelineQueryDays
// JOIN with filecache for existing files // JOIN with filecache for existing files
$query = $this->joinFilecache($query, $root, $recursive, $archive); $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 // JOIN with mimetypes to get the mimetype
$query->join('f', 'mimetypes', 'mimetypes', $query->expr()->eq('f.mimetype', 'mimetypes.id')); $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) 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) { foreach ($day as &$row) {
// Convert field types // Convert field types
$row['fileid'] = (int) $row['fileid']; $row['fileid'] = (int) $row['fileid'];
@ -282,26 +219,12 @@ trait TimelineQueryDays
unset($row['liveid']); 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 // All transform processing
$this->processPeopleRecognizeDetection($row); $this->processPeopleRecognizeDetection($row);
$this->processFaceRecognitionDetection($row); $this->processFaceRecognitionDetection($row);
// We don't need these fields // We don't need these fields
unset($row['datetaken'], $row['rootid']); unset($row['datetaken']);
} }
return $day; return $day;

View File

@ -25,8 +25,6 @@ trait TimelineQuerySingleItem
// JOIN with mimetypes to get the mimetype // JOIN with mimetypes to get the mimetype
$query->join('f', 'mimetypes', 'mimetypes', $query->expr()->eq('f.mimetype', 'mimetypes.id')); $query->join('f', 'mimetypes', 'mimetypes', $query->expr()->eq('f.mimetype', 'mimetypes.id'));
unset($row['datetaken'], $row['path']);
return $query->executeQuery()->fetch(); return $query->executeQuery()->fetch();
} }
} }

View File

@ -200,11 +200,6 @@ export default defineComponent({
// Register sidebar metadata tab // Register sidebar metadata tab
const OCA = globalThis.OCA; const OCA = globalThis.OCA;
if (OCA.Files && OCA.Files.Sidebar) { if (OCA.Files && OCA.Files.Sidebar) {
const pf = (fileInfo) => {
fileInfo.fileid = Number(fileInfo.id);
return fileInfo;
};
OCA.Files.Sidebar.registerTab( OCA.Files.Sidebar.registerTab(
new OCA.Files.Sidebar.Tab({ new OCA.Files.Sidebar.Tab({
id: "memories-metadata", id: "memories-metadata",
@ -215,10 +210,10 @@ export default defineComponent({
this.metadataComponent?.$destroy?.(); this.metadataComponent?.$destroy?.();
this.metadataComponent = new Vue(Metadata as any); this.metadataComponent = new Vue(Metadata as any);
this.metadataComponent.$mount(el); this.metadataComponent.$mount(el);
this.metadataComponent.update(pf(fileInfo)); this.metadataComponent.update(Number(fileInfo.id));
}, },
update(fileInfo) { update(fileInfo) {
this.metadataComponent.update(pf(fileInfo)); this.metadataComponent.update(Number(fileInfo.id));
}, },
destroy() { destroy() {
this.metadataComponent?.$destroy?.(); this.metadataComponent?.$destroy?.();

View File

@ -1,5 +1,5 @@
<template> <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="top-field" v-for="field of topFields" :key="field.title">
<div class="icon"> <div class="icon">
<component :is="field.icon" :size="24" /> <component :is="field.icon" :size="24" />
@ -62,8 +62,6 @@ import moment from "moment";
import * as utils from "../services/Utils"; import * as utils from "../services/Utils";
import { IFileInfo } from "../types";
import EditIcon from "vue-material-design-icons/Pencil.vue"; import EditIcon from "vue-material-design-icons/Pencil.vue";
import CalendarIcon from "vue-material-design-icons/Calendar.vue"; import CalendarIcon from "vue-material-design-icons/Calendar.vue";
import CameraIrisIcon from "vue-material-design-icons/CameraIris.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 LocationIcon from "vue-material-design-icons/MapMarker.vue";
import TagIcon from "vue-material-design-icons/Tag.vue"; import TagIcon from "vue-material-design-icons/Tag.vue";
import { API } from "../services/API"; import { API } from "../services/API";
import { IImageInfo } from "../types";
interface TopField { interface TopField {
title: string; title: string;
@ -91,9 +90,9 @@ export default defineComponent({
}, },
data: () => ({ data: () => ({
fileInfo: null as IFileInfo, fileid: null as number | null,
exif: {} as { [prop: string]: any }, exif: {} as { [prop: string]: any },
baseInfo: {} as any, baseInfo: {} as IImageInfo,
state: 0, state: 0,
}), }),
@ -246,11 +245,7 @@ export default defineComponent({
/** Image info */ /** Image info */
imageInfo(): string | null { imageInfo(): string | null {
return ( return this.baseInfo.basename;
this.fileInfo?.originalBasename ||
this.fileInfo?.basename ||
(<any>this.fileInfo)?.name
);
}, },
imageInfoSub(): string[] | null { imageInfoSub(): string[] | null {
@ -310,24 +305,25 @@ export default defineComponent({
}, },
methods: { methods: {
async update(fileInfo: IFileInfo) { async update(fileid: number): Promise<IImageInfo> {
this.state = Math.random(); this.state = Math.random();
this.fileInfo = null; this.fileid = null;
this.exif = {}; this.exif = {};
const state = this.state; 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); 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.exif = res.data.exif || {};
this.baseInfo = res.data; this.baseInfo = res.data;
return this.baseInfo;
}, },
handleFileUpdated({ fileid }) { handleFileUpdated({ fileid }) {
if (fileid && this.fileInfo?.id === fileid) { if (fileid && this.fileid === fileid) {
this.update(this.fileInfo); this.update(this.fileid);
} }
}, },
}, },

View File

@ -23,7 +23,7 @@ import NcActions from "@nextcloud/vue/dist/Components/NcActions";
import NcActionButton from "@nextcloud/vue/dist/Components/NcActionButton"; import NcActionButton from "@nextcloud/vue/dist/Components/NcActionButton";
import Metadata from "./Metadata.vue"; import Metadata from "./Metadata.vue";
import { IFileInfo } from "../types"; import { IImageInfo } from "../types";
import CloseIcon from "vue-material-design-icons/Close.vue"; import CloseIcon from "vue-material-design-icons/Close.vue";
@ -61,20 +61,17 @@ export default defineComponent({
}, },
methods: { methods: {
async open(file: IFileInfo) { async open(fileid: number, filename?: string, forceNative = false) {
if ( if (!this.reducedOpen && this.native() && (!fileid || forceNative)) {
!this.reducedOpen &&
this.native() &&
(!file.fileid || file.originalFilename?.startsWith("/files/"))
) {
this.native()?.setFullScreenMode?.(true); this.native()?.setFullScreenMode?.(true);
this.native()?.open(file.filename); this.native()?.open(filename);
} else { } else {
this.reducedOpen = true; this.reducedOpen = true;
await this.$nextTick(); await this.$nextTick();
this.basename = file.originalBasename || file.basename; const info: IImageInfo = await (<any>this.$refs.metadata)?.update(
fileid
(<any>this.$refs.metadata)?.update(file); );
this.basename = info.basename;
emit("memories:sidebar:opened", null); emit("memories:sidebar:opened", null);
} }
}, },

View File

@ -996,9 +996,7 @@ export default defineComponent({
head.day.detail.length === photos.length && head.day.detail.length === photos.length &&
head.day.detail.every( head.day.detail.every(
(p, i) => (p, i) =>
p.fileid === photos[i].fileid && p.fileid === photos[i].fileid && p.etag === photos[i].etag
p.etag === photos[i].etag &&
p.filename === photos[i].filename
) )
) { ) {
continue; continue;

View File

@ -193,7 +193,7 @@ img {
font-size: 1em; font-size: 1em;
word-wrap: break-word; word-wrap: break-word;
text-overflow: ellipsis; text-overflow: ellipsis;
line-height: 1.2em; line-height: 1.1em;
> .subtitle { > .subtitle {
font-size: 0.7em; font-size: 0.7em;

View File

@ -114,10 +114,8 @@ export default defineComponent({
photoMap.set(photo.fileid, photo); photoMap.set(photo.fileid, photo);
} }
let data = await dav.getFiles(this.photos);
// Create move calls // Create move calls
const calls = data.map((p) => async () => { const calls = this.photos.map((p) => async () => {
try { try {
await client.moveFile( await client.moveFile(
`/recognize/${user}/faces/${name}/${p.fileid}-${p.basename}`, `/recognize/${user}/faces/${name}/${p.fileid}-${p.basename}`,

View File

@ -68,7 +68,7 @@ export default defineComponent({
mounted() { mounted() {
if (this.sidebar) { 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 // Adjust width anyway in case the sidebar is already open
this.handleAppSidebarOpen(); this.handleAppSidebarOpen();

View File

@ -250,7 +250,7 @@ export default defineComponent({
refreshSidebar() { refreshSidebar() {
if (this.isMobile) return; if (this.isMobile) return;
globalThis.mSidebar.close(); globalThis.mSidebar.close();
globalThis.mSidebar.open({ filename: this.filename } as any); globalThis.mSidebar.open(0, this.filename, true);
}, },
}, },
}); });

View File

@ -83,7 +83,7 @@ import NcListItem from "@nextcloud/vue/dist/Components/NcListItem";
import NcLoadingIcon from "@nextcloud/vue/dist/Components/NcLoadingIcon"; import NcLoadingIcon from "@nextcloud/vue/dist/Components/NcLoadingIcon";
import Modal from "./Modal.vue"; import Modal from "./Modal.vue";
import { IFileInfo, IPhoto } from "../../types"; import { IPhoto } from "../../types";
import { getPreviewUrl } from "../../services/FileUtils"; import { getPreviewUrl } from "../../services/FileUtils";
import { API } from "../../services/API"; import { API } from "../../services/API";
import * as dav from "../../services/DavRequests"; 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() { async sharePreview() {
const src = getPreviewUrl(this.photo, false, 2048); const src = getPreviewUrl(this.photo, false, 2048);
this.shareWithHref(src, true); this.shareWithHref(src, true);
@ -185,26 +177,26 @@ export default defineComponent({
}, },
async shareLink() { async shareLink() {
this.l(async () => this.l(async () => {
globalThis.shareNodeLink((await this.getFileInfo()).filename, true) const fileInfo = (await dav.getFiles([this.photo]))[0];
); globalThis.shareNodeLink(fileInfo.filename, true);
});
this.close(); this.close();
}, },
async shareWithHref(href: string, replaceExt = false) { async shareWithHref(href: string, replaceExt = false) {
let fileInfo: IFileInfo, blob: Blob; let blob: Blob;
await this.l(async () => { await this.l(async () => {
const res = await axios.get(href, { responseType: "blob" }); const res = await axios.get(href, { responseType: "blob" });
blob = res.data; blob = res.data;
fileInfo = await this.getFileInfo();
}); });
if (!blob || !fileInfo) { if (!blob) {
showError(this.t("memories", "Failed to download and share file")); showError(this.t("memories", "Failed to download file"));
return; return;
} }
let basename = fileInfo.originalBasename || fileInfo.basename; let basename = this.photo.basename;
if (replaceExt) { if (replaceExt) {
// Fix basename extension // Fix basename extension

View File

@ -10,7 +10,7 @@
<ImageEditor <ImageEditor
v-if="editorOpen" v-if="editorOpen"
:etag="currentPhoto.etag" :etag="currentPhoto.etag"
:src="editorDownloadLink" :src="editorSrc"
:fileid="currentPhoto.fileid" :fileid="currentPhoto.fileid"
@close="editorOpen = false" @close="editorOpen = false"
/> />
@ -178,7 +178,7 @@
<script lang="ts"> <script lang="ts">
import { defineComponent } from "vue"; 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 UserConfig from "../../mixins/UserConfig";
import NcActions from "@nextcloud/vue/dist/Components/NcActions"; import NcActions from "@nextcloud/vue/dist/Components/NcActions";
@ -241,6 +241,7 @@ export default defineComponent({
isOpen: false, isOpen: false,
originalTitle: null, originalTitle: null,
editorOpen: false, editorOpen: false,
editorSrc: "",
show: false, show: false,
showControls: false, showControls: false,
@ -351,14 +352,6 @@ export default defineComponent({
return utils.getLongDateStr(new Date(date * 1000), false, true); 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 */ /** Show edit buttons */
canEdit(): boolean { canEdit(): boolean {
return this.currentPhoto?.imageInfo?.permissions?.includes("U"); return this.currentPhoto?.imageInfo?.permissions?.includes("U");
@ -897,7 +890,7 @@ export default defineComponent({
} }
}, },
openEditor() { async openEditor() {
// Only for JPEG for now // Only for JPEG for now
if (!this.canEdit) return; if (!this.canEdit) return;
@ -909,6 +902,18 @@ export default defineComponent({
return; 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; this.editorOpen = true;
}, },
@ -1019,14 +1024,15 @@ export default defineComponent({
/** Open the sidebar */ /** Open the sidebar */
async openSidebar(photo?: IPhoto) { async openSidebar(photo?: IPhoto) {
globalThis.mSidebar.setTab("memories-metadata"); globalThis.mSidebar.setTab("memories-metadata");
globalThis.mSidebar.open(await this.getFileInfo(photo)); photo ||= this.currentPhoto;
},
/** Get fileInfo for a photo */ if (this.routeIsPublic) {
async getFileInfo(photo?: IPhoto): Promise<IFileInfo> { globalThis.mSidebar.open(photo.fileid);
photo = photo || this.currentPhoto; } else {
if (this.routeIsPublic) return photo as IFileInfo; const fileInfo = (await dav.getFiles([photo]))[0];
return (await dav.getFiles([photo]))[0]; const forceNative = fileInfo?.originalFilename?.startsWith("/files/");
globalThis.mSidebar.open(photo.fileid, fileInfo?.filename, forceNative);
}
}, },
async updateSizeWithoutAnim() { async updateSizeWithoutAnim() {

View File

@ -12,7 +12,7 @@ import router from "./router";
import { Route } from "vue-router"; import { Route } from "vue-router";
import { generateFilePath } from "@nextcloud/router"; import { generateFilePath } from "@nextcloud/router";
import { getRequestToken } from "@nextcloud/auth"; import { getRequestToken } from "@nextcloud/auth";
import { IFileInfo, IPhoto } from "./types"; import { IPhoto } from "./types";
import "./global.scss"; import "./global.scss";
@ -27,7 +27,7 @@ declare global {
var shareNodeLink: (path: string, immediate?: boolean) => Promise<void>; var shareNodeLink: (path: string, immediate?: boolean) => Promise<void>;
var mSidebar: { var mSidebar: {
open: (filename: IFileInfo) => void; open: (fileid: number, filename?: string, forceNative?: boolean) => void;
close: () => void; close: () => void;
setTab: (tab: string) => void; setTab: (tab: string) => void;
}; };

View File

@ -20,7 +20,7 @@
* *
*/ */
import camelcase from "camelcase"; import camelcase from "camelcase";
import { IFileInfo, IPhoto } from "../types"; import { IPhoto } from "../types";
import { API } from "./API"; import { API } from "./API";
import { isNumber } from "./NumberUtils"; import { isNumber } from "./NumberUtils";
@ -136,7 +136,7 @@ const genFileInfo = function (obj) {
/** Get preview URL from photo object */ /** Get preview URL from photo object */
const getPreviewUrl = function ( const getPreviewUrl = function (
photo: IPhoto | IFileInfo, photo: IPhoto,
square: boolean, square: boolean,
size: number | [number, number] size: number | [number, number]
) { ) {

View File

@ -118,11 +118,8 @@ export async function* removeFromAlbum(
name: string, name: string,
photos: IPhoto[] photos: IPhoto[]
) { ) {
// Get files data
let fileInfos = await base.getFiles(photos);
// Add each file // Add each file
const calls = fileInfos.map((f) => async () => { const calls = photos.map((f) => async () => {
try { try {
await client.deleteFile( await client.deleteFile(
`/photos/${user}/albums/${name}/${f.fileid}-${f.basename}` `/photos/${user}/albums/${name}/${f.fileid}-${f.basename}`
@ -131,7 +128,7 @@ export async function* removeFromAlbum(
} catch (e) { } catch (e) {
showError( showError(
t("memories", "Failed to remove {filename}.", { t("memories", "Failed to remove {filename}.", {
filename: f.filename, filename: f.basename,
}) })
); );
return 0; return 0;
@ -285,10 +282,6 @@ export function getAlbumFileInfos(
filename: `${collection}/${basename}`, filename: `${collection}/${basename}`,
originalFilename: `${collection}/${basename}`, originalFilename: `${collection}/${basename}`,
basename: basename, basename: basename,
originalBasename: photo.basename,
mime: photo.mimetype,
hasPreview: true,
etag: photo.etag,
} as IFileInfo; } as IFileInfo;
}); });
} }

View File

@ -56,33 +56,8 @@ export async function getFiles(photos: IPhoto[]): Promise<IFileInfo[]> {
// Get file infos // Get file infos
let fileInfos: IFileInfo[] = []; 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 // 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 // Divide fileIds into chunks of GET_FILE_CHUNK_SIZE
const chunks = []; const chunks = [];

View File

@ -93,11 +93,8 @@ export async function* removeFaceImages(
name: string, name: string,
photos: IPhoto[] photos: IPhoto[]
) { ) {
// Get files data
let fileInfos = await base.getFiles(photos);
// Remove each file // Remove each file
const calls = fileInfos.map((f) => async () => { const calls = photos.map((f) => async () => {
try { try {
await client.deleteFile( await client.deleteFile(
`/recognize/${user}/faces/${name}/${f.fileid}-${f.basename}` `/recognize/${user}/faces/${name}/${f.fileid}-${f.basename}`
@ -107,7 +104,7 @@ export async function* removeFaceImages(
console.error(e); console.error(e);
showError( showError(
t("memories", "Failed to remove {filename} from face.", { t("memories", "Failed to remove {filename} from face.", {
filename: f.filename, filename: f.basename,
}) })
); );
return 0; return 0;

View File

@ -1,6 +1,6 @@
import { showError } from "@nextcloud/dialogs"; import { showError } from "@nextcloud/dialogs";
import { translate as t } from "@nextcloud/l10n"; import { translate as t } from "@nextcloud/l10n";
import { IPhoto } from "../../types"; import { IFileInfo, IPhoto } from "../../types";
import client from "../DavClient"; import client from "../DavClient";
import * as base from "./base"; import * as base from "./base";
import * as utils from "../Utils"; import * as utils from "../Utils";
@ -45,7 +45,7 @@ export async function* favoritePhotos(
} }
// Get files data // Get files data
let fileInfos: any[] = []; let fileInfos: IFileInfo[] = [];
try { try {
fileInfos = await base.getFiles(photos); fileInfos = await base.getFiles(photos);
} catch (e) { } catch (e) {

View File

@ -9,20 +9,6 @@ export type IFileInfo = {
originalFilename?: string; originalFilename?: string;
/** Base name of file e.g. Qx0dq7dvEXA.jpg */ /** Base name of file e.g. Qx0dq7dvEXA.jpg */
basename: string; 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 = { export type IDay = {
@ -43,8 +29,6 @@ export type IPhoto = {
key?: string; key?: string;
/** Etag from server */ /** Etag from server */
etag?: string; etag?: string;
/** Path to file */
filename?: string;
/** Base name of file */ /** Base name of file */
basename?: string; basename?: string;
/** Mime type of file */ /** Mime type of file */
@ -74,23 +58,7 @@ export type IPhoto = {
/** Reference to day object */ /** Reference to day object */
d?: IDay; d?: IDay;
/** Reference to exif object */ /** Reference to exif object */
imageInfo?: { imageInfo?: IImageInfo;
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;
};
};
/** Face detection ID */ /** Face detection ID */
faceid?: number; faceid?: number;
@ -117,6 +85,29 @@ export type IPhoto = {
datetaken?: number; 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 { export interface IFolder extends IPhoto {
/** Path to folder */ /** Path to folder */
path: string; path: string;