aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorSam Wilkins <samwilkins333@gmail.com>2019-09-07 17:08:49 -0400
committerSam Wilkins <samwilkins333@gmail.com>2019-09-07 17:08:49 -0400
commit32cd51e2bcc0a8cf498c0b31a5ead60802f672de (patch)
treee353f730591ee9b7d1a5661364679dc33fe38fa1 /src
parentd94509864920b2bbe7f4af8837f3af3f721b7dad (diff)
working on import from google photos
Diffstat (limited to 'src')
-rw-r--r--src/client/apis/google_docs/GooglePhotosClientUtils.ts123
-rw-r--r--src/client/views/MainView.tsx4
-rw-r--r--src/client/views/nodes/DocumentView.tsx2
-rw-r--r--src/server/RouteStore.ts3
-rw-r--r--src/server/apis/google/GooglePhotosUploadUtils.ts14
-rw-r--r--src/server/credentials/google_docs_token.json2
-rw-r--r--src/server/index.ts16
7 files changed, 140 insertions, 24 deletions
diff --git a/src/client/apis/google_docs/GooglePhotosClientUtils.ts b/src/client/apis/google_docs/GooglePhotosClientUtils.ts
index 924362c03..e8daf3dd4 100644
--- a/src/client/apis/google_docs/GooglePhotosClientUtils.ts
+++ b/src/client/apis/google_docs/GooglePhotosClientUtils.ts
@@ -6,9 +6,42 @@ 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";
export namespace GooglePhotosClientUtils {
+ export enum ContentCategories {
+ NONE = 'NONE',
+ LANDSCAPES = 'LANDSCAPES',
+ RECEIPTS = 'RECEIPTS',
+ CITYSCAPES = 'CITYSCAPES',
+ LANDMARKS = 'LANDMARKS',
+ SELFIES = 'SELFIES',
+ PEOPLE = 'PEOPLE',
+ PETS = 'PETS',
+ WEDDINGS = 'WEDDINGS',
+ BIRTHDAYS = 'BIRTHDAYS',
+ DOCUMENTS = 'DOCUMENTS',
+ TRAVEL = 'TRAVEL',
+ ANIMALS = 'ANIMALS',
+ FOOD = 'FOOD',
+ SPORT = 'SPORT',
+ NIGHT = 'NIGHT',
+ PERFORMANCES = 'PERFORMANCES',
+ WHITEBOARDS = 'WHITEBOARDS',
+ SCREENSHOTS = 'SCREENSHOTS',
+ UTILITY = 'UTILITY'
+ }
+
+ export enum MediaType {
+ ALL_MEDIA = 'ALL_MEDIA',
+ PHOTO = 'PHOTO',
+ VIDEO = 'VIDEO'
+ }
+
export type AlbumReference = { id: string } | { title: string };
export const endpoint = () => fetch(Utils.prepend(RouteStore.googlePhotosAccessToken)).then(async response => new Photos(await response.text()));
@@ -17,26 +50,96 @@ export namespace GooglePhotosClientUtils {
description: string;
}
- export const UploadMedia = async (sources: Doc[], album?: AlbumReference) => {
+ export const UploadImageDocuments = async (sources: Doc[], album?: AlbumReference, descriptionKey = "caption") => {
if (album && "title" in album) {
- album = (await endpoint()).albums.create(album.title);
+ album = await (await endpoint()).albums.create(album.title);
}
const media: MediaInput[] = [];
sources.forEach(document => {
const data = Cast(Doc.GetProto(document).data, ImageField);
- const description = StrCast(document.caption);
- if (!data) {
- return undefined;
- }
- media.push({
+ data && media.push({
url: data.url.href,
- description,
- } as MediaInput);
+ description: parseDescription(document, descriptionKey),
+ });
});
if (media.length) {
return PostToServer(RouteStore.googlePhotosMediaUpload, { media, album });
}
- return undefined;
+ };
+
+ const parseDescription = (document: Doc, descriptionKey: string) => {
+ let description: string = Utils.prepend("/doc/" + document[Id]);
+ const target = document[descriptionKey];
+ if (typeof target === "string") {
+ description = target;
+ } else if (target instanceof RichTextField) {
+ description = RichTextUtils.ToPlainText(EditorState.fromJSON(FormattedTextBox.Instance.config, JSON.parse(target.Data)));
+ }
+ return description;
+ };
+
+ export interface DateRange {
+ after: Date;
+ before: Date;
+ }
+ export interface SearchOptions {
+ pageSize: number;
+ included: ContentCategories[];
+ excluded: ContentCategories[];
+ date: Opt<Date | DateRange>;
+ includeArchivedMedia: boolean;
+ type: MediaType;
+ }
+
+ const DefaultSearchOptions: SearchOptions = {
+ pageSize: 20,
+ included: [],
+ excluded: [],
+ date: undefined,
+ includeArchivedMedia: true,
+ type: MediaType.ALL_MEDIA
+ };
+
+ export interface SearchResponse {
+ mediaItems: any[];
+ nextPageToken: string;
+ }
+
+ export const Search = async (requested: Opt<Partial<SearchOptions>>) => {
+ const options = requested || DefaultSearchOptions;
+ const photos = await endpoint();
+ const filters = new photos.Filters(options.includeArchivedMedia === undefined ? true : options.includeArchivedMedia);
+
+ const included = options.included || [];
+ const excluded = options.excluded || [];
+ const contentFilter = new photos.ContentFilter();
+ included.length && included.forEach(category => contentFilter.addIncludedContentCategories(category));
+ excluded.length && excluded.forEach(category => contentFilter.addExcludedContentCategories(category));
+ filters.setContentFilter(contentFilter);
+
+ const date = options.date;
+ if (date) {
+ const dateFilter = new photos.DateFilter();
+ if (date instanceof Date) {
+ dateFilter.addDate(date);
+ } else {
+ dateFilter.addRange(date.after, date.before);
+ }
+ filters.setDateFilter(dateFilter);
+ }
+
+ filters.setMediaTypeFilter(new photos.MediaTypeFilter(options.type || MediaType.ALL_MEDIA));
+
+ return new Promise<any>((resolve, reject) => {
+ photos.mediaItems.search(filters, options.pageSize || 20).then(async (response: SearchResponse) => {
+ if (!response) {
+ return reject();
+ }
+ let filenames = await PostToServer(RouteStore.googlePhotosMediaDownload, response);
+ console.log(filenames);
+ resolve(filenames);
+ });
+ });
};
} \ No newline at end of file
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index 7fe35494d..ee58c684a 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -131,6 +131,8 @@ export class MainView extends React.Component {
window.addEventListener("keydown", KeyManager.Instance.handle);
// this.executeGooglePhotosRoutine();
+ const imageTag = GooglePhotosClientUtils.ContentCategories;
+ GooglePhotosClientUtils.Search({ included: [imageTag.ANIMALS] });
reaction(() => {
let workspaces = CurrentUserUtils.UserDocument.workspaces;
@@ -155,7 +157,7 @@ export class MainView extends React.Component {
doc.caption = "Well isn't this a nice cat image!";
let photos = await GooglePhotosClientUtils.endpoint();
let albumId = (await photos.albums.list(50)).albums.filter((album: any) => album.title === "This is a generically created album!")[0].id;
- console.log(await GooglePhotosClientUtils.UploadMedia([doc], { id: albumId }));
+ console.log(await GooglePhotosClientUtils.UploadImageDocuments([doc], { id: albumId }));
}
componentWillUnMount() {
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index b8a034efc..4033ffd9c 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -591,7 +591,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
subitems.push({ description: "Open Fields", event: this.fieldsClicked, icon: "layer-group" });
cm.addItem({ description: "Open...", subitems: subitems, icon: "external-link-alt" });
if (Cast(this.props.Document.data, ImageField)) {
- cm.addItem({ description: "Export to Google Photos", event: () => GooglePhotosClientUtils.UploadMedia([this.props.Document]), icon: "caret-square-right" });
+ cm.addItem({ description: "Export to Google Photos", event: () => GooglePhotosClientUtils.UploadImageDocuments([this.props.Document]), icon: "caret-square-right" });
}
let existingMake = ContextMenu.Instance.findByDescription("Make...");
let makes: ContextMenuProps[] = existingMake && "subitems" in existingMake ? existingMake.subitems : [];
diff --git a/src/server/RouteStore.ts b/src/server/RouteStore.ts
index b221b71bc..f65e6134c 100644
--- a/src/server/RouteStore.ts
+++ b/src/server/RouteStore.ts
@@ -33,6 +33,7 @@ export enum RouteStore {
cognitiveServices = "/cognitiveservices",
googleDocs = "/googleDocs",
googlePhotosAccessToken = "/googlePhotosAccessToken",
- googlePhotosMediaUpload = "/googlePhotosMediaUpload"
+ googlePhotosMediaUpload = "/googlePhotosMediaUpload",
+ googlePhotosMediaDownload = "/googlePhotosMediaDownload"
} \ No newline at end of file
diff --git a/src/server/apis/google/GooglePhotosUploadUtils.ts b/src/server/apis/google/GooglePhotosUploadUtils.ts
index 13db1df03..032bc2a2d 100644
--- a/src/server/apis/google/GooglePhotosUploadUtils.ts
+++ b/src/server/apis/google/GooglePhotosUploadUtils.ts
@@ -20,6 +20,7 @@ export namespace GooglePhotosUploadUtils {
export interface DownloadInformation {
mediaPath: string;
+ fileName: string;
contentType?: string;
contentSize?: string;
}
@@ -77,15 +78,9 @@ export namespace GooglePhotosUploadUtils {
export namespace IOUtils {
- export const Download = async (url: string): Promise<Opt<DownloadInformation>> => {
- const filename = `temporary_upload_${Utils.GenerateGuid()}${path.extname(url).toLowerCase()}`;
- const temporaryDirectory = Paths.uploadDirectory + "temporary/";
- const mediaPath = temporaryDirectory + filename;
-
- if (!(await createIfNotExists(temporaryDirectory))) {
- return undefined;
- }
-
+ export const Download = async (url: string, filename?: string): Promise<Opt<DownloadInformation>> => {
+ const resolved = filename || `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) {
@@ -95,6 +90,7 @@ export namespace GooglePhotosUploadUtils {
mediaPath,
contentType: res.headers['content-type'],
contentSize: res.headers['content-length'],
+ fileName: resolved
};
request(url).pipe(fs.createWriteStream(mediaPath)).on('close', () => resolve(information));
});
diff --git a/src/server/credentials/google_docs_token.json b/src/server/credentials/google_docs_token.json
index e67c4b5ba..88838e18a 100644
--- a/src/server/credentials/google_docs_token.json
+++ b/src/server/credentials/google_docs_token.json
@@ -1 +1 @@
-{"access_token":"ya29.Glx8B81Wqa67aMtB6AwlIUcLO4k0bnsICbtkXJUkqXWPIZgnSw0SnCG0jiFAmwLGPg8ca-Qk3R0SqWt4JlgwfrzuOqt90I0P8tHH2x_4RXfgisVBg4Muf8Gz59AEkA","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":1567878663996} \ No newline at end of file
+{"access_token":"ya29.Glx8B266dydsOIEYhedUZYQ8sIsR9utSSxCBUex0O85zYrujZCSTbjVhrXF3Y4q41mLFghLwspgW-1w6zqnGnMtkZhuDGpBGArIwLZsJDyhUugEu3xvh7gY78WfePA","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":1567890805451} \ No newline at end of file
diff --git a/src/server/index.ts b/src/server/index.ts
index 99d8a02d4..aadadb11a 100644
--- a/src/server/index.ts
+++ b/src/server/index.ts
@@ -113,7 +113,7 @@ function addSecureRoute(method: Method,
) {
let abstracted = (req: express.Request, res: express.Response) => {
if (req.user) {
- handler(req.user as any, res, req);
+ handler(req.user, res, req);
} else {
req.session!.target = req.originalUrl;
onRejection(res, req);
@@ -848,6 +848,20 @@ app.post(RouteStore.googlePhotosMediaUpload, async (req, res) => {
);
});
+app.post(RouteStore.googlePhotosMediaDownload, async (req, res) => {
+ const contents = req.body;
+ if (!contents) {
+ return res.send(undefined);
+ }
+ await GooglePhotosUploadUtils.initialize({ uploadDirectory, credentialsPath, tokenPath });
+ let bundles: GooglePhotosUploadUtils.DownloadInformation[] = [];
+ await Promise.all(contents.mediaItems.forEach(async (item: any) => {
+ const information = await GooglePhotosUploadUtils.IOUtils.Download(item.baseUrl, item.filename);
+ information && bundles.push(information);
+ }));
+ res.send(bundles);
+});
+
const suffixMap: { [type: string]: (string | [string, string | ((json: any) => any)]) } = {
"number": "_n",
"string": "_t",