diff options
| author | syuilo <Syuilotan@yahoo.co.jp> | 2021-11-13 12:23:49 +0900 |
|---|---|---|
| committer | syuilo <Syuilotan@yahoo.co.jp> | 2021-11-13 12:23:49 +0900 |
| commit | 2795fe457909c687f668d020ef65d52abc3182fb (patch) | |
| tree | 0a52e4e4d854333496fcc487560c93c3de5d5eb5 /src/services/drive | |
| parent | Merge branch 'develop' (diff) | |
| parent | 12.96.0 (diff) | |
| download | misskey-2795fe457909c687f668d020ef65d52abc3182fb.tar.gz misskey-2795fe457909c687f668d020ef65d52abc3182fb.tar.bz2 misskey-2795fe457909c687f668d020ef65d52abc3182fb.zip | |
Merge branch 'develop'
Diffstat (limited to 'src/services/drive')
| -rw-r--r-- | src/services/drive/add-file.ts | 466 | ||||
| -rw-r--r-- | src/services/drive/delete-file.ts | 166 | ||||
| -rw-r--r-- | src/services/drive/generate-video-thumbnail.ts | 37 | ||||
| -rw-r--r-- | src/services/drive/image-processor.ts | 107 | ||||
| -rw-r--r-- | src/services/drive/internal-storage.ts | 35 | ||||
| -rw-r--r-- | src/services/drive/logger.ts | 3 | ||||
| -rw-r--r-- | src/services/drive/s3.ts | 24 | ||||
| -rw-r--r-- | src/services/drive/upload-from-url.ts | 62 |
8 files changed, 0 insertions, 900 deletions
diff --git a/src/services/drive/add-file.ts b/src/services/drive/add-file.ts deleted file mode 100644 index 6c5fefd4ad..0000000000 --- a/src/services/drive/add-file.ts +++ /dev/null @@ -1,466 +0,0 @@ -import * as fs from 'fs'; - -import { v4 as uuid } from 'uuid'; - -import { publishMainStream, publishDriveStream } from '@/services/stream'; -import { deleteFile } from './delete-file'; -import { fetchMeta } from '@/misc/fetch-meta'; -import { GenerateVideoThumbnail } from './generate-video-thumbnail'; -import { driveLogger } from './logger'; -import { IImage, convertSharpToJpeg, convertSharpToWebp, convertSharpToPng, convertSharpToPngOrJpeg } from './image-processor'; -import { contentDisposition } from '@/misc/content-disposition'; -import { getFileInfo } from '@/misc/get-file-info'; -import { DriveFiles, DriveFolders, Users, Instances, UserProfiles } from '@/models/index'; -import { InternalStorage } from './internal-storage'; -import { DriveFile } from '@/models/entities/drive-file'; -import { IRemoteUser, User } from '@/models/entities/user'; -import { driveChart, perUserDriveChart, instanceChart } from '@/services/chart/index'; -import { genId } from '@/misc/gen-id'; -import { isDuplicateKeyValueError } from '@/misc/is-duplicate-key-value-error'; -import * as S3 from 'aws-sdk/clients/s3'; -import { getS3 } from './s3'; -import * as sharp from 'sharp'; - -const logger = driveLogger.createSubLogger('register', 'yellow'); - -/*** - * Save file - * @param path Path for original - * @param name Name for original - * @param type Content-Type for original - * @param hash Hash for original - * @param size Size for original - */ -async function save(file: DriveFile, path: string, name: string, type: string, hash: string, size: number): Promise<DriveFile> { - // thunbnail, webpublic を必要なら生成 - const alts = await generateAlts(path, type, !file.uri); - - const meta = await fetchMeta(); - - if (meta.useObjectStorage) { - //#region ObjectStorage params - let [ext] = (name.match(/\.([a-zA-Z0-9_-]+)$/) || ['']); - - if (ext === '') { - if (type === 'image/jpeg') ext = '.jpg'; - if (type === 'image/png') ext = '.png'; - if (type === 'image/webp') ext = '.webp'; - if (type === 'image/apng') ext = '.apng'; - if (type === 'image/vnd.mozilla.apng') ext = '.apng'; - } - - const baseUrl = meta.objectStorageBaseUrl - || `${ meta.objectStorageUseSSL ? 'https' : 'http' }://${ meta.objectStorageEndpoint }${ meta.objectStoragePort ? `:${meta.objectStoragePort}` : '' }/${ meta.objectStorageBucket }`; - - // for original - const key = `${meta.objectStoragePrefix}/${uuid()}${ext}`; - const url = `${ baseUrl }/${ key }`; - - // for alts - let webpublicKey: string | null = null; - let webpublicUrl: string | null = null; - let thumbnailKey: string | null = null; - let thumbnailUrl: string | null = null; - //#endregion - - //#region Uploads - logger.info(`uploading original: ${key}`); - const uploads = [ - upload(key, fs.createReadStream(path), type, name) - ]; - - if (alts.webpublic) { - webpublicKey = `${meta.objectStoragePrefix}/webpublic-${uuid()}.${alts.webpublic.ext}`; - webpublicUrl = `${ baseUrl }/${ webpublicKey }`; - - logger.info(`uploading webpublic: ${webpublicKey}`); - uploads.push(upload(webpublicKey, alts.webpublic.data, alts.webpublic.type, name)); - } - - if (alts.thumbnail) { - thumbnailKey = `${meta.objectStoragePrefix}/thumbnail-${uuid()}.${alts.thumbnail.ext}`; - thumbnailUrl = `${ baseUrl }/${ thumbnailKey }`; - - logger.info(`uploading thumbnail: ${thumbnailKey}`); - uploads.push(upload(thumbnailKey, alts.thumbnail.data, alts.thumbnail.type)); - } - - await Promise.all(uploads); - //#endregion - - file.url = url; - file.thumbnailUrl = thumbnailUrl; - file.webpublicUrl = webpublicUrl; - file.accessKey = key; - file.thumbnailAccessKey = thumbnailKey; - file.webpublicAccessKey = webpublicKey; - file.name = name; - file.type = type; - file.md5 = hash; - file.size = size; - file.storedInternal = false; - - return await DriveFiles.save(file); - } else { // use internal storage - const accessKey = uuid(); - const thumbnailAccessKey = 'thumbnail-' + uuid(); - const webpublicAccessKey = 'webpublic-' + uuid(); - - const url = InternalStorage.saveFromPath(accessKey, path); - - let thumbnailUrl: string | null = null; - let webpublicUrl: string | null = null; - - if (alts.thumbnail) { - thumbnailUrl = InternalStorage.saveFromBuffer(thumbnailAccessKey, alts.thumbnail.data); - logger.info(`thumbnail stored: ${thumbnailAccessKey}`); - } - - if (alts.webpublic) { - webpublicUrl = InternalStorage.saveFromBuffer(webpublicAccessKey, alts.webpublic.data); - logger.info(`web stored: ${webpublicAccessKey}`); - } - - file.storedInternal = true; - file.url = url; - file.thumbnailUrl = thumbnailUrl; - file.webpublicUrl = webpublicUrl; - file.accessKey = accessKey; - file.thumbnailAccessKey = thumbnailAccessKey; - file.webpublicAccessKey = webpublicAccessKey; - file.name = name; - file.type = type; - file.md5 = hash; - file.size = size; - - return await DriveFiles.save(file); - } -} - -/** - * Generate webpublic, thumbnail, etc - * @param path Path for original - * @param type Content-Type for original - * @param generateWeb Generate webpublic or not - */ -export async function generateAlts(path: string, type: string, generateWeb: boolean) { - if (type.startsWith('video/')) { - try { - const thumbnail = await GenerateVideoThumbnail(path); - return { - webpublic: null, - thumbnail - }; - } catch (e) { - logger.warn(`GenerateVideoThumbnail failed: ${e}`); - return { - webpublic: null, - thumbnail: null - }; - } - } - - if (!['image/jpeg', 'image/png', 'image/webp'].includes(type)) { - logger.debug(`web image and thumbnail not created (not an required file)`); - return { - webpublic: null, - thumbnail: null - }; - } - - let img: sharp.Sharp | null = null; - - try { - img = sharp(path); - const metadata = await img.metadata(); - const isAnimated = metadata.pages && metadata.pages > 1; - - // skip animated - if (isAnimated) { - return { - webpublic: null, - thumbnail: null - }; - } - } catch (e) { - logger.warn(`sharp failed: ${e}`); - return { - webpublic: null, - thumbnail: null - }; - } - - // #region webpublic - let webpublic: IImage | null = null; - - if (generateWeb) { - logger.info(`creating web image`); - - try { - if (['image/jpeg'].includes(type)) { - webpublic = await convertSharpToJpeg(img, 2048, 2048); - } else if (['image/webp'].includes(type)) { - webpublic = await convertSharpToWebp(img, 2048, 2048); - } else if (['image/png'].includes(type)) { - webpublic = await convertSharpToPng(img, 2048, 2048); - } else { - logger.debug(`web image not created (not an required image)`); - } - } catch (e) { - logger.warn(`web image not created (an error occured)`, e); - } - } else { - logger.info(`web image not created (from remote)`); - } - // #endregion webpublic - - // #region thumbnail - let thumbnail: IImage | null = null; - - try { - if (['image/jpeg', 'image/webp'].includes(type)) { - thumbnail = await convertSharpToJpeg(img, 498, 280); - } else if (['image/png'].includes(type)) { - thumbnail = await convertSharpToPngOrJpeg(img, 498, 280); - } else { - logger.debug(`thumbnail not created (not an required file)`); - } - } catch (e) { - logger.warn(`thumbnail not created (an error occured)`, e); - } - // #endregion thumbnail - - return { - webpublic, - thumbnail, - }; -} - -/** - * Upload to ObjectStorage - */ -async function upload(key: string, stream: fs.ReadStream | Buffer, type: string, filename?: string) { - if (type === 'image/apng') type = 'image/png'; - - const meta = await fetchMeta(); - - const params = { - Bucket: meta.objectStorageBucket, - Key: key, - Body: stream, - ContentType: type, - CacheControl: 'max-age=31536000, immutable', - } as S3.PutObjectRequest; - - if (filename) params.ContentDisposition = contentDisposition('inline', filename); - if (meta.objectStorageSetPublicRead) params.ACL = 'public-read'; - - const s3 = getS3(meta); - - const upload = s3.upload(params, { - partSize: s3.endpoint?.hostname === 'storage.googleapis.com' ? 500 * 1024 * 1024 : 8 * 1024 * 1024 - }); - - const result = await upload.promise(); - if (result) logger.debug(`Uploaded: ${result.Bucket}/${result.Key} => ${result.Location}`); -} - -async function deleteOldFile(user: IRemoteUser) { - const q = DriveFiles.createQueryBuilder('file') - .where('file.userId = :userId', { userId: user.id }) - .andWhere('file.isLink = FALSE'); - - if (user.avatarId) { - q.andWhere('file.id != :avatarId', { avatarId: user.avatarId }); - } - - if (user.bannerId) { - q.andWhere('file.id != :bannerId', { bannerId: user.bannerId }); - } - - q.orderBy('file.id', 'ASC'); - - const oldFile = await q.getOne(); - - if (oldFile) { - deleteFile(oldFile, true); - } -} - -/** - * Add file to drive - * - * @param user User who wish to add file - * @param path File path - * @param name Name - * @param comment Comment - * @param folderId Folder ID - * @param force If set to true, forcibly upload the file even if there is a file with the same hash. - * @param isLink Do not save file to local - * @param url URL of source (URLからアップロードされた場合(ローカル/リモート)の元URL) - * @param uri URL of source (リモートインスタンスのURLからアップロードされた場合の元URL) - * @param sensitive Mark file as sensitive - * @return Created drive file - */ -export default async function( - user: { id: User['id']; host: User['host'] } | null, - path: string, - name: string | null = null, - comment: string | null = null, - folderId: any = null, - force: boolean = false, - isLink: boolean = false, - url: string | null = null, - uri: string | null = null, - sensitive: boolean | null = null -): Promise<DriveFile> { - const info = await getFileInfo(path); - logger.info(`${JSON.stringify(info)}`); - - // detect name - const detectedName = name || (info.type.ext ? `untitled.${info.type.ext}` : 'untitled'); - - if (user && !force) { - // Check if there is a file with the same hash - const much = await DriveFiles.findOne({ - md5: info.md5, - userId: user.id, - }); - - if (much) { - logger.info(`file with same hash is found: ${much.id}`); - return much; - } - } - - //#region Check drive usage - if (user && !isLink) { - const usage = await DriveFiles.calcDriveUsageOf(user); - - const instance = await fetchMeta(); - const driveCapacity = 1024 * 1024 * (Users.isLocalUser(user) ? instance.localDriveCapacityMb : instance.remoteDriveCapacityMb); - - logger.debug(`drive usage is ${usage} (max: ${driveCapacity})`); - - // If usage limit exceeded - if (usage + info.size > driveCapacity) { - if (Users.isLocalUser(user)) { - throw new Error('no-free-space'); - } else { - // (アバターまたはバナーを含まず)最も古いファイルを削除する - deleteOldFile(await Users.findOneOrFail(user.id) as IRemoteUser); - } - } - } - //#endregion - - const fetchFolder = async () => { - if (!folderId) { - return null; - } - - const driveFolder = await DriveFolders.findOne({ - id: folderId, - userId: user ? user.id : null - }); - - if (driveFolder == null) throw new Error('folder-not-found'); - - return driveFolder; - }; - - const properties: { - width?: number; - height?: number; - } = {}; - - if (info.width) { - properties['width'] = info.width; - properties['height'] = info.height; - } - - const profile = user ? await UserProfiles.findOne(user.id) : null; - - const folder = await fetchFolder(); - - let file = new DriveFile(); - file.id = genId(); - file.createdAt = new Date(); - file.userId = user ? user.id : null; - file.userHost = user ? user.host : null; - file.folderId = folder !== null ? folder.id : null; - file.comment = comment; - file.properties = properties; - file.blurhash = info.blurhash || null; - file.isLink = isLink; - file.isSensitive = user - ? Users.isLocalUser(user) && profile!.alwaysMarkNsfw ? true : - (sensitive !== null && sensitive !== undefined) - ? sensitive - : false - : false; - - if (url !== null) { - file.src = url; - - if (isLink) { - file.url = url; - // ローカルプロキシ用 - file.accessKey = uuid(); - file.thumbnailAccessKey = 'thumbnail-' + uuid(); - file.webpublicAccessKey = 'webpublic-' + uuid(); - } - } - - if (uri !== null) { - file.uri = uri; - } - - if (isLink) { - try { - file.size = 0; - file.md5 = info.md5; - file.name = detectedName; - file.type = info.type.mime; - file.storedInternal = false; - - file = await DriveFiles.save(file); - } catch (e) { - // duplicate key error (when already registered) - if (isDuplicateKeyValueError(e)) { - logger.info(`already registered ${file.uri}`); - - file = await DriveFiles.findOne({ - uri: file.uri, - userId: user ? user.id : null - }) as DriveFile; - } else { - logger.error(e); - throw e; - } - } - } else { - file = await (save(file, path, detectedName, info.type.mime, info.md5, info.size)); - } - - logger.succ(`drive file has been created ${file.id}`); - - if (user) { - DriveFiles.pack(file, { self: true }).then(packedFile => { - // Publish driveFileCreated event - publishMainStream(user.id, 'driveFileCreated', packedFile); - publishDriveStream(user.id, 'fileCreated', packedFile); - }); - } - - // 統計を更新 - driveChart.update(file, true); - perUserDriveChart.update(file, true); - if (file.userHost !== null) { - instanceChart.updateDrive(file, true); - Instances.increment({ host: file.userHost }, 'driveUsage', file.size); - Instances.increment({ host: file.userHost }, 'driveFiles', 1); - } - - return file; -} diff --git a/src/services/drive/delete-file.ts b/src/services/drive/delete-file.ts deleted file mode 100644 index 2eab17ac1c..0000000000 --- a/src/services/drive/delete-file.ts +++ /dev/null @@ -1,166 +0,0 @@ -import { DriveFile } from '@/models/entities/drive-file'; -import { InternalStorage } from './internal-storage'; -import { DriveFiles, Instances, Notes, Users } from '@/models/index'; -import { driveChart, perUserDriveChart, instanceChart } from '@/services/chart/index'; -import { createDeleteObjectStorageFileJob } from '@/queue/index'; -import { fetchMeta } from '@/misc/fetch-meta'; -import { getS3 } from './s3'; -import { v4 as uuid } from 'uuid'; -import { Note } from '@/models/entities/note'; -import { renderActivity } from '@/remote/activitypub/renderer/index'; -import renderDelete from '@/remote/activitypub/renderer/delete'; -import renderTombstone from '@/remote/activitypub/renderer/tombstone'; -import config from '@/config/index'; -import { deliverToFollowers } from '@/remote/activitypub/deliver-manager'; -import { Brackets } from 'typeorm'; -import { deliverToRelays } from '../relay'; - -export async function deleteFile(file: DriveFile, isExpired = false) { - if (file.storedInternal) { - InternalStorage.del(file.accessKey!); - - if (file.thumbnailUrl) { - InternalStorage.del(file.thumbnailAccessKey!); - } - - if (file.webpublicUrl) { - InternalStorage.del(file.webpublicAccessKey!); - } - } else if (!file.isLink) { - createDeleteObjectStorageFileJob(file.accessKey!); - - if (file.thumbnailUrl) { - createDeleteObjectStorageFileJob(file.thumbnailAccessKey!); - } - - if (file.webpublicUrl) { - createDeleteObjectStorageFileJob(file.webpublicAccessKey!); - } - } - - postProcess(file, isExpired); -} - -export async function deleteFileSync(file: DriveFile, isExpired = false) { - if (file.storedInternal) { - InternalStorage.del(file.accessKey!); - - if (file.thumbnailUrl) { - InternalStorage.del(file.thumbnailAccessKey!); - } - - if (file.webpublicUrl) { - InternalStorage.del(file.webpublicAccessKey!); - } - } else if (!file.isLink) { - const promises = []; - - promises.push(deleteObjectStorageFile(file.accessKey!)); - - if (file.thumbnailUrl) { - promises.push(deleteObjectStorageFile(file.thumbnailAccessKey!)); - } - - if (file.webpublicUrl) { - promises.push(deleteObjectStorageFile(file.webpublicAccessKey!)); - } - - await Promise.all(promises); - } - - postProcess(file, isExpired); -} - -async function postProcess(file: DriveFile, isExpired = false) { - // リモートファイル期限切れ削除後は直リンクにする - if (isExpired && file.userHost !== null && file.uri != null) { - DriveFiles.update(file.id, { - isLink: true, - url: file.uri, - thumbnailUrl: null, - webpublicUrl: null, - storedInternal: false, - // ローカルプロキシ用 - accessKey: uuid(), - thumbnailAccessKey: 'thumbnail-' + uuid(), - webpublicAccessKey: 'webpublic-' + uuid(), - }); - } else { - DriveFiles.delete(file.id); - - // TODO: トランザクション - const relatedNotes = await findRelatedNotes(file.id); - for (const relatedNote of relatedNotes) { // for each note with deleted driveFile - const cascadingNotes = (await findCascadingNotes(relatedNote)).filter(note => !note.localOnly); - for (const cascadingNote of cascadingNotes) { // for each notes subject to cascade deletion - if (!cascadingNote.user) continue; - if (!Users.isLocalUser(cascadingNote.user)) continue; - const content = renderActivity(renderDelete(renderTombstone(`${config.url}/notes/${cascadingNote.id}`), cascadingNote.user)); - deliverToFollowers(cascadingNote.user, content); // federate delete msg - deliverToRelays(cascadingNote.user, content); - } - if (!relatedNote.user) continue; - if (Users.isLocalUser(relatedNote.user)) { - const content = renderActivity(renderDelete(renderTombstone(`${config.url}/notes/${relatedNote.id}`), relatedNote.user)); - deliverToFollowers(relatedNote.user, content); - deliverToRelays(relatedNote.user, content); - } - } - Notes.createQueryBuilder().delete() - .where(':id = ANY("fileIds")', { id: file.id }) - .execute(); - } - - // 統計を更新 - driveChart.update(file, false); - perUserDriveChart.update(file, false); - if (file.userHost !== null) { - instanceChart.updateDrive(file, false); - Instances.decrement({ host: file.userHost }, 'driveUsage', file.size); - Instances.decrement({ host: file.userHost }, 'driveFiles', 1); - } -} - -export async function deleteObjectStorageFile(key: string) { - const meta = await fetchMeta(); - - const s3 = getS3(meta); - - await s3.deleteObject({ - Bucket: meta.objectStorageBucket!, - Key: key - }).promise(); -} - -async function findRelatedNotes(fileId: string) { - // NOTE: When running raw query, TypeORM converts field name to lowercase. Wrap in quotes to prevent conversion. - const relatedNotes = await Notes.createQueryBuilder('note').where(':id = ANY("fileIds")', { id: fileId }).getMany(); - for (const relatedNote of relatedNotes) { - const user = await Users.findOne({ id: relatedNote.userId }); - if (user) - relatedNote.user = user; - } - return relatedNotes; -} - -async function findCascadingNotes(note: Note) { - const cascadingNotes: Note[] = []; - - const recursive = async (noteId: string) => { - const query = Notes.createQueryBuilder('note') - .where('note.replyId = :noteId', { noteId }) - .orWhere(new Brackets(q => { - q.where('note.renoteId = :noteId', { noteId }) - .andWhere('note.text IS NOT NULL'); - })) - .leftJoinAndSelect('note.user', 'user'); - const replies = await query.getMany(); - for (const reply of replies) { - cascadingNotes.push(reply); - await recursive(reply.id); - } - }; - await recursive(note.id); - - return cascadingNotes.filter(note => note.userHost === null); // filter out non-local users -} diff --git a/src/services/drive/generate-video-thumbnail.ts b/src/services/drive/generate-video-thumbnail.ts deleted file mode 100644 index f0adc7c338..0000000000 --- a/src/services/drive/generate-video-thumbnail.ts +++ /dev/null @@ -1,37 +0,0 @@ -import * as fs from 'fs'; -import * as tmp from 'tmp'; -import { IImage, convertToJpeg } from './image-processor'; -import * as FFmpeg from 'fluent-ffmpeg'; - -export async function GenerateVideoThumbnail(path: string): Promise<IImage> { - const [outDir, cleanup] = await new Promise<[string, any]>((res, rej) => { - tmp.dir((e, path, cleanup) => { - if (e) return rej(e); - res([path, cleanup]); - }); - }); - - await new Promise((res, rej) => { - FFmpeg({ - source: path - }) - .on('end', res) - .on('error', rej) - .screenshot({ - folder: outDir, - filename: 'output.png', - count: 1, - timestamps: ['5%'] - }); - }); - - const outPath = `${outDir}/output.png`; - - const thumbnail = await convertToJpeg(outPath, 498, 280); - - // cleanup - await fs.promises.unlink(outPath); - cleanup(); - - return thumbnail; -} diff --git a/src/services/drive/image-processor.ts b/src/services/drive/image-processor.ts deleted file mode 100644 index 493bf5c1cc..0000000000 --- a/src/services/drive/image-processor.ts +++ /dev/null @@ -1,107 +0,0 @@ -import * as sharp from 'sharp'; - -export type IImage = { - data: Buffer; - ext: string | null; - type: string; -}; - -/** - * Convert to JPEG - * with resize, remove metadata, resolve orientation, stop animation - */ -export async function convertToJpeg(path: string, width: number, height: number): Promise<IImage> { - return convertSharpToJpeg(await sharp(path), width, height); -} - -export async function convertSharpToJpeg(sharp: sharp.Sharp, width: number, height: number): Promise<IImage> { - const data = await sharp - .resize(width, height, { - fit: 'inside', - withoutEnlargement: true - }) - .rotate() - .jpeg({ - quality: 85, - progressive: true - }) - .toBuffer(); - - return { - data, - ext: 'jpg', - type: 'image/jpeg' - }; -} - -/** - * Convert to WebP - * with resize, remove metadata, resolve orientation, stop animation - */ -export async function convertToWebp(path: string, width: number, height: number): Promise<IImage> { - return convertSharpToWebp(await sharp(path), width, height); -} - -export async function convertSharpToWebp(sharp: sharp.Sharp, width: number, height: number): Promise<IImage> { - const data = await sharp - .resize(width, height, { - fit: 'inside', - withoutEnlargement: true - }) - .rotate() - .webp({ - quality: 85 - }) - .toBuffer(); - - return { - data, - ext: 'webp', - type: 'image/webp' - }; -} - -/** - * Convert to PNG - * with resize, remove metadata, resolve orientation, stop animation - */ -export async function convertToPng(path: string, width: number, height: number): Promise<IImage> { - return convertSharpToPng(await sharp(path), width, height); -} - -export async function convertSharpToPng(sharp: sharp.Sharp, width: number, height: number): Promise<IImage> { - const data = await sharp - .resize(width, height, { - fit: 'inside', - withoutEnlargement: true - }) - .rotate() - .png() - .toBuffer(); - - return { - data, - ext: 'png', - type: 'image/png' - }; -} - -/** - * Convert to PNG or JPEG - * with resize, remove metadata, resolve orientation, stop animation - */ -export async function convertToPngOrJpeg(path: string, width: number, height: number): Promise<IImage> { - return convertSharpToPngOrJpeg(await sharp(path), width, height); -} - -export async function convertSharpToPngOrJpeg(sharp: sharp.Sharp, width: number, height: number): Promise<IImage> { - const stats = await sharp.stats(); - const metadata = await sharp.metadata(); - - // 不透明で300x300pxの範囲を超えていればJPEG - if (stats.isOpaque && ((metadata.width && metadata.width >= 300) || (metadata.height && metadata!.height >= 300))) { - return await convertSharpToJpeg(sharp, width, height); - } else { - return await convertSharpToPng(sharp, width, height); - } -} diff --git a/src/services/drive/internal-storage.ts b/src/services/drive/internal-storage.ts deleted file mode 100644 index deaf3dc831..0000000000 --- a/src/services/drive/internal-storage.ts +++ /dev/null @@ -1,35 +0,0 @@ -import * as fs from 'fs'; -import * as Path from 'path'; -import { fileURLToPath } from 'url'; -import { dirname } from 'path'; -import config from '@/config/index'; - -//const _filename = fileURLToPath(import.meta.url); -const _filename = __filename; -const _dirname = dirname(_filename); - -export class InternalStorage { - private static readonly path = Path.resolve(_dirname, '../../../files'); - - public static resolvePath = (key: string) => Path.resolve(InternalStorage.path, key); - - public static read(key: string) { - return fs.createReadStream(InternalStorage.resolvePath(key)); - } - - public static saveFromPath(key: string, srcPath: string) { - fs.mkdirSync(InternalStorage.path, { recursive: true }); - fs.copyFileSync(srcPath, InternalStorage.resolvePath(key)); - return `${config.url}/files/${key}`; - } - - public static saveFromBuffer(key: string, data: Buffer) { - fs.mkdirSync(InternalStorage.path, { recursive: true }); - fs.writeFileSync(InternalStorage.resolvePath(key), data); - return `${config.url}/files/${key}`; - } - - public static del(key: string) { - fs.unlink(InternalStorage.resolvePath(key), () => {}); - } -} diff --git a/src/services/drive/logger.ts b/src/services/drive/logger.ts deleted file mode 100644 index 655d074d6e..0000000000 --- a/src/services/drive/logger.ts +++ /dev/null @@ -1,3 +0,0 @@ -import Logger from '../logger'; - -export const driveLogger = new Logger('drive', 'blue'); diff --git a/src/services/drive/s3.ts b/src/services/drive/s3.ts deleted file mode 100644 index f473c4a203..0000000000 --- a/src/services/drive/s3.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { URL } from 'url'; -import * as S3 from 'aws-sdk/clients/s3'; -import { Meta } from '@/models/entities/meta'; -import { getAgentByUrl } from '@/misc/fetch'; - -export function getS3(meta: Meta) { - const u = meta.objectStorageEndpoint != null - ? `${meta.objectStorageUseSSL ? 'https://' : 'http://'}${meta.objectStorageEndpoint}` - : `${meta.objectStorageUseSSL ? 'https://' : 'http://'}example.net`; - - return new S3({ - endpoint: meta.objectStorageEndpoint || undefined, - accessKeyId: meta.objectStorageAccessKey!, - secretAccessKey: meta.objectStorageSecretKey!, - region: meta.objectStorageRegion || undefined, - sslEnabled: meta.objectStorageUseSSL, - s3ForcePathStyle: !meta.objectStorageEndpoint // AWS with endPoint omitted - ? false - : meta.objectStorageS3ForcePathStyle, - httpOptions: { - agent: getAgentByUrl(new URL(u), !meta.objectStorageUseProxy) - } - }); -} diff --git a/src/services/drive/upload-from-url.ts b/src/services/drive/upload-from-url.ts deleted file mode 100644 index 29788c4af4..0000000000 --- a/src/services/drive/upload-from-url.ts +++ /dev/null @@ -1,62 +0,0 @@ -import { URL } from 'url'; -import create from './add-file'; -import { User } from '@/models/entities/user'; -import { driveLogger } from './logger'; -import { createTemp } from '@/misc/create-temp'; -import { downloadUrl } from '@/misc/download-url'; -import { DriveFolder } from '@/models/entities/drive-folder'; -import { DriveFile } from '@/models/entities/drive-file'; -import { DriveFiles } from '@/models/index'; - -const logger = driveLogger.createSubLogger('downloader'); - -export default async ( - url: string, - user: { id: User['id']; host: User['host'] } | null, - folderId: DriveFolder['id'] | null = null, - uri: string | null = null, - sensitive = false, - force = false, - link = false, - comment = null -): Promise<DriveFile> => { - let name = new URL(url).pathname.split('/').pop() || null; - if (name == null || !DriveFiles.validateFileName(name)) { - name = null; - } - - // If the comment is same as the name, skip comment - // (image.name is passed in when receiving attachment) - if (comment !== null && name == comment) { - comment = null; - } - - // Create temp file - const [path, cleanup] = await createTemp(); - - // write content at URL to temp file - await downloadUrl(url, path); - - let driveFile: DriveFile; - let error; - - try { - driveFile = await create(user, path, name, comment, folderId, force, link, url, uri, sensitive); - logger.succ(`Got: ${driveFile.id}`); - } catch (e) { - error = e; - logger.error(`Failed to create drive file: ${e}`, { - url: url, - e: e - }); - } - - // clean-up - cleanup(); - - if (error) { - throw error; - } else { - return driveFile!; - } -}; |