Include filename in days
parent
87e96141c4
commit
38b37ad32f
|
@ -45,14 +45,6 @@ class TimelineQuery
|
||||||
|
|
||||||
public function transformExtraFields(IQueryBuilder &$query, string $uid, array &$fields)
|
public function transformExtraFields(IQueryBuilder &$query, string $uid, array &$fields)
|
||||||
{
|
{
|
||||||
if (\in_array('basename', $fields, true)) {
|
|
||||||
$query->addSelect('f.name AS basename');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (\in_array('mimetype', $fields, true)) {
|
|
||||||
$query->join('f', 'mimetypes', 'mimetypes', $query->expr()->eq('f.mimetype', 'mimetypes.id'));
|
|
||||||
$query->addSelect('mimetypes.mimetype');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getInfoById(int $id): array
|
public function getInfoById(int $id): array
|
||||||
|
|
|
@ -102,10 +102,17 @@ trait TimelineQueryDays
|
||||||
// We don't actually use m.datetaken here, but postgres
|
// We don't actually use m.datetaken here, but postgres
|
||||||
// needs that all fields in ORDER BY are also in SELECT
|
// needs that all fields in ORDER BY are also in SELECT
|
||||||
// when using DISTINCT on selected fields
|
// when using DISTINCT on selected fields
|
||||||
$query->select($fileid, 'f.etag', 'f.path', 'm.isvideo', 'vco.categoryid', 'm.datetaken', 'm.dayid', 'm.w', 'm.h')
|
$query->select($fileid, 'm.isvideo', 'm.datetaken', 'm.dayid', 'm.w', 'm.h')
|
||||||
->from('memories', 'm')
|
->from('memories', 'm')
|
||||||
;
|
;
|
||||||
|
|
||||||
|
// JOIN with filecache for existing files
|
||||||
$query = $this->joinFilecache($query, $folder, $recursive, $archive);
|
$query = $this->joinFilecache($query, $folder, $recursive, $archive);
|
||||||
|
$query->addSelect('f.etag', 'f.path', 'f.name AS basename');
|
||||||
|
|
||||||
|
// JOIN with mimetypes to get the mimetype
|
||||||
|
$query->join('f', 'mimetypes', 'mimetypes', $query->expr()->eq('f.mimetype', 'mimetypes.id'));
|
||||||
|
$query->addSelect('mimetypes.mimetype');
|
||||||
|
|
||||||
// Filter by dayid unless wildcard
|
// Filter by dayid unless wildcard
|
||||||
if (null !== $day_ids) {
|
if (null !== $day_ids) {
|
||||||
|
@ -155,7 +162,30 @@ trait TimelineQueryDays
|
||||||
*/
|
*/
|
||||||
private function processDay(&$day, $folder)
|
private function processDay(&$day, $folder)
|
||||||
{
|
{
|
||||||
$basePath = null !== $folder ? $folder->getInternalPath() : '#__#__#';
|
$basePath = '#__#__#';
|
||||||
|
$davPath = '/';
|
||||||
|
if (null !== $folder) {
|
||||||
|
// No way to get the internal path from the folder
|
||||||
|
$query = $this->connection->getQueryBuilder();
|
||||||
|
$query->select('path')
|
||||||
|
->from('filecache')
|
||||||
|
->where($query->expr()->eq('fileid', $query->createNamedParameter($folder->getId(), IQueryBuilder::PARAM_INT)))
|
||||||
|
;
|
||||||
|
$path = $query->executeQuery()->fetchOne();
|
||||||
|
$basePath = $path ?: $basePath;
|
||||||
|
|
||||||
|
// Get user facing path
|
||||||
|
// getPath looks like /user/files/... but we want /files/user/...
|
||||||
|
// Split at / and swap these
|
||||||
|
$actualPath = $folder->getPath();
|
||||||
|
$actualPath = explode('/', $actualPath);
|
||||||
|
if (\count($actualPath) >= 3) {
|
||||||
|
$tmp = $actualPath[1];
|
||||||
|
$actualPath[1] = $actualPath[2];
|
||||||
|
$actualPath[2] = $tmp;
|
||||||
|
$davPath = implode('/', $actualPath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
foreach ($day as &$row) {
|
foreach ($day as &$row) {
|
||||||
// We don't need date taken (see query builder)
|
// We don't need date taken (see query builder)
|
||||||
|
@ -178,7 +208,7 @@ trait TimelineQueryDays
|
||||||
// Check if path exists and starts with basePath and remove
|
// Check if path exists and starts with basePath and remove
|
||||||
if (isset($row['path']) && !empty($row['path'])) {
|
if (isset($row['path']) && !empty($row['path'])) {
|
||||||
if (0 === strpos($row['path'], $basePath)) {
|
if (0 === strpos($row['path'], $basePath)) {
|
||||||
$row['filename'] = substr($row['path'], \strlen($basePath));
|
$row['filename'] = $davPath.substr($row['path'], \strlen($basePath));
|
||||||
}
|
}
|
||||||
unset($row['path']);
|
unset($row['path']);
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,6 +23,7 @@ trait TimelineQueryFilters
|
||||||
$query->expr()->eq('vco.objid', 'm.fileid'),
|
$query->expr()->eq('vco.objid', 'm.fileid'),
|
||||||
$query->expr()->in('vco.categoryid', $this->getFavoriteVCategoryFun($query, $userId)),
|
$query->expr()->in('vco.categoryid', $this->getFavoriteVCategoryFun($query, $userId)),
|
||||||
));
|
));
|
||||||
|
$query->addSelect('vco.categoryid');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function transformVideoFilter(IQueryBuilder &$query, string $userId)
|
public function transformVideoFilter(IQueryBuilder &$query, string $userId)
|
||||||
|
|
|
@ -21,7 +21,7 @@
|
||||||
*/
|
*/
|
||||||
import { generateUrl } from "@nextcloud/router";
|
import { generateUrl } from "@nextcloud/router";
|
||||||
import camelcase from "camelcase";
|
import camelcase from "camelcase";
|
||||||
import { IExtendedPhoto, IFileInfo, IPhoto } from "../types";
|
import { IFileInfo, IPhoto } from "../types";
|
||||||
import { isNumber } from "./NumberUtils";
|
import { isNumber } from "./NumberUtils";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -3,14 +3,7 @@ import { getCurrentUser } from "@nextcloud/auth";
|
||||||
import { generateUrl } from "@nextcloud/router";
|
import { generateUrl } from "@nextcloud/router";
|
||||||
import { showError } from "@nextcloud/dialogs";
|
import { showError } from "@nextcloud/dialogs";
|
||||||
import { translate as t, translatePlural as n } from "@nextcloud/l10n";
|
import { translate as t, translatePlural as n } from "@nextcloud/l10n";
|
||||||
import {
|
import { IAlbum, IDay, IFileInfo, IPhoto, ITag } from "../../types";
|
||||||
IAlbum,
|
|
||||||
IDay,
|
|
||||||
IExtendedPhoto,
|
|
||||||
IFileInfo,
|
|
||||||
IPhoto,
|
|
||||||
ITag,
|
|
||||||
} from "../../types";
|
|
||||||
import { constants } from "../Utils";
|
import { constants } from "../Utils";
|
||||||
import axios from "@nextcloud/axios";
|
import axios from "@nextcloud/axios";
|
||||||
import client from "../DavClient";
|
import client from "../DavClient";
|
||||||
|
@ -270,15 +263,13 @@ export function getAlbumFileInfos(
|
||||||
albumUser: string,
|
albumUser: string,
|
||||||
albumName: string
|
albumName: string
|
||||||
): IFileInfo[] {
|
): IFileInfo[] {
|
||||||
const ephotos = photos as IExtendedPhoto[];
|
|
||||||
|
|
||||||
const uid = getCurrentUser()?.uid;
|
const uid = getCurrentUser()?.uid;
|
||||||
const collection =
|
const collection =
|
||||||
albumUser === uid
|
albumUser === uid
|
||||||
? `/photos/${uid}/albums/${albumName}`
|
? `/photos/${uid}/albums/${albumName}`
|
||||||
: `/photos/${uid}/sharedalbums/${albumName} (${albumUser})`;
|
: `/photos/${uid}/sharedalbums/${albumName} (${albumUser})`;
|
||||||
|
|
||||||
return ephotos.map((photo) => {
|
return photos.map((photo) => {
|
||||||
const basename =
|
const basename =
|
||||||
albumUser === uid
|
albumUser === uid
|
||||||
? `${photo.fileid}-${photo.basename}`
|
? `${photo.fileid}-${photo.basename}`
|
||||||
|
|
|
@ -46,8 +46,33 @@ export async function getFiles(photos: IPhoto[]): Promise<IFileInfo[]> {
|
||||||
return getAlbumFileInfos(photos, route.params.user, route.params.name);
|
return getAlbumFileInfos(photos, route.params.user, route.params.name);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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) => {
|
||||||
|
return {
|
||||||
|
fileid: photo.fileid,
|
||||||
|
filename: photo.filename.split("/").slice(3).join("/"),
|
||||||
|
originalFilename: photo.filename,
|
||||||
|
basename: photo.basename,
|
||||||
|
mime: photo.mimetype,
|
||||||
|
hasPreview: true,
|
||||||
|
etag: photo.etag,
|
||||||
|
} 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 = photos.map((photo) => photo.fileid);
|
const fileIds = photosWithoutFilename.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 = [];
|
||||||
|
@ -56,8 +81,10 @@ export async function getFiles(photos: IPhoto[]): Promise<IFileInfo[]> {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get file infos for each chunk
|
// Get file infos for each chunk
|
||||||
const fileInfos = await Promise.all(chunks.map(getFilesInternal));
|
const ef = await Promise.all(chunks.map(getFilesInternal));
|
||||||
return fileInfos.flat();
|
fileInfos = fileInfos.concat(ef.flat());
|
||||||
|
|
||||||
|
return fileInfos;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -152,16 +179,6 @@ export async function* runInParallel<T>(
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Delete a single file
|
|
||||||
*
|
|
||||||
* @param path path to the file
|
|
||||||
*/
|
|
||||||
export async function deleteFile(path: string) {
|
|
||||||
const prefixPath = `/files/${getCurrentUser()?.uid}`;
|
|
||||||
return await client.deleteFile(`${prefixPath}${path}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Delete all files in a given list of Ids
|
* Delete all files in a given list of Ids
|
||||||
*
|
*
|
||||||
|
@ -189,11 +206,15 @@ export async function* deletePhotos(photos: IPhoto[]) {
|
||||||
fileInfos = fileInfos.filter((f) => fileIdsSet.has(f.fileid));
|
fileInfos = fileInfos.filter((f) => fileIdsSet.has(f.fileid));
|
||||||
const calls = fileInfos.map((fileInfo) => async () => {
|
const calls = fileInfos.map((fileInfo) => async () => {
|
||||||
try {
|
try {
|
||||||
await deleteFile(fileInfo.filename);
|
await client.deleteFile(fileInfo.originalFilename);
|
||||||
return fileInfo.fileid;
|
return fileInfo.fileid;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Failed to delete", fileInfo, error);
|
console.error("Failed to delete", fileInfo, error);
|
||||||
showError(t("memories", "Failed to delete {fileName}.", fileInfo));
|
showError(
|
||||||
|
t("memories", "Failed to delete {fileName}.", {
|
||||||
|
fileName: fileInfo.filename,
|
||||||
|
})
|
||||||
|
);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
11
src/types.ts
11
src/types.ts
|
@ -39,6 +39,10 @@ export type IPhoto = {
|
||||||
etag?: string;
|
etag?: string;
|
||||||
/** Path to file */
|
/** Path to file */
|
||||||
filename?: string;
|
filename?: string;
|
||||||
|
/** Base name of file */
|
||||||
|
basename?: string;
|
||||||
|
/** Mime type of file */
|
||||||
|
mimetype?: string;
|
||||||
/** Bit flags */
|
/** Bit flags */
|
||||||
flag: number;
|
flag: number;
|
||||||
/** DayID from server */
|
/** DayID from server */
|
||||||
|
@ -81,13 +85,6 @@ export type IPhoto = {
|
||||||
datetaken?: number;
|
datetaken?: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
export interface IExtendedPhoto extends IPhoto {
|
|
||||||
/** Base name of file */
|
|
||||||
basename: string;
|
|
||||||
/** Mime type of file */
|
|
||||||
mimetype: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface IFolder extends IPhoto {
|
export interface IFolder extends IPhoto {
|
||||||
/** Path to folder */
|
/** Path to folder */
|
||||||
path: string;
|
path: string;
|
||||||
|
|
Loading…
Reference in New Issue