aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/client/apis/google_docs/GooglePhotosClientUtils.ts27
-rw-r--r--src/client/documents/Documents.ts9
-rw-r--r--src/client/views/MainView.tsx2
-rw-r--r--src/server/apis/google/GooglePhotosUploadUtils.ts119
-rw-r--r--src/server/credentials/google_docs_token.json2
-rw-r--r--src/server/index.ts27
6 files changed, 131 insertions, 55 deletions
diff --git a/src/client/apis/google_docs/GooglePhotosClientUtils.ts b/src/client/apis/google_docs/GooglePhotosClientUtils.ts
index bb5d23971..5f5b39b14 100644
--- a/src/client/apis/google_docs/GooglePhotosClientUtils.ts
+++ b/src/client/apis/google_docs/GooglePhotosClientUtils.ts
@@ -1,16 +1,16 @@
import { PostToServer, Utils } from "../../../Utils";
import { RouteStore } from "../../../server/RouteStore";
import { ImageField } from "../../../new_fields/URLField";
-import { StrCast, Cast } from "../../../new_fields/Types";
+import { Cast } from "../../../new_fields/Types";
import { Doc, Opt } from "../../../new_fields/Doc";
import { Id } from "../../../new_fields/FieldSymbols";
-import requestImageSize = require('../../util/request-image-size');
import Photos = require('googlephotos');
import { RichTextField } from "../../../new_fields/RichTextField";
import { RichTextUtils } from "../../../new_fields/RichTextUtils";
import { EditorState } from "prosemirror-state";
import { FormattedTextBox } from "../../views/nodes/FormattedTextBox";
-import { Docs } from "../../documents/Documents";
+import { Docs, DocumentOptions } from "../../documents/Documents";
+import { type } from "os";
export namespace GooglePhotosClientUtils {
@@ -98,7 +98,7 @@ export namespace GooglePhotosClientUtils {
excluded: [],
date: undefined,
includeArchivedMedia: true,
- type: MediaType.ALL_MEDIA
+ type: MediaType.ALL_MEDIA,
};
export interface SearchResponse {
@@ -106,7 +106,18 @@ export namespace GooglePhotosClientUtils {
nextPageToken: string;
}
- export const Search = async (requested: Opt<Partial<SearchOptions>>) => {
+ export type CollectionConstructor = (data: Array<Doc>, options: DocumentOptions, ...args: any) => Doc;
+ export const CollectionFromSearch = async (provider: CollectionConstructor, requested: Opt<Partial<SearchOptions>>): Promise<Doc> => {
+ let downloads = await Search(requested);
+ return provider(downloads.map((download: any) => {
+ let document = Docs.Create.ImageDocument(Utils.prepend(`/files/${download.fileNames.clean}`));
+ document.fillColumn = true;
+ document.contentSize = download.contentSize;
+ return document;
+ }), { width: 500, height: 500 });
+ };
+
+ export const Search = async (requested: Opt<Partial<SearchOptions>>): Promise<any> => {
const options = requested || DefaultSearchOptions;
const photos = await endpoint();
const filters = new photos.Filters(options.includeArchivedMedia === undefined ? true : options.includeArchivedMedia);
@@ -133,11 +144,7 @@ export namespace GooglePhotosClientUtils {
return new Promise<Doc>(resolve => {
photos.mediaItems.search(filters, options.pageSize || 20).then(async (response: SearchResponse) => {
- response && resolve(Docs.Create.StackingDocument((await PostToServer(RouteStore.googlePhotosMediaDownload, response)).map((download: any) => {
- let document = Docs.Create.ImageDocument(Utils.prepend(`/files/${download.fileName}`));
- document.contentSize = download.contentSize;
- return document;
- }), { width: 500, height: 500 }));
+ response && resolve(await PostToServer(RouteStore.googlePhotosMediaDownload, response));
});
});
};
diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index 4b7f1eeb6..9bac57d16 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -21,7 +21,7 @@ import { AggregateFunction } from "../northstar/model/idea/idea";
import { MINIMIZED_ICON_SIZE } from "../views/globalCssVariables.scss";
import { IconBox } from "../views/nodes/IconBox";
import { Field, Doc, Opt } from "../../new_fields/Doc";
-import { OmitKeys, JSONUtils } from "../../Utils";
+import { OmitKeys, JSONUtils, Utils } from "../../Utils";
import { ImageField, VideoField, AudioField, PdfField, WebField, YoutubeField } from "../../new_fields/URLField";
import { HtmlField } from "../../new_fields/HtmlField";
import { List } from "../../new_fields/List";
@@ -332,7 +332,12 @@ export namespace Docs {
export function ImageDocument(url: string, options: DocumentOptions = {}) {
let imgField = new ImageField(new URL(url));
let inst = InstanceFromProto(Prototypes.get(DocumentType.IMG), imgField, { title: path.basename(url), ...options });
- requestImageSize(imgField.url.href)
+ let target = imgField.url.href;
+ if (new RegExp(window.location.origin).test(target)) {
+ let extension = path.extname(target);
+ target = `${target.substring(0, target.length - extension.length)}_o${extension}`;
+ }
+ requestImageSize(target)
.then((size: any) => {
let aspect = size.height / size.width;
if (!inst.proto!.nativeWidth) {
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index b72df3715..326c13424 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -470,7 +470,7 @@ export class MainView extends React.Component {
// let youtubeurl = "https://www.youtube.com/embed/TqcApsGRzWw";
// let addYoutubeSearcher = action(() => Docs.Create.YoutubeDocument(youtubeurl, { width: 600, height: 600, title: "youtube search" }));
- let googlePhotosSearch = () => GooglePhotosClientUtils.Search({ included: [GooglePhotosClientUtils.ContentCategories.ANIMALS] });
+ let googlePhotosSearch = () => GooglePhotosClientUtils.CollectionFromSearch(Docs.Create.MasonryDocument, { included: [GooglePhotosClientUtils.ContentCategories.LANDSCAPES] });
let btns: [React.RefObject<HTMLDivElement>, IconName, string, () => Doc | Promise<Doc>][] = [
[React.createRef<HTMLDivElement>(), "object-group", "Add Collection", addColNode],
diff --git a/src/server/apis/google/GooglePhotosUploadUtils.ts b/src/server/apis/google/GooglePhotosUploadUtils.ts
index 9b3e68761..5ac3eaef7 100644
--- a/src/server/apis/google/GooglePhotosUploadUtils.ts
+++ b/src/server/apis/google/GooglePhotosUploadUtils.ts
@@ -4,6 +4,9 @@ import * as fs from 'fs';
import { Utils } from '../../../Utils';
import * as path from 'path';
import { Opt } from '../../../new_fields/Doc';
+import * as sharp from 'sharp';
+
+const uploadDirectory = path.join(__dirname, "../../public/files/");
export namespace GooglePhotosUploadUtils {
@@ -18,13 +21,6 @@ export namespace GooglePhotosUploadUtils {
description: string;
}
- export interface DownloadInformation {
- mediaPath: string;
- fileName: string;
- contentType?: string;
- contentSize?: string;
- }
-
const prepend = (extension: string) => `https://photoslibrary.googleapis.com/v1/${extension}`;
const headers = (type: string) => ({
'Content-Type': `application/${type}`,
@@ -76,35 +72,92 @@ export namespace GooglePhotosUploadUtils {
});
};
- export namespace IOUtils {
+}
- export const Download = async (url: string, filename?: string, prefix = ""): Promise<Opt<DownloadInformation>> => {
- const resolved = filename || `${prefix}upload_${Utils.GenerateGuid()}${path.extname(url).toLowerCase()}`;
- const mediaPath = Paths.uploadDirectory + resolved;
- return new Promise<DownloadInformation>((resolve, reject) => {
- request.head(url, (error, res) => {
- if (error) {
- return reject(error);
- }
- const information: DownloadInformation = {
- fileName: resolved,
- contentSize: res.headers['content-length'],
- contentType: res.headers['content-type'],
- mediaPath
- };
- request(url).pipe(fs.createWriteStream(mediaPath)).on('close', () => resolve(information));
- });
- });
- };
+export namespace DownloadUtils {
- export const createIfNotExists = async (path: string) => {
- if (await new Promise<boolean>(resolve => fs.exists(path, resolve))) {
- return true;
- }
- return new Promise<boolean>(resolve => fs.mkdir(path, error => resolve(error === null)));
- };
+ export interface Size {
+ width: number;
+ suffix: string;
+ }
- export const Destroy = (mediaPath: string) => new Promise<boolean>(resolve => fs.unlink(mediaPath, error => resolve(error === null)));
+ export const Sizes: { [size: string]: Size } = {
+ SMALL: { width: 100, suffix: "_s" },
+ MEDIUM: { width: 400, suffix: "_m" },
+ LARGE: { width: 900, suffix: "_l" },
+ };
+
+ const png = ".png";
+ const pngs = [".png", ".PNG"];
+ const jpg = [".jpg", ".JPG", ".jpeg", ".JPEG"];
+ const size = "content-length";
+ const type = "content-type";
+
+ export interface DownloadInformation {
+ mediaPaths: string[];
+ fileNames: { [key: string]: string };
+ contentSize?: string;
+ contentType?: string;
}
+ const generate = (prefix: string, url: string) => `${prefix}upload_${Utils.GenerateGuid()}${path.extname(url).toLowerCase()}`;
+ const sanitize = (filename: string) => filename.replace(/\s+/g, "_");
+
+ export const Download = async (url: string, filename?: string, prefix = ""): Promise<Opt<DownloadInformation>> => {
+ const resolved = filename ? sanitize(filename) : generate(prefix, url);
+ const extension = path.extname(url) || path.extname(resolved) || png;
+ return new Promise<DownloadInformation>((resolve, reject) => {
+ request.head(url, async (error, res) => {
+ if (error) {
+ return reject(error);
+ }
+ const information: DownloadInformation = {
+ fileNames: { clean: resolved },
+ contentSize: res.headers[size],
+ contentType: res.headers[type],
+ mediaPaths: []
+ };
+ const resizers = [
+ { resizer: sharp().rotate(), suffix: "_o" },
+ ...Object.values(Sizes).map(size => ({
+ resizer: sharp().resize(size.width, undefined, { withoutEnlargement: true }).rotate(),
+ suffix: size.suffix
+ }))
+ ];
+ let validated = true;
+ if (pngs.includes(extension)) {
+ resizers.forEach(element => element.resizer = element.resizer.png());
+ } else if (jpg.includes(extension)) {
+ resizers.forEach(element => element.resizer = element.resizer.jpeg());
+ } else {
+ validated = false;
+ }
+ if (validated) {
+ for (let resizer of resizers) {
+ const suffix = resizer.suffix;
+ let mediaPath: string;
+ await new Promise<void>(resolve => {
+ const filename = resolved.substring(0, resolved.length - extension.length) + suffix + extension;
+ information.mediaPaths.push(mediaPath = uploadDirectory + filename);
+ information.fileNames[suffix] = filename;
+ request(url)
+ .pipe(resizer.resizer)
+ .pipe(fs.createWriteStream(mediaPath))
+ .on('close', resolve);
+ });
+ }
+ resolve(information);
+ }
+ });
+ });
+ };
+
+ export const createIfNotExists = async (path: string) => {
+ if (await new Promise<boolean>(resolve => fs.exists(path, resolve))) {
+ return true;
+ }
+ return new Promise<boolean>(resolve => fs.mkdir(path, error => resolve(error === null)));
+ };
+
+ export const Destroy = (mediaPath: string) => new Promise<boolean>(resolve => fs.unlink(mediaPath, error => resolve(error === null)));
} \ No newline at end of file
diff --git a/src/server/credentials/google_docs_token.json b/src/server/credentials/google_docs_token.json
index a1c23ea35..4f911f7e0 100644
--- a/src/server/credentials/google_docs_token.json
+++ b/src/server/credentials/google_docs_token.json
@@ -1 +1 @@
-{"access_token":"ya29.Glx9B3Fumh3qHpgasQvHNNrwNXtmTVWJR9XckFsnUjOswDOO91ccF3FhD4ko7Z-3rvxEljpP1Qj5BgNq305pt-pgIquoLPWYiaEtinHNF7IXGPz4s4raqJWEJPJxow","refresh_token":"1/HTv_xFHszu2Nf3iiFrUTaeKzC_Vp2-6bpIB06xW_WHI","scope":"https://www.googleapis.com/auth/presentations.readonly https://www.googleapis.com/auth/documents.readonly https://www.googleapis.com/auth/drive.file https://www.googleapis.com/auth/documents https://www.googleapis.com/auth/photoslibrary https://www.googleapis.com/auth/photoslibrary.appendonly https://www.googleapis.com/auth/drive https://www.googleapis.com/auth/presentations https://www.googleapis.com/auth/photoslibrary.sharing","token_type":"Bearer","expiry_date":1567913435149} \ No newline at end of file
+{"access_token":"ya29.Glx9B3_l5JbKNtvzx378Nsz917bP-OTKf6VZzc2K8QDBm-Y0j_-c8v7bL8LCEM3wF8d7JauF-5Z4Uq4v7wPwUQUlDO1uPyoHeSF6iz98xkgJr9OW4KzJo2Ij722gpQ","refresh_token":"1/HTv_xFHszu2Nf3iiFrUTaeKzC_Vp2-6bpIB06xW_WHI","scope":"https://www.googleapis.com/auth/presentations.readonly https://www.googleapis.com/auth/documents.readonly https://www.googleapis.com/auth/drive.file https://www.googleapis.com/auth/documents https://www.googleapis.com/auth/photoslibrary https://www.googleapis.com/auth/photoslibrary.appendonly https://www.googleapis.com/auth/drive https://www.googleapis.com/auth/presentations https://www.googleapis.com/auth/photoslibrary.sharing","token_type":"Bearer","expiry_date":1567931641928} \ No newline at end of file
diff --git a/src/server/index.ts b/src/server/index.ts
index 49010e7e2..013345a76 100644
--- a/src/server/index.ts
+++ b/src/server/index.ts
@@ -42,7 +42,7 @@ var AdmZip = require('adm-zip');
import * as YoutubeApi from "./apis/youtube/youtubeApiSample";
import { Response } from 'express-serve-static-core';
import { GoogleApiServerUtils } from "./apis/google/GoogleApiServerUtils";
-import { GooglePhotosUploadUtils } from './apis/google/GooglePhotosUploadUtils';
+import { GooglePhotosUploadUtils, DownloadUtils } from './apis/google/GooglePhotosUploadUtils';
const MongoStore = require('connect-mongo')(session);
const mongoose = require('mongoose');
const probe = require("probe-image-size");
@@ -154,6 +154,11 @@ app.get("/buxton", (req, res) => {
command_line('python scraper.py', cwd).then(onResolved, tryPython3);
});
+const STATUS = {
+ OK: 200,
+ BAD_REQUEST: 400
+};
+
const command_line = (command: string, fromDirectory?: string) => {
return new Promise<string>((resolve, reject) => {
let options: ExecOptions = {};
@@ -848,16 +853,22 @@ app.post(RouteStore.googlePhotosMediaUpload, async (req, res) => {
);
});
+interface MediaItem {
+ baseUrl: string;
+ filename: string;
+}
const prefix = "google_photos_";
+
app.post(RouteStore.googlePhotosMediaDownload, async (req, res) => {
- const contents = req.body;
- if (!contents) {
- return res.send(undefined);
+ const contents: { mediaItems: MediaItem[] } = req.body;
+ if (contents) {
+ const downloads = contents.mediaItems.map(item =>
+ DownloadUtils.Download(item.baseUrl, item.filename, prefix)
+ );
+ res.status(STATUS.OK).send(await Promise.all(downloads));
+ return;
}
- await GooglePhotosUploadUtils.initialize({ uploadDirectory, credentialsPath, tokenPath });
- res.send(await Promise.all(contents.mediaItems.map((item: any) =>
- GooglePhotosUploadUtils.IOUtils.Download(item.baseUrl, undefined, prefix)))
- );
+ res.status(STATUS.BAD_REQUEST).send();
});
const suffixMap: { [type: string]: (string | [string, string | ((json: any) => any)]) } = {