summaryrefslogtreecommitdiff
path: root/src/services
diff options
context:
space:
mode:
authorsyuilo <syuilotan@yahoo.co.jp>2018-05-25 18:41:49 +0900
committersyuilo <syuilotan@yahoo.co.jp>2018-05-25 18:41:49 +0900
commitbd434ed02dd69b4a120de5a727a3e5d321f3cbfe (patch)
treee83ad93481d5253b2c911de6b5d7d9dc2167a075 /src/services
parentUpdate welcome.vue (diff)
downloadsharkey-bd434ed02dd69b4a120de5a727a3e5d321f3cbfe.tar.gz
sharkey-bd434ed02dd69b4a120de5a727a3e5d321f3cbfe.tar.bz2
sharkey-bd434ed02dd69b4a120de5a727a3e5d321f3cbfe.zip
Refactor and some fixes
Diffstat (limited to 'src/services')
-rw-r--r--src/services/drive/add-file.ts423
1 files changed, 177 insertions, 246 deletions
diff --git a/src/services/drive/add-file.ts b/src/services/drive/add-file.ts
index bcd5bee512..8b1d3eef00 100644
--- a/src/services/drive/add-file.ts
+++ b/src/services/drive/add-file.ts
@@ -1,6 +1,5 @@
import { Buffer } from 'buffer';
import * as fs from 'fs';
-import * as tmp from 'tmp';
import * as stream from 'stream';
import * as mongodb from 'mongodb';
@@ -14,8 +13,7 @@ import DriveFile, { IMetadata, getDriveFileBucket, IDriveFile, DriveFileChunk }
import DriveFolder from '../../models/drive-folder';
import { pack } from '../../models/drive-file';
import event, { publishDriveStream } from '../../publishers/stream';
-import getAcct from '../../acct/render';
-import { IUser, isLocalUser, isRemoteUser } from '../../models/user';
+import { isLocalUser, IRemoteUser } from '../../models/user';
import DriveFileThumbnail, { getDriveFileThumbnailBucket, DriveFileThumbnailChunk } from '../../models/drive-file-thumbnail';
import genThumbnail from '../../drive/gen-thumbnail';
@@ -25,13 +23,6 @@ const gm = _gm.subClass({
const log = debug('misskey:drive:add-file');
-const tmpFile = (): Promise<[string, any]> => new Promise((resolve, reject) => {
- tmp.file((e, path, fd, cleanup) => {
- if (e) return reject(e);
- resolve([path, cleanup]);
- });
-});
-
const writeChunks = (name: string, readable: stream.Readable, type: string, metadata: any) =>
getDriveFileBucket()
.then(bucket => new Promise((resolve, reject) => {
@@ -55,8 +46,59 @@ const writeThumbnailChunks = (name: string, readable: stream.Readable, originalI
readable.pipe(writeStream);
}));
-const addFile = async (
- user: IUser,
+async function deleteOldFile(user: IRemoteUser) {
+ const oldFile = await DriveFile.findOne({
+ _id: {
+ $nin: [user.avatarId, user.bannerId]
+ }
+ }, {
+ sort: {
+ _id: 1
+ }
+ });
+
+ if (oldFile) {
+ // チャンクをすべて削除
+ DriveFileChunk.remove({
+ files_id: oldFile._id
+ });
+
+ DriveFile.update({ _id: oldFile._id }, {
+ $set: {
+ 'metadata.deletedAt': new Date(),
+ 'metadata.isExpired': true
+ }
+ });
+
+ //#region サムネイルもあれば削除
+ const thumbnail = await DriveFileThumbnail.findOne({
+ 'metadata.originalId': oldFile._id
+ });
+
+ if (thumbnail) {
+ DriveFileThumbnailChunk.remove({
+ files_id: thumbnail._id
+ });
+
+ DriveFileThumbnail.remove({ _id: thumbnail._id });
+ }
+ //#endregion
+ }
+}
+
+/**
+ * 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.
+ * @return Created drive file
+ */
+export default async function(
+ user: any,
path: string,
name: string = null,
comment: string = null,
@@ -64,55 +106,54 @@ const addFile = async (
force: boolean = false,
url: string = null,
uri: string = null
-): Promise<IDriveFile> => {
- log(`registering ${name} (user: ${getAcct(user)}, path: ${path})`);
+): Promise<IDriveFile> {
+ // Calc md5 hash
+ const calcHash = new Promise<string>((res, rej) => {
+ const readable = fs.createReadStream(path);
+ const hash = crypto.createHash('md5');
+ const chunks = [];
+ readable
+ .on('error', rej)
+ .pipe(hash)
+ .on('error', rej)
+ .on('data', chunk => chunks.push(chunk))
+ .on('end', () => {
+ const buffer = Buffer.concat(chunks);
+ res(buffer.toString('hex'));
+ });
+ });
- // Calculate hash, get content type and get file size
- const [hash, [mime, ext], size] = await Promise.all([
- // hash
- ((): Promise<string> => new Promise((res, rej) => {
- const readable = fs.createReadStream(path);
- const hash = crypto.createHash('md5');
- const chunks = [];
- readable
- .on('error', rej)
- .pipe(hash)
- .on('error', rej)
- .on('data', (chunk) => chunks.push(chunk))
- .on('end', () => {
- const buffer = Buffer.concat(chunks);
- res(buffer.toString('hex'));
- });
- }))(),
- // mime
- ((): Promise<[string, string | null]> => new Promise((res, rej) => {
- const readable = fs.createReadStream(path);
- readable
- .on('error', rej)
- .once('data', (buffer: Buffer) => {
- readable.destroy();
- const type = fileType(buffer);
- if (type) {
- return res([type.mime, type.ext]);
- } else {
- // 種類が同定できなかったら application/octet-stream にする
- return res(['application/octet-stream', null]);
- }
- });
- }))(),
- // size
- ((): Promise<number> => new Promise((res, rej) => {
- fs.stat(path, (err, stats) => {
- if (err) return rej(err);
- res(stats.size);
+ // Detect content type
+ const detectMime = new Promise<[string, string]>((res, rej) => {
+ const readable = fs.createReadStream(path);
+ readable
+ .on('error', rej)
+ .once('data', (buffer: Buffer) => {
+ readable.destroy();
+ const type = fileType(buffer);
+ if (type) {
+ res([type.mime, type.ext]);
+ } else {
+ // 種類が同定できなかったら application/octet-stream にする
+ res(['application/octet-stream', null]);
+ }
});
- }))()
- ]);
+ });
+
+ // Get file size
+ const getFileSize = new Promise<number>((res, rej) => {
+ fs.stat(path, (err, stats) => {
+ if (err) return rej(err);
+ res(stats.size);
+ });
+ });
+
+ const [hash, [mime, ext], size] = await Promise.all([calcHash, detectMime, getFileSize]);
log(`hash: ${hash}, mime: ${mime}, ext: ${ext}, size: ${size}`);
// detect name
- const detectedName: string = name || (ext ? `untitled.${ext}` : 'untitled');
+ const detectedName = name || (ext ? `untitled.${ext}` : 'untitled');
if (!force) {
// Check if there is a file with the same hash
@@ -125,26 +166,70 @@ const addFile = async (
if (much !== null) {
log('file with same hash is found');
return much;
- } else {
- log('file with same hash is not found');
}
}
- const [wh, averageColor, folder] = await Promise.all([
- // Width and height (when image)
- (async () => {
- // 画像かどうか
- if (!/^image\/.*$/.test(mime)) {
- return null;
+ //#region Check drive usage
+ const usage = await DriveFile
+ .aggregate([{
+ $match: {
+ 'metadata.userId': user._id,
+ 'metadata.deletedAt': { $exists: false }
+ }
+ }, {
+ $project: {
+ length: true
+ }
+ }, {
+ $group: {
+ _id: null,
+ usage: { $sum: '$length' }
}
+ }])
+ .then((aggregates: any[]) => {
+ if (aggregates.length > 0) {
+ return aggregates[0].usage;
+ }
+ return 0;
+ });
- const imageType = mime.split('/')[1];
+ log(`drive usage is ${usage}`);
- // 画像でもPNGかJPEGかGIFでないならスキップ
- if (imageType != 'png' && imageType != 'jpeg' && imageType != 'gif') {
- return null;
- }
+ // If usage limit exceeded
+ if (usage + size > user.driveCapacity) {
+ if (isLocalUser(user)) {
+ throw 'no-free-space';
+ } else {
+ // (アバターまたはバナーを含まず)最も古いファイルを削除する
+ deleteOldFile(user);
+ }
+ }
+ //#endregion
+ const fetchFolder = async () => {
+ if (!folderId) {
+ return null;
+ }
+
+ const driveFolder = await DriveFolder.findOne({
+ _id: folderId,
+ userId: user._id
+ });
+
+ if (driveFolder == null) throw 'folder-not-found';
+
+ return driveFolder;
+ };
+
+ const properties = {};
+
+ let propPromises = [];
+
+ const isImage = ['image/jpeg', 'image/gif', 'image/png'].includes(mime);
+
+ if (isImage) {
+ // Calc width and height
+ const calcWh = async () => {
log('calculate image width and height...');
// Calculate width and height
@@ -153,22 +238,12 @@ const addFile = async (
log(`image width and height is calculated: ${size.width}, ${size.height}`);
- return [size.width, size.height];
- })(),
- // average color (when image)
- (async () => {
- // 画像かどうか
- if (!/^image\/.*$/.test(mime)) {
- return null;
- }
-
- const imageType = mime.split('/')[1];
-
- // 画像でもPNGかJPEGでないならスキップ
- if (imageType != 'png' && imageType != 'jpeg') {
- return null;
- }
+ properties['width'] = size.width;
+ properties['height'] = size.height;
+ };
+ // Calc average color
+ const calcAvg = async () => {
log('calculate average color...');
const info = await prominence(gm(fs.createReadStream(path), name)).identify();
@@ -185,112 +260,18 @@ const addFile = async (
log(`average color is calculated: ${r}, ${g}, ${b}`);
- return isTransparent ? [r, g, b, 255] : [r, g, b];
- })(),
- // folder
- (async () => {
- if (!folderId) {
- return null;
- }
- const driveFolder = await DriveFolder.findOne({
- _id: folderId,
- userId: user._id
- });
- if (!driveFolder) {
- throw 'folder-not-found';
- }
- return driveFolder;
- })(),
- // usage checker
- (async () => {
- // Calculate drive usage
- const usage = await DriveFile
- .aggregate([{
- $match: {
- 'metadata.userId': user._id,
- 'metadata.deletedAt': { $exists: false }
- }
- }, {
- $project: {
- length: true
- }
- }, {
- $group: {
- _id: null,
- usage: { $sum: '$length' }
- }
- }])
- .then((aggregates: any[]) => {
- if (aggregates.length > 0) {
- return aggregates[0].usage;
- }
- return 0;
- });
-
- log(`drive usage is ${usage}`);
-
- // If usage limit exceeded
- if (usage + size > user.driveCapacity) {
- if (isLocalUser(user)) {
- throw 'no-free-space';
- } else {
- //#region (アバターまたはバナーを含まず)最も古いファイルを削除する
- const oldFile = await DriveFile.findOne({
- _id: {
- $nin: [user.avatarId, user.bannerId]
- }
- }, {
- sort: {
- _id: 1
- }
- });
-
- if (oldFile) {
- // チャンクをすべて削除
- DriveFileChunk.remove({
- files_id: oldFile._id
- });
+ const value = isTransparent ? [r, g, b, 255] : [r, g, b];
- DriveFile.update({ _id: oldFile._id }, {
- $set: {
- 'metadata.deletedAt': new Date(),
- 'metadata.isExpired': true
- }
- });
+ properties['avgColor'] = value;
+ };
- //#region サムネイルもあれば削除
- const thumbnail = await DriveFileThumbnail.findOne({
- 'metadata.originalId': oldFile._id
- });
-
- if (thumbnail) {
- DriveFileThumbnailChunk.remove({
- files_id: thumbnail._id
- });
+ propPromises = [calcWh(), calcAvg()];
+ }
- DriveFileThumbnail.remove({ _id: thumbnail._id });
- }
- //#endregion
- }
- //#endregion
- }
- }
- })()
- ]);
+ const [folder] = await Promise.all([fetchFolder(), propPromises]);
const readable = fs.createReadStream(path);
- const properties = {};
-
- if (wh) {
- properties['width'] = wh[0];
- properties['height'] = wh[1];
- }
-
- if (averageColor) {
- properties['avgColor'] = averageColor;
- }
-
const metadata = {
userId: user._id,
_user: {
@@ -309,74 +290,24 @@ const addFile = async (
metadata.uri = uri;
}
- const file = await (writeChunks(detectedName, readable, mime, metadata) as Promise<IDriveFile>);
+ const driveFile = await (writeChunks(detectedName, readable, mime, metadata) as Promise<IDriveFile>);
+
+ log(`drive file has been created ${driveFile._id}`);
+
+ pack(driveFile).then(packedFile => {
+ // Publish drive_file_created event
+ event(user._id, 'drive_file_created', packedFile);
+ publishDriveStream(user._id, 'file_created', packedFile);
+ });
try {
- const thumb = await genThumbnail(file);
+ const thumb = await genThumbnail(driveFile);
if (thumb) {
- await writeThumbnailChunks(detectedName, thumb, file._id);
+ await writeThumbnailChunks(detectedName, thumb, driveFile._id);
}
} catch (e) {
// noop
}
- return file;
-};
-
-/**
- * Add file to drive
- *
- * @param user User who wish to add file
- * @param file File path or readableStream
- * @param comment Comment
- * @param type File type
- * @param folderId Folder ID
- * @param force If set to true, forcibly upload the file even if there is a file with the same hash.
- * @return Object that represents added file
- */
-export default (user: any, file: string | stream.Readable, ...args) => new Promise<any>((resolve, reject) => {
- const isStream = typeof file === 'object' && typeof file.read === 'function';
-
- // Get file path
- new Promise<[string, any]>((res, rej) => {
- if (typeof file === 'string') {
- res([file, null]);
- } else if (isStream) {
- tmpFile()
- .then(([path, cleanup]) => {
- const readable: stream.Readable = file;
- const writable = fs.createWriteStream(path);
- readable
- .on('error', rej)
- .on('end', () => {
- res([path, cleanup]);
- })
- .pipe(writable)
- .on('error', rej);
- })
- .catch(rej);
- } else {
- rej(new Error('un-compatible file.'));
- }
- })
- .then(([path, cleanup]) => new Promise<IDriveFile>((res, rej) => {
- addFile(user, path, ...args)
- .then(file => {
- res(file);
- if (cleanup) cleanup();
- })
- .catch(rej);
- }))
- .then(file => {
- log(`drive file has been created ${file._id}`);
-
- resolve(file);
-
- pack(file).then(packedFile => {
- // Publish drive_file_created event
- event(user._id, 'drive_file_created', packedFile);
- publishDriveStream(user._id, 'file_created', packedFile);
- });
- })
- .catch(reject);
-});
+ return driveFile;
+}