import { db } from "../../db/postgre.js"; import { DriveFile } from "../entities/drive-file.js"; import { toPuny } from "../../misc/convert-host.js"; import { awaitAll } from "../../prelude/await-all.js"; import config from "../../config/index.js"; import { appendQuery, query } from "../../prelude/url.js"; import { DriveFolders, Users } from "../index.js"; import { deepClone } from "../../misc/clone.js"; import { fetchMetaSync } from "../../misc/fetch-meta.js"; export const DriveFileRepository = db.getRepository(DriveFile).extend({ validateFileName (name) { return name.trim().length > 0 && name.length <= 200 && name.indexOf("\\") === -1 && name.indexOf("/") === -1 && name.indexOf("..") === -1; }, getPublicProperties (file) { if (file.properties.orientation != null) { const properties = deepClone(file.properties); if (file.properties.orientation >= 5) { [properties.width, properties.height] = [ properties.height, properties.width ]; } properties.orientation = undefined; return properties; } return file.properties; }, isImage (file) { return !!file.type && [ "image/png", "image/apng", "image/gif", "image/jpeg", "image/webp", "image/svg+xml", "image/avif" ].includes(file.type); }, isStreamableMedia (file) { return file.type.startsWith("video/") || file.type.startsWith("audio/"); }, withStreamQuery (file, url) { if (url == null || file.allowDownload || !this.isStreamableMedia(file)) return url; return appendQuery(url, query({ stream: "1" })); }, getPublicUrl (file, thumbnail = false) { // リモートかつメディアプロキシ if (file.uri != null && file.userHost != null && config.mediaProxy != null) { return appendQuery(config.mediaProxy, query({ url: file.uri, thumbnail: thumbnail ? "1" : undefined })); } if (file.isLink && config.proxyRemoteFiles) { const url = this.getDatabasePrefetchUrl(file, thumbnail); if (url != null) return `${config.url}/proxy/${encodeURIComponent(new URL(url).pathname)}?${query({ url: url })}`; } const url = thumbnail ? file.thumbnailUrl || (this.isImage(file) ? file.webpublicUrl || file.url : null) : file.webpublicUrl || file.url; return thumbnail ? url : this.withStreamQuery(file, url); }, getDatabasePrefetchUrl (file, thumbnail = false) { return thumbnail ? file.thumbnailUrl ?? file.webpublicUrl ?? file.url : file.webpublicUrl ?? file.url; }, getFinalUrl (url) { if (!config.proxyRemoteFiles) return url; if (!url.startsWith('https://') && !url.startsWith('http://')) return url; if (url.startsWith(`${config.url}/files`)) return url; if (url.startsWith(`${config.url}/static-assets`)) return url; if (url.startsWith(`${config.url}/identicon`)) return url; if (url.startsWith(`${config.url}/avatar`)) return url; const meta = fetchMetaSync(); const baseUrl = meta ? meta.objectStorageBaseUrl ?? `${meta.objectStorageUseSSL ? "https" : "http"}://${meta.objectStorageEndpoint}${meta.objectStoragePort ? `:${meta.objectStoragePort}` : ""}/${meta.objectStorageBucket}` : null; if (baseUrl !== null && url.startsWith(baseUrl)) return url; return `${config.url}/proxy/${encodeURIComponent(new URL(url).pathname)}?${query({ url: url })}`; }, getFinalUrlMaybe (url) { if (url == null) return null; return this.getFinalUrl(url); }, async calcDriveUsageOf (user) { const id = typeof user === "object" ? user.id : user; const { sum } = await this.createQueryBuilder("file").where("file.userId = :id", { id: id }).andWhere("file.isLink = FALSE").andWhere("file.isDatabase = FALSE").select("SUM(file.size)", "sum").getRawOne(); return parseInt(sum, 10) || 0; }, async calcDatabaseUsageOf (user) { const id = typeof user === "object" ? user.id : user; const { sum } = await this.createQueryBuilder("file").where("file.userId = :id", { id: id }).andWhere("file.isLink = FALSE").andWhere("file.isDatabase = TRUE").select("SUM(file.size)", "sum").getRawOne(); return parseInt(sum, 10) || 0; }, async calcDriveUsageOfHost (host) { const { sum } = await this.createQueryBuilder("file").where("file.userHost = :host", { host: toPuny(host) }).andWhere("file.isLink = FALSE").andWhere("file.isDatabase = FALSE").select("SUM(file.size)", "sum").getRawOne(); return parseInt(sum, 10) || 0; }, async calcDriveUsageOfLocal () { const { sum } = await this.createQueryBuilder("file").where("file.userHost IS NULL").andWhere("file.isLink = FALSE").andWhere("file.isDatabase = FALSE").select("SUM(file.size)", "sum").getRawOne(); return parseInt(sum, 10) || 0; }, async calcDriveUsageOfRemote () { const { sum } = await this.createQueryBuilder("file").where("file.userHost IS NOT NULL").andWhere("file.isLink = FALSE").andWhere("file.isDatabase = FALSE").select("SUM(file.size)", "sum").getRawOne(); return parseInt(sum, 10) || 0; }, async pack (src, options) { const opts = Object.assign({ detail: false, self: false }, options); const file = typeof src === "object" ? src : await this.findOneByOrFail({ id: src }); return await awaitAll({ id: file.id, createdAt: file.createdAt.toISOString(), name: file.name, type: file.type, md5: file.md5, size: file.size, isSensitive: file.isSensitive, allowDownload: file.allowDownload, isDatabase: file.isDatabase, blurhash: file.blurhash, properties: opts.self ? file.properties : this.getPublicProperties(file), url: opts.self ? file.url : this.getPublicUrl(file, false), thumbnailUrl: this.getPublicUrl(file, true), comment: file.comment, folderId: file.folderId, folder: opts.detail && file.folderId ? DriveFolders.pack(file.folderId, { detail: true }) : null, userId: opts.withUser ? file.userId : null, user: opts.withUser && file.userId ? Users.pack(file.userId) : null }); }, async packNullable (src, options) { const opts = Object.assign({ detail: false, self: false }, options); const file = typeof src === "object" ? src : await this.findOneBy({ id: src }); if (file == null) return null; return await awaitAll({ id: file.id, createdAt: file.createdAt.toISOString(), name: file.name, type: file.type, md5: file.md5, size: file.size, isSensitive: file.isSensitive, allowDownload: file.allowDownload, isDatabase: file.isDatabase, blurhash: file.blurhash, properties: opts.self ? file.properties : this.getPublicProperties(file), url: opts.self ? file.url : this.getPublicUrl(file, false), thumbnailUrl: this.getPublicUrl(file, true), comment: file.comment, folderId: file.folderId, folder: opts.detail && file.folderId ? DriveFolders.pack(file.folderId, { detail: true }) : null, userId: opts.withUser ? file.userId : null, user: opts.withUser && file.userId ? Users.pack(file.userId) : null }); }, async packMany (files, options) { const items = await Promise.all(files.map((f)=>this.packNullable(f, options))); return items.filter((x)=>x != null); } });