fix: sync image - add mimeType, add manual sync in options-ui

This commit is contained in:
Michal Szczepanski 2023-09-25 23:41:14 +02:00
parent 2863a878e6
commit 50b27e731e
30 changed files with 392 additions and 190 deletions

@ -17,8 +17,6 @@
import { BrowserStorage } from '@pinmenote/browser-api'; import { BrowserStorage } from '@pinmenote/browser-api';
import { ICommand } from '../../../model/shared/common.dto'; import { ICommand } from '../../../model/shared/common.dto';
import { ObjectStoreKeys } from '../../../keys/object.store.keys'; import { ObjectStoreKeys } from '../../../keys/object.store.keys';
import { SegmentData } from '@pinmenote/page-compute';
import { fnConsoleLog } from '../../../fn/fn-console';
export class PageSegmentAddRefCommand implements ICommand<Promise<boolean>> { export class PageSegmentAddRefCommand implements ICommand<Promise<boolean>> {
constructor(private hash: string) {} constructor(private hash: string) {}

@ -33,7 +33,7 @@ export class PageSegmentAddCommand<T> implements ICommand<Promise<void>> {
const key = `${ObjectStoreKeys.CONTENT_HASH_COUNT}:${this.content.hash}`; const key = `${ObjectStoreKeys.CONTENT_HASH_COUNT}:${this.content.hash}`;
let count = (await BrowserStorage.get<number | undefined>(key)) || 0; let count = (await BrowserStorage.get<number | undefined>(key)) || 0;
count++; count++;
fnConsoleLog('PageSegmentAddCommand->incrementCount', count); // fnConsoleLog('PageSegmentAddCommand->incrementCount', count);
await BrowserStorage.set(key, count); await BrowserStorage.set(key, count);
} }
} }

@ -56,6 +56,8 @@ export enum BusMessageType {
CONTENT_TAKE_SCREENSHOT = 'content.take.screenshot', CONTENT_TAKE_SCREENSHOT = 'content.take.screenshot',
CONTENT_FETCH_PDF = 'content.fetch.pdf', CONTENT_FETCH_PDF = 'content.fetch.pdf',
CONTENT_THEME = 'content.theme', CONTENT_THEME = 'content.theme',
// Iframe
OPTIONS_SYNC_OUTGOING_OBJECT = 'options.sync.outgoing.object',
// Iframe content script // Iframe content script
IFRAME_INDEX = 'iframe.index', IFRAME_INDEX = 'iframe.index',
IFRAME_INDEX_REGISTER = 'iframe.index.register', IFRAME_INDEX_REGISTER = 'iframe.index.register',

@ -48,9 +48,11 @@ export const AccountDetailsComponent: FunctionComponent<Props> = (props) => {
if (PopupTokenStore.token) { if (PopupTokenStore.token) {
setTokenData(jwtDecode<TokenDataDto>(PopupTokenStore.token.access_token)); setTokenData(jwtDecode<TokenDataDto>(PopupTokenStore.token.access_token));
quotaKey = dispatcher.addListener<ServerQuotaResponse>( quotaKey = dispatcher.addListener<ServerQuotaResponse | ServerErrorDto>(
BusMessageType.POPUP_SERVER_QUOTA, BusMessageType.POPUP_SERVER_QUOTA,
(event, key, value) => { (event, key, value) => {
// TODO fix server error handling
if ('code' in value) return;
LogManager.log(`${event} - ${JSON.stringify(value)}`); LogManager.log(`${event} - ${JSON.stringify(value)}`);
setServerQuota(value); setServerQuota(value);
}, },

@ -0,0 +1,100 @@
/*
* This file is part of the pinmenote-extension distribution (https://github.com/pinmenote/pinmenote-extension).
* Copyright (c) 2023 Michal Szczepanski.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import React, { FunctionComponent, useEffect, useRef } from 'react';
import { BrowserApi } from '@pinmenote/browser-api';
import CircularProgress from '@mui/material/CircularProgress';
import IconButton from '@mui/material/IconButton';
import DownloadIcon from '@mui/icons-material/Download';
import ClearIcon from '@mui/icons-material/Clear';
import { ObjDto } from '../../../common/model/obj/obj.dto';
import { ObjPageDto } from '../../../common/model/obj/obj-page.dto';
import { PageSnapshotDto } from '../../../common/model/obj/page-snapshot.dto';
import dayjs from 'dayjs';
import { DATE_YEAR_SECOND } from '../../../common/date-format.constraints';
import CloudSyncIcon from '@mui/icons-material/CloudSync';
import { BusMessageType } from '../../../common/model/bus.model';
import { TinyDispatcher } from '@pinmenote/tiny-dispatcher';
import { SyncObjectStatus } from '../../../common/model/sync.model';
import { fnConsoleLog } from '../../../common/fn/fn-console';
export interface Props {
obj?: ObjDto<ObjPageDto>;
isLoading: boolean;
handleDownload: () => void;
handleClose: () => void;
}
export const HtmlPreviewHeaderComponent: FunctionComponent<Props> = (props) => {
const titleRef = useRef<HTMLHeadingElement>(null);
const urlRef = useRef<HTMLDivElement>(null);
useEffect(() => {
if (!titleRef.current) return;
if (!urlRef.current) return;
if (props.obj) renderHeader(props.obj);
const dispatcher = TinyDispatcher.getInstance();
const syncKey = dispatcher.addListener<SyncObjectStatus>(
BusMessageType.OPTIONS_SYNC_OUTGOING_OBJECT,
(event, key, value) => {
fnConsoleLog('HtmlPreviewHeaderComponent->SYNC !!!!!!!', event, key, value);
},
true
);
return () => {
dispatcher.removeListener(BusMessageType.OPTIONS_SYNC_OUTGOING_OBJECT, syncKey);
};
}, [props]);
const renderHeader = (obj: ObjDto<ObjPageDto>): void => {
const snapshot: PageSnapshotDto = obj.data.snapshot;
if (titleRef.current) {
titleRef.current.innerHTML = snapshot.info.title;
}
if (urlRef.current) {
urlRef.current.innerHTML = `
<a href="${snapshot.info.url.href}" target="_blank" style="word-break: break-all">
${snapshot.info.url.href}
</a><span style="margin-left: 10px;">Created At : ${dayjs(obj.createdAt).format(DATE_YEAR_SECOND)}</span>`;
}
};
const handleManualSync = async () => {
await BrowserApi.sendRuntimeMessage({ type: BusMessageType.OPTIONS_SYNC_OUTGOING_OBJECT, data: props.obj?.id });
};
return (
<div style={{ backgroundColor: '#ffffff', width: '100%', display: 'flex', justifyContent: 'space-between' }}>
<div style={{ marginLeft: '10px', marginBottom: '5px' }}>
<h2 style={{ marginTop: '5px', marginBottom: '5px' }} ref={titleRef}></h2>
<div ref={urlRef}></div>
</div>
<div style={{ display: 'flex', alignItems: 'center' }}>
<div style={{ display: props.isLoading ? 'flex' : 'none' }}>
<CircularProgress />
</div>
<IconButton onClick={handleManualSync}>
<CloudSyncIcon />
</IconButton>
<IconButton onClick={props.handleDownload}>
<DownloadIcon />
</IconButton>
<IconButton onClick={props.handleClose}>
<ClearIcon />
</IconButton>
</div>
</div>
);
};

@ -19,10 +19,6 @@ import React, { FunctionComponent, useEffect, useRef, useState } from 'react';
import { SegmentImg, SegmentPage, SegmentType } from '@pinmenote/page-compute'; import { SegmentImg, SegmentPage, SegmentType } from '@pinmenote/page-compute';
import { BrowserApi } from '@pinmenote/browser-api'; import { BrowserApi } from '@pinmenote/browser-api';
import CircularProgress from '@mui/material/CircularProgress'; import CircularProgress from '@mui/material/CircularProgress';
import ClearIcon from '@mui/icons-material/Clear';
import { DATE_YEAR_SECOND } from '../../../common/date-format.constraints';
import DownloadIcon from '@mui/icons-material/Download';
import IconButton from '@mui/material/IconButton';
import { IframeHtmlFactory } from '../../../common/factory/iframe-html.factory'; import { IframeHtmlFactory } from '../../../common/factory/iframe-html.factory';
import { LinkHrefStore } from '../../../common/store/link-href.store'; import { LinkHrefStore } from '../../../common/store/link-href.store';
import { ObjGetCommand } from '../../../common/command/obj/obj-get.command'; import { ObjGetCommand } from '../../../common/command/obj/obj-get.command';
@ -33,10 +29,10 @@ import { PageSnapshotDto } from '../../../common/model/obj/page-snapshot.dto';
import { PinComponent } from '../../../common/components/pin/pin.component'; import { PinComponent } from '../../../common/components/pin/pin.component';
import { SettingsStore } from '../../store/settings.store'; import { SettingsStore } from '../../store/settings.store';
import { XpathFactory } from '../../../common/factory/xpath.factory'; import { XpathFactory } from '../../../common/factory/xpath.factory';
import dayjs from 'dayjs';
import { fnConsoleLog } from '../../../common/fn/fn-console'; import { fnConsoleLog } from '../../../common/fn/fn-console';
import { fnSleep } from '../../../common/fn/fn-sleep'; import { fnSleep } from '../../../common/fn/fn-sleep';
import { fnUid } from '../../../common/fn/fn-uid'; import { fnUid } from '../../../common/fn/fn-uid';
import { HtmlPreviewHeaderComponent } from './html-preview-header.component';
interface Props { interface Props {
visible: boolean; visible: boolean;
@ -45,13 +41,11 @@ interface Props {
export const HtmlPreviewComponent: FunctionComponent<Props> = (props) => { export const HtmlPreviewComponent: FunctionComponent<Props> = (props) => {
const containerRef = useRef<HTMLDivElement>(null); const containerRef = useRef<HTMLDivElement>(null);
const htmlRef = useRef<HTMLDivElement>(null); const htmlRef = useRef<HTMLDivElement>(null);
const titleRef = useRef<HTMLHeadingElement>(null);
const urlRef = useRef<HTMLDivElement>(null);
const [visible, setVisible] = useState<boolean>(props.visible); const [visible, setVisible] = useState<boolean>(props.visible);
const [pageSegment, setPageSegment] = useState<SegmentPage | undefined>(); const [pageSegment, setPageSegment] = useState<SegmentPage | undefined>();
const [pageSnapshot, setPageSnapshot] = useState<PageSnapshotDto | undefined>(); const [objData, setObjData] = useState<ObjDto<ObjPageDto> | undefined>();
const [isLoading, setIsLoading] = useState<boolean>(false); const [isLoading, setIsLoading] = useState<boolean>(false);
const [isPreLoading, setIsPreLoading] = useState<boolean>(true); const [isPreLoading, setIsPreLoading] = useState<boolean>(true);
@ -62,21 +56,18 @@ export const HtmlPreviewComponent: FunctionComponent<Props> = (props) => {
if (!idhash[0].startsWith('#obj')) return; if (!idhash[0].startsWith('#obj')) return;
try { try {
fnConsoleLog('HtmlPreviewComponent->useEffect->render', props.visible, visible); fnConsoleLog('HtmlPreviewComponent->useEffect->render', props.visible, visible);
render(parseInt(idhash[1])); render(parseInt(idhash[1])).catch((e) => fnConsoleLog('HtmlPreviewComponent->useEffect->render->ERROR', e));
} catch (e) { } catch (e) {
fnConsoleLog('Error render or parseInt', e); fnConsoleLog('Error render or parseInt', e);
} }
} }
}); }, [props]);
const render = (id: number) => { const render = async (id: number) => {
if (titleRef.current) titleRef.current.innerHTML = '';
if (urlRef.current) urlRef.current.innerHTML = '';
setTimeout(async () => {
setIsPreLoading(true); setIsPreLoading(true);
setIsLoading(true); setIsLoading(true);
const obj = await new ObjGetCommand<ObjPageDto>(id).execute(); const obj = await new ObjGetCommand<ObjPageDto>(id).execute();
setPageSnapshot(obj.data.snapshot); setObjData(obj);
const pageSegment = await new PageSegmentGetCommand<SegmentPage>(obj.data.snapshot.segment).execute(); const pageSegment = await new PageSegmentGetCommand<SegmentPage>(obj.data.snapshot.segment).execute();
if (pageSegment) { if (pageSegment) {
setPageSegment(pageSegment.content); setPageSegment(pageSegment.content);
@ -92,7 +83,6 @@ export const HtmlPreviewComponent: FunctionComponent<Props> = (props) => {
const pinIds = await LinkHrefStore.pinIds(obj.data.snapshot.info.url.href); const pinIds = await LinkHrefStore.pinIds(obj.data.snapshot.info.url.href);
await renderPins(pinIds); await renderPins(pinIds);
} }
});
}; };
const renderPins = async (ids: number[]) => { const renderPins = async (ids: number[]) => {
@ -160,8 +150,6 @@ export const HtmlPreviewComponent: FunctionComponent<Props> = (props) => {
const renderCanvas = (obj: ObjDto<ObjPageDto>, content?: SegmentPage) => { const renderCanvas = (obj: ObjDto<ObjPageDto>, content?: SegmentPage) => {
const snapshot: PageSnapshotDto = obj.data.snapshot; const snapshot: PageSnapshotDto = obj.data.snapshot;
renderHeader(obj);
if (!htmlRef.current) return; if (!htmlRef.current) return;
if (!containerRef.current) return; if (!containerRef.current) return;
const iframe = document.createElement('iframe'); const iframe = document.createElement('iframe');
@ -187,7 +175,6 @@ export const HtmlPreviewComponent: FunctionComponent<Props> = (props) => {
const renderSnapshot = async (obj: ObjDto<ObjPageDto>, segment?: SegmentPage): Promise<void> => { const renderSnapshot = async (obj: ObjDto<ObjPageDto>, segment?: SegmentPage): Promise<void> => {
const snapshot: PageSnapshotDto = obj.data.snapshot; const snapshot: PageSnapshotDto = obj.data.snapshot;
renderHeader(obj);
if (!htmlRef.current) return; if (!htmlRef.current) return;
if (!containerRef.current) return; if (!containerRef.current) return;
if (!segment) return; if (!segment) return;
@ -210,19 +197,6 @@ export const HtmlPreviewComponent: FunctionComponent<Props> = (props) => {
setIsLoading(false); setIsLoading(false);
}; };
const renderHeader = (obj: ObjDto<ObjPageDto>): void => {
const snapshot: PageSnapshotDto = obj.data.snapshot;
if (titleRef.current) {
titleRef.current.innerHTML = snapshot.info.title;
}
if (urlRef.current) {
urlRef.current.innerHTML = `
<a href="${snapshot.info.url.href}" target="_blank" style="word-break: break-all">
${snapshot.info.url.href}
</a><span style="margin-left: 10px;">Created At : ${dayjs(obj.createdAt).format(DATE_YEAR_SECOND)}</span>`;
}
};
const writeDoc = async (doc: Document, html: string, assets: string[], cleanLoader = false): Promise<void> => { const writeDoc = async (doc: Document, html: string, assets: string[], cleanLoader = false): Promise<void> => {
doc.write(html); doc.write(html);
doc.close(); doc.close();
@ -310,7 +284,7 @@ export const HtmlPreviewComponent: FunctionComponent<Props> = (props) => {
}; };
const handleDownload = async () => { const handleDownload = async () => {
if (!pageSegment || !pageSnapshot) return; if (!pageSegment || !objData) return;
if (!htmlRef.current) return; if (!htmlRef.current) return;
const iframe = htmlRef.current.lastChild as HTMLIFrameElement; const iframe = htmlRef.current.lastChild as HTMLIFrameElement;
// TODO gather all iframe hashes and pass here with content // TODO gather all iframe hashes and pass here with content
@ -347,23 +321,12 @@ export const HtmlPreviewComponent: FunctionComponent<Props> = (props) => {
left: '0px' left: '0px'
}} }}
> >
<div style={{ backgroundColor: '#ffffff', width: '100%', display: 'flex', justifyContent: 'space-between' }}> <HtmlPreviewHeaderComponent
<div style={{ marginLeft: '10px', marginBottom: '5px' }}> obj={objData}
<h2 style={{ marginTop: '5px', marginBottom: '5px' }} ref={titleRef}></h2> handleDownload={handleDownload}
<div ref={urlRef}></div> handleClose={handleClose}
</div> isLoading={isLoading}
<div style={{ display: 'flex', alignItems: 'center' }}> />
<div style={{ display: isLoading ? 'flex' : 'none' }}>
<CircularProgress />
</div>
<IconButton onClick={handleDownload}>
<DownloadIcon />
</IconButton>
<IconButton onClick={handleClose}>
<ClearIcon />
</IconButton>
</div>
</div>
<div style={{ width: '100%', height: '100%', backgroundColor: '#ffffff' }} ref={htmlRef}> <div style={{ width: '100%', height: '100%', backgroundColor: '#ffffff' }} ref={htmlRef}>
<div <div
style={{ style={{

@ -36,7 +36,6 @@ export class ApiStoreCommitCommand extends ApiCallBase implements ICommand<Promi
}, },
this.refreshParams() this.refreshParams()
); );
fnConsoleLog('ApiStoreCommitCommand->response', resp);
return resp.ok; return resp.ok;
} catch (e) { } catch (e) {
fnConsoleLog('ApiStoreBeginCommand->Error', e); fnConsoleLog('ApiStoreBeginCommand->Error', e);

@ -58,8 +58,9 @@ export enum SyncHashType {
} }
export interface SegmentSingleHash { export interface SegmentSingleHash {
hash: string;
type: SyncHashType; type: SyncHashType;
hash: string;
mimeType?: string;
} }
export interface SegmentHashListResponse { export interface SegmentHashListResponse {

@ -16,27 +16,36 @@
*/ */
import { ApiCallBase } from '../../api-call.base'; import { ApiCallBase } from '../../api-call.base';
import { FetchService } from '@pinmenote/fetch-service'; import { FetchService } from '@pinmenote/fetch-service';
import { ICommand } from '../../../../../common/model/shared/common.dto'; import { ICommand, ServerErrorDto } from '../../../../../common/model/shared/common.dto';
import { fnConsoleLog } from '../../../../../common/fn/fn-console'; import { fnConsoleLog } from '../../../../../common/fn/fn-console';
import { ObjChangesResponse } from '../api-store.model'; import { ObjChangesResponse } from '../api-store.model';
import { ApiErrorCode } from '../../../../../common/model/shared/api.error-code';
export class ApiObjGetChangesCommand extends ApiCallBase implements ICommand<Promise<ObjChangesResponse | undefined>> { const errorResponse: ServerErrorDto = { code: ApiErrorCode.INTERNAL, message: 'Send request problem' };
constructor(private serverId?: number) {
export class ApiObjGetChangesCommand
extends ApiCallBase
implements ICommand<Promise<ObjChangesResponse | ServerErrorDto>>
{
constructor(private serverId: number) {
super(); super();
} }
async execute(): Promise<ObjChangesResponse | undefined> { async execute(): Promise<ObjChangesResponse | ServerErrorDto> {
await this.initTokenData(); await this.initTokenData();
if (!this.storeUrl) return; if (!this.storeUrl) return errorResponse;
try { try {
const resp = await FetchService.fetch<ObjChangesResponse>( const resp = await FetchService.fetch<ObjChangesResponse | ServerErrorDto>(
`${this.storeUrl}/api/v1/obj/changes?serverId=${this.serverId || 0}`, `${this.storeUrl}/api/v1/obj/changes?serverId=${this.serverId}`,
{ headers: this.getAuthHeaders() }, { headers: this.getAuthHeaders() },
this.refreshParams() this.refreshParams()
); );
fnConsoleLog('ApiStoreChangesCommand->response', resp); if (resp.status === 200) return resp.data;
return resp.data; fnConsoleLog(resp);
errorResponse.message = (resp.data as ServerErrorDto).message;
return errorResponse;
} catch (e) { } catch (e) {
fnConsoleLog('ApiStoreChangesCommand->Error', e); fnConsoleLog('ApiObjGetChangesCommand->Error', e);
} }
return errorResponse;
} }
} }

@ -36,15 +36,16 @@ export class ApiSegmentAddCommand extends ApiCallBase implements ICommand<Promis
async execute(): Promise<boolean> { async execute(): Promise<boolean> {
await this.initTokenData(); await this.initTokenData();
if (!this.storeUrl) return false; if (!this.storeUrl) return false;
if (await this.hasSegment()) return true; // if (await this.hasSegment(this.storeUrl)) return true;
return await this.addSegment(); return await this.addSegment(this.storeUrl);
} }
async hasSegment(): Promise<boolean> { async hasSegment(storeUrl: string): Promise<boolean> {
if (!this.storeUrl) return false;
const authHeaders = this.getAuthHeaders(); const authHeaders = this.getAuthHeaders();
const params = this.data.parent ? `?parent=${this.data.parent}` : ''; const params = this.data.parent ? `?parent=${this.data.parent}` : '';
const resp = await FetchService.fetch<BeginTxResponse>( const resp = await FetchService.fetch<BeginTxResponse>(
`${this.storeUrl!}/api/v1/segment/has/${this.tx.tx}/${this.data.hash}${params}`, `${storeUrl}/api/v1/segment/has/${this.tx.tx}/${this.data.hash}${params}`,
{ {
type: 'TEXT', type: 'TEXT',
headers: { headers: {
@ -56,22 +57,26 @@ export class ApiSegmentAddCommand extends ApiCallBase implements ICommand<Promis
return resp.status === 200; return resp.status === 200;
} }
async addSegment(): Promise<boolean> { async addSegment(storeUrl: string): Promise<boolean> {
const formData = new FormData(); const formData = new FormData();
if (this.data.type.toString() === SyncHashType.Img) { if (this.data.type.toString() === SyncHashType.Img) {
if (this.file === 'data:') { if (this.file === 'data:') {
fnConsoleLog('AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA', this.file); fnConsoleLog('ApiSegmentAddCommand->addSegment->EMPTY', this.file);
formData.append('file', new Blob([this.file], { type: 'image/svg+xml' })); formData.append('file', new Blob([this.file], { type: 'image/svg+xml' }));
} else { } else {
try { try {
formData.append('file', fnB64toBlob(this.file)); const blob = fnB64toBlob(this.file);
formData.append('mimeType', blob.type);
formData.append('file', blob);
} catch (e) { } catch (e) {
console.log(this.file, this.data, e); fnConsoleLog(this.file, this.data, e);
throw new Error('aaaaaaaaaaaaaaaaaaaaa'); throw new Error('ApiSegmentAddCommand->addSegment->Error->Img->base64');
} }
} }
} else if (this.data.type.toString() === SyncHashType.ObjPdf) { } else if (this.data.type.toString() === SyncHashType.ObjPdf) {
formData.append('file', fnB64toBlob(this.file)); const blob = fnB64toBlob(this.file);
formData.append('mimeType', blob.type);
formData.append('file', blob);
} else { } else {
const fileData = deflate(this.file); const fileData = deflate(this.file);
formData.append('file', new Blob([fileData], { type: 'application/zip' })); formData.append('file', new Blob([fileData], { type: 'application/zip' }));
@ -85,7 +90,7 @@ export class ApiSegmentAddCommand extends ApiCallBase implements ICommand<Promis
const authHeaders = this.getAuthHeaders(false); const authHeaders = this.getAuthHeaders(false);
const resp = await FetchService.fetch( const resp = await FetchService.fetch(
`${this.storeUrl!}/api/v1/segment/add/${this.tx.tx}`, `${storeUrl}/api/v1/segment/add/${this.tx.tx}`,
{ {
headers: { headers: {
...authHeaders ...authHeaders

@ -20,15 +20,16 @@ import { ICommand } from '../../../../../common/model/shared/common.dto';
import { fnConsoleLog } from '../../../../../common/fn/fn-console'; import { fnConsoleLog } from '../../../../../common/fn/fn-console';
export class ApiSegmentGetCommand extends ApiCallBase implements ICommand<Promise<Blob | undefined>> { export class ApiSegmentGetCommand extends ApiCallBase implements ICommand<Promise<Blob | undefined>> {
constructor(private hash: string) { constructor(private hash: string, private mimeType?: string) {
super(); super();
} }
async execute(): Promise<Blob | undefined> { async execute(): Promise<Blob | undefined> {
await this.initTokenData(); await this.initTokenData();
if (!this.storeUrl) return; if (!this.storeUrl) return;
try { try {
const mimeType = this.mimeType ? `?mimeType=${encodeURI(this.mimeType)}` : '';
const resp = await FetchService.fetch<Blob>( const resp = await FetchService.fetch<Blob>(
`${this.storeUrl}/api/v1/segment/${this.hash}`, `${this.storeUrl}/api/v1/segment/${this.hash}${mimeType}`,
{ headers: this.getAuthHeaders(), type: 'BLOB' }, { headers: this.getAuthHeaders(), type: 'BLOB' },
this.refreshParams() this.refreshParams()
); );

@ -16,17 +16,24 @@
*/ */
import { ApiCallBase } from '../../api-call.base'; import { ApiCallBase } from '../../api-call.base';
import { FetchService } from '@pinmenote/fetch-service'; import { FetchService } from '@pinmenote/fetch-service';
import { ICommand } from '../../../../../common/model/shared/common.dto'; import { ICommand, ServerErrorDto } from '../../../../../common/model/shared/common.dto';
import { ServerQuotaResponse } from '../../../../../common/model/sync-server.model'; import { ServerQuotaResponse } from '../../../../../common/model/sync-server.model';
import { ApiErrorCode } from '../../../../../common/model/shared/api.error-code';
import { fnConsoleLog } from '../../../../../common/fn/fn-console';
export class ApiSegmentQuotaGetCommand extends ApiCallBase implements ICommand<Promise<ServerQuotaResponse>> { export class ApiSegmentQuotaGetCommand
extends ApiCallBase
implements ICommand<Promise<ServerQuotaResponse | ServerErrorDto>>
{
constructor() { constructor() {
super(); super();
} }
async execute(): Promise<ServerQuotaResponse> { async execute(): Promise<ServerQuotaResponse | ServerErrorDto> {
await this.initTokenData(); await this.initTokenData();
if (!this.storeUrl) return { code: ApiErrorCode.INTERNAL, message: 'ApiSegmentQuotaGetCommand' };
try {
const resp = await FetchService.fetch<ServerQuotaResponse>( const resp = await FetchService.fetch<ServerQuotaResponse>(
`${this.storeUrl!}/api/v1/segment/quota`, `${this.storeUrl}/api/v1/segment/quota`,
{ {
type: 'JSON', type: 'JSON',
headers: this.getAuthHeaders(true) headers: this.getAuthHeaders(true)
@ -34,5 +41,9 @@ export class ApiSegmentQuotaGetCommand extends ApiCallBase implements ICommand<P
this.refreshParams() this.refreshParams()
); );
return resp.data; return resp.data;
} catch (e) {
fnConsoleLog('ApiSegmentQuotaGetCommand', e);
}
return { code: ApiErrorCode.INTERNAL, message: 'ApiSegmentQuotaGetCommand' };
} }
} }

@ -14,14 +14,18 @@
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
import { ApiSegmentQuotaGetCommand, ServerQuotaResponse } from '../api/store/segment/api-segment-quota-get.command'; import { ApiSegmentQuotaGetCommand } from '../api/store/segment/api-segment-quota-get.command';
import { BrowserApi } from '@pinmenote/browser-api'; import { BrowserApi } from '@pinmenote/browser-api';
import { BusMessageType } from '../../../common/model/bus.model'; import { BusMessageType } from '../../../common/model/bus.model';
import { ICommand } from '../../../common/model/shared/common.dto'; import { ICommand, ServerErrorDto } from '../../../common/model/shared/common.dto';
import { ServerQuotaResponse } from '../../../common/model/sync-server.model';
export class PopupServerQuotaCommand implements ICommand<Promise<void>> { export class PopupServerQuotaCommand implements ICommand<Promise<void>> {
async execute(): Promise<void> { async execute(): Promise<void> {
const data = await new ApiSegmentQuotaGetCommand().execute(); const data = await new ApiSegmentQuotaGetCommand().execute();
await BrowserApi.sendRuntimeMessage<ServerQuotaResponse>({ type: BusMessageType.POPUP_SERVER_QUOTA, data }); await BrowserApi.sendRuntimeMessage<ServerQuotaResponse | ServerErrorDto>({
type: BusMessageType.POPUP_SERVER_QUOTA,
data
});
} }
} }

@ -50,7 +50,7 @@ export class SyncSnapshotIncomingCommand implements ICommand<Promise<boolean>> {
if (!hashList) return false; if (!hashList) return false;
const pageSnapshot = await this.getSnapshot(hashList); const pageSnapshot = await this.getSnapshot(hashList);
if (!pageSnapshot) { if (!pageSnapshot) {
fnConsoleLog('SyncSnapshotIncomingCommand->execute PROBLEM !!!!!!!!!!!!!!!!!!!', this.change); fnConsoleLog('SyncSnapshotIncomingCommand->execute PROBLEM !!!!!!!!!!!!!!!!!!!', this.change, hashList);
return false; return false;
} }
// sleep 1s for now // sleep 1s for now
@ -108,7 +108,7 @@ export class SyncSnapshotIncomingCommand implements ICommand<Promise<boolean>> {
let info: PageSnapshotInfoDto | undefined; let info: PageSnapshotInfoDto | undefined;
let segment: string | undefined = undefined; let segment: string | undefined = undefined;
for (const child of hashList.children) { for (const child of hashList.children) {
const segmentData = await new ApiSegmentGetCommand(child.hash).execute(); const segmentData = await new ApiSegmentGetCommand(child.hash, child.mimeType).execute();
if (!segmentData) continue; if (!segmentData) continue;
switch (child.type.toString()) { switch (child.type.toString()) {
case SyncHashType.Img: { case SyncHashType.Img: {
@ -117,10 +117,13 @@ export class SyncSnapshotIncomingCommand implements ICommand<Promise<boolean>> {
let src = 'data:'; let src = 'data:';
try { try {
src = await UrlFactory.toDataUri(segmentData); src = await UrlFactory.toDataUri(segmentData);
// @vane WORKAROUND FIX convert image/svg -> image/svg+xml to render correctly inside <img> tag
const check = 'data:image/svg';
if (src.startsWith(check)) src = 'data:image/svg+xml' + src.substring(check.length);
} catch (e) { } catch (e) {
const buffer = await segmentData.arrayBuffer(); const buffer = await segmentData.arrayBuffer();
const textData = new TextDecoder().decode(buffer); const textData = new TextDecoder().decode(buffer);
console.log('ERROR !!!!!!!!! AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAa !!!!!!!!!!!!!!!!!!!!!!!!!!!', textData); fnConsoleLog('SyncSnapshotIncomingCommand->getSnapshot->Img->Error !!!', textData);
} }
const content: SegmentImg = { src }; const content: SegmentImg = { src };
await new PageSegmentAddCommand({ type: SegmentType.IMG, hash: child.hash, content }).execute(); await new PageSegmentAddCommand({ type: SegmentType.IMG, hash: child.hash, content }).execute();
@ -162,7 +165,6 @@ export class SyncSnapshotIncomingCommand implements ICommand<Promise<boolean>> {
segment = child.hash; segment = child.hash;
const content = await this.getString(segmentData); const content = await this.getString(segmentData);
const html: SegmentHtml = JSON.parse(content); const html: SegmentHtml = JSON.parse(content);
fnConsoleLog('SyncSnapshotIncomingCommand->getSnapshot->PageSnapshotFirstHash', html, child.hash);
await new PageSegmentAddCommand({ await new PageSegmentAddCommand({
type: SegmentType.SNAPSHOT, type: SegmentType.SNAPSHOT,
content: html, content: html,

@ -0,0 +1,93 @@
/*
* This file is part of the pinmenote-extension distribution (https://github.com/pinmenote/pinmenote-extension).
* Copyright (c) 2023 Michal Szczepanski.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import { BrowserApi } from '@pinmenote/browser-api';
import { ICommand } from '../../../../common/model/shared/common.dto';
import { ObjGetCommand } from '../../../../common/command/obj/obj-get.command';
import { fnConsoleLog } from '../../../../common/fn/fn-console';
import { BusMessageType } from '../../../../common/model/bus.model';
import { ObjDto, ObjRemovedDto, ObjTypeDto } from '../../../../common/model/obj/obj.dto';
import { SyncSnapshotCommand } from '../outgoing/sync-snapshot.command';
import { ObjPageDto } from '../../../../common/model/obj/obj-page.dto';
import { SyncPinCommand } from '../outgoing/sync-pin.command';
import { ObjPinDto } from '../../../../common/model/obj/obj-pin.dto';
import { SyncPdfCommand } from '../outgoing/sync-pdf.command';
import { ObjPdfDto } from '../../../../common/model/obj/obj-pdf.dto';
import { SyncNoteCommand } from '../outgoing/sync-note.command';
import { ObjNoteDto, ObjPageNoteDto } from '../../../../common/model/obj/obj-note.dto';
import { SyncPageNoteCommand } from '../outgoing/sync-page-note.command';
import { SyncRemovedCommand } from '../outgoing/sync-removed.command';
import { SyncTxHelper } from '../sync-tx.helper';
import { BeginTxResponse } from '../../api/store/api-store.model';
import { SyncObjectStatus } from '../../../../common/model/sync.model';
export class SyncManualOutgoingCommand implements ICommand<Promise<void>> {
constructor(private id: number) {}
async execute(): Promise<void> {
fnConsoleLog('SyncManualOutgoingCommand !!!!!!!!!!');
const obj = await new ObjGetCommand(this.id).execute();
if (!obj) {
fnConsoleLog('SyncObjectCommand->syncObject EMPTY', this.id, obj);
await this.sendResponse(SyncObjectStatus.OBJECT_NOT_EXISTS);
return;
}
const tx = await SyncTxHelper.begin();
if (!tx) {
fnConsoleLog('SyncManualOutgoingCommand !!!!!!!!!! TX', tx);
await this.sendResponse(SyncObjectStatus.TX_LOCKED);
return;
}
const status = await this.syncObj(obj, tx);
await this.sendResponse(status);
fnConsoleLog('SyncManualOutgoingCommand->execute id', obj.id, 'serverId', obj.server?.id);
await SyncTxHelper.commit();
}
private sendResponse = async (status: SyncObjectStatus) => {
await BrowserApi.sendRuntimeMessage<SyncObjectStatus>({
type: BusMessageType.OPTIONS_SYNC_OUTGOING_OBJECT,
data: status
});
};
private syncObj = async (obj: ObjDto, tx: BeginTxResponse) => {
switch (obj.type) {
case ObjTypeDto.PageSnapshot:
case ObjTypeDto.PageElementSnapshot: {
return await new SyncSnapshotCommand(obj as ObjDto<ObjPageDto>, tx).execute();
}
case ObjTypeDto.PageElementPin: {
return await new SyncPinCommand(obj as ObjDto<ObjPinDto>, tx).execute();
}
case ObjTypeDto.Pdf: {
return await new SyncPdfCommand(obj as ObjDto<ObjPdfDto>, tx).execute();
}
case ObjTypeDto.Note: {
return await new SyncNoteCommand(obj as ObjDto<ObjNoteDto>, tx).execute();
}
case ObjTypeDto.PageNote: {
return await new SyncPageNoteCommand(obj as ObjDto<ObjPageNoteDto>, tx).execute();
}
case ObjTypeDto.Removed: {
return await new SyncRemovedCommand(obj as ObjDto<ObjRemovedDto>, tx).execute();
}
default: {
fnConsoleLog('SyncObjectCommand->PROBLEM', obj, 'index', this.id);
return SyncObjectStatus.SERVER_ERROR;
}
}
};
}

@ -18,9 +18,9 @@ import { ICommand } from '../../../../common/model/shared/common.dto';
import { ObjDto } from '../../../../common/model/obj/obj.dto'; import { ObjDto } from '../../../../common/model/obj/obj.dto';
import { ObjNoteDto } from '../../../../common/model/obj/obj-note.dto'; import { ObjNoteDto } from '../../../../common/model/obj/obj-note.dto';
import { SyncObjectCommand } from './sync-object.command'; import { SyncObjectCommand } from './sync-object.command';
import { SyncObjectStatus } from '../sync.model';
import { fnConsoleLog } from '../../../../common/fn/fn-console'; import { fnConsoleLog } from '../../../../common/fn/fn-console';
import { BeginTxResponse } from '../../api/store/api-store.model'; import { BeginTxResponse } from '../../api/store/api-store.model';
import { SyncObjectStatus } from '../../../../common/model/sync.model';
export class SyncNoteCommand implements ICommand<Promise<SyncObjectStatus>> { export class SyncNoteCommand implements ICommand<Promise<SyncObjectStatus>> {
constructor(private obj: ObjDto<ObjNoteDto>, private tx: BeginTxResponse) {} constructor(private obj: ObjDto<ObjNoteDto>, private tx: BeginTxResponse) {}

@ -36,7 +36,7 @@ export class SyncObjectCommand implements ICommand<Promise<void>> {
} else if ('code' in resp && resp.code === ApiErrorCode.SYNC_DUPLICATED_HASH) { } else if ('code' in resp && resp.code === ApiErrorCode.SYNC_DUPLICATED_HASH) {
return await this.setByHash(); return await this.setByHash();
} }
fnConsoleLog('SyncObjectCommand', 'tx', this.tx, 'resp', resp); fnConsoleLog('SyncObjectCommand', 'tx', this.tx, 'resp', resp, 'obj', this.obj);
throw new Error('PROBLEM !!!!!!!!!!!!!!!'); throw new Error('PROBLEM !!!!!!!!!!!!!!!');
} }

@ -19,7 +19,7 @@ import { ICommand } from '../../../../common/model/shared/common.dto';
import { ObjDto } from '../../../../common/model/obj/obj.dto'; import { ObjDto } from '../../../../common/model/obj/obj.dto';
import { ObjPageNoteDto } from '../../../../common/model/obj/obj-note.dto'; import { ObjPageNoteDto } from '../../../../common/model/obj/obj-note.dto';
import { SyncObjectCommand } from './sync-object.command'; import { SyncObjectCommand } from './sync-object.command';
import { SyncObjectStatus } from '../sync.model'; import { SyncObjectStatus } from '../../../../common/model/sync.model';
import { fnConsoleLog } from '../../../../common/fn/fn-console'; import { fnConsoleLog } from '../../../../common/fn/fn-console';
import { BeginTxResponse, SyncHashType } from '../../api/store/api-store.model'; import { BeginTxResponse, SyncHashType } from '../../api/store/api-store.model';
import { ObjectStoreKeys } from '../../../../common/keys/object.store.keys'; import { ObjectStoreKeys } from '../../../../common/keys/object.store.keys';
@ -41,10 +41,9 @@ export class SyncPageNoteCommand implements ICommand<Promise<SyncObjectStatus>>
private async syncNote(data: ObjPageNoteDto): Promise<void> { private async syncNote(data: ObjPageNoteDto): Promise<void> {
const content = JSON.stringify(data); const content = JSON.stringify(data);
await new ApiSegmentAddCommand(this.tx, content, { await new ApiSegmentAddCommand(this.tx, content, {
hash: data.hash, key: await SyncCryptoFactory.newKey(),
parent: data.hash,
type: SyncHashType.ObjPdfDataDto, type: SyncHashType.ObjPdfDataDto,
key: await SyncCryptoFactory.newKey() hash: data.hash
}).execute(); }).execute();
if (data.prev) { if (data.prev) {
const prevData = await BrowserStorage.get<ObjPageNoteDto>(`${ObjectStoreKeys.NOTE_HASH}:${data.prev}`); const prevData = await BrowserStorage.get<ObjPageNoteDto>(`${ObjectStoreKeys.NOTE_HASH}:${data.prev}`);

@ -18,7 +18,7 @@ import { ICommand } from '../../../../common/model/shared/common.dto';
import { ObjDto } from '../../../../common/model/obj/obj.dto'; import { ObjDto } from '../../../../common/model/obj/obj.dto';
import { ObjPdfDataDto, ObjPdfDto } from '../../../../common/model/obj/obj-pdf.dto'; import { ObjPdfDataDto, ObjPdfDto } from '../../../../common/model/obj/obj-pdf.dto';
import { SyncObjectCommand } from './sync-object.command'; import { SyncObjectCommand } from './sync-object.command';
import { SyncObjectStatus } from '../sync.model'; import { SyncObjectStatus } from '../../../../common/model/sync.model';
import { BeginTxResponse, SyncHashType } from '../../api/store/api-store.model'; import { BeginTxResponse, SyncHashType } from '../../api/store/api-store.model';
import { ObjectStoreKeys } from '../../../../common/keys/object.store.keys'; import { ObjectStoreKeys } from '../../../../common/keys/object.store.keys';
import { BrowserStorage } from '@pinmenote/browser-api'; import { BrowserStorage } from '@pinmenote/browser-api';
@ -40,21 +40,20 @@ export class SyncPdfCommand implements ICommand<Promise<SyncObjectStatus>> {
private async syncData(data: ObjPdfDataDto, parent: string): Promise<void> { private async syncData(data: ObjPdfDataDto, parent: string): Promise<void> {
const content = JSON.stringify(data); const content = JSON.stringify(data);
await new ApiSegmentAddCommand(this.tx, content, { await new ApiSegmentAddCommand(this.tx, content, {
hash: data.hash, key: await SyncCryptoFactory.newKey(),
parent,
type: SyncHashType.ObjPdfDataDto, type: SyncHashType.ObjPdfDataDto,
key: await SyncCryptoFactory.newKey() hash: data.hash,
parent
}).execute(); }).execute();
} }
private async syncPdf(parent: string): Promise<void> { private async syncPdf(hash: string): Promise<void> {
const pdfData = await BrowserStorage.get<string | undefined>(`${ObjectStoreKeys.PDF_DATA}:${parent}`); const pdfData = await BrowserStorage.get<string | undefined>(`${ObjectStoreKeys.PDF_DATA}:${hash}`);
if (!pdfData) return; if (!pdfData) return;
await new ApiSegmentAddCommand(this.tx, pdfData, { await new ApiSegmentAddCommand(this.tx, pdfData, {
hash: parent, key: await SyncCryptoFactory.newKey(),
parent,
type: SyncHashType.ObjPdf, type: SyncHashType.ObjPdf,
key: await SyncCryptoFactory.newKey() hash
}).execute(); }).execute();
} }
} }

@ -18,7 +18,7 @@ import { ICommand } from '../../../../common/model/shared/common.dto';
import { ObjDto } from '../../../../common/model/obj/obj.dto'; import { ObjDto } from '../../../../common/model/obj/obj.dto';
import { ObjPinDataDto, ObjPinDescription, ObjPinDto } from '../../../../common/model/obj/obj-pin.dto'; import { ObjPinDataDto, ObjPinDescription, ObjPinDto } from '../../../../common/model/obj/obj-pin.dto';
import { SyncObjectCommand } from './sync-object.command'; import { SyncObjectCommand } from './sync-object.command';
import { SyncObjectStatus } from '../sync.model'; import { SyncObjectStatus } from '../../../../common/model/sync.model';
import { BeginTxResponse, SyncHashType } from '../../api/store/api-store.model'; import { BeginTxResponse, SyncHashType } from '../../api/store/api-store.model';
import { ApiSegmentAddCommand } from '../../api/store/segment/api-segment-add.command'; import { ApiSegmentAddCommand } from '../../api/store/segment/api-segment-add.command';
import { PinGetCommentCommand } from '../../../../common/command/pin/comment/pin-get-comment.command'; import { PinGetCommentCommand } from '../../../../common/command/pin/comment/pin-get-comment.command';
@ -45,10 +45,10 @@ export class SyncPinCommand implements ICommand<Promise<SyncObjectStatus>> {
if (!data) return; if (!data) return;
const content = JSON.stringify(data); const content = JSON.stringify(data);
await new ApiSegmentAddCommand(this.tx, content, { await new ApiSegmentAddCommand(this.tx, content, {
hash: data.hash, key: await SyncCryptoFactory.newKey(),
parent,
type: SyncHashType.ObjVideoDataDto, type: SyncHashType.ObjVideoDataDto,
key: await SyncCryptoFactory.newKey() hash: data.hash,
parent
}).execute(); }).execute();
}; };
@ -57,10 +57,10 @@ export class SyncPinCommand implements ICommand<Promise<SyncObjectStatus>> {
// TODO SYNC DRAW LIKE COMMENTS // TODO SYNC DRAW LIKE COMMENTS
const content = JSON.stringify(draw); const content = JSON.stringify(draw);
await new ApiSegmentAddCommand(this.tx, content, { await new ApiSegmentAddCommand(this.tx, content, {
hash: draw.hash, key: await SyncCryptoFactory.newKey(),
parent,
type: SyncHashType.ObjDrawDto, type: SyncHashType.ObjDrawDto,
key: await SyncCryptoFactory.newKey() hash: draw.hash,
parent
}).execute(); }).execute();
} }
}; };
@ -71,10 +71,10 @@ export class SyncPinCommand implements ICommand<Promise<SyncObjectStatus>> {
if (!comment) continue; if (!comment) continue;
const content = JSON.stringify(comment); const content = JSON.stringify(comment);
await new ApiSegmentAddCommand(this.tx, content, { await new ApiSegmentAddCommand(this.tx, content, {
hash: comment.hash, key: await SyncCryptoFactory.newKey(),
parent,
type: SyncHashType.ObjCommentDto, type: SyncHashType.ObjCommentDto,
key: await SyncCryptoFactory.newKey() hash: comment.hash,
parent
}).execute(); }).execute();
} }
}; };
@ -82,20 +82,19 @@ export class SyncPinCommand implements ICommand<Promise<SyncObjectStatus>> {
private syncPinData = async (data: ObjPinDataDto) => { private syncPinData = async (data: ObjPinDataDto) => {
const content = JSON.stringify(data); const content = JSON.stringify(data);
await new ApiSegmentAddCommand(this.tx, content, { await new ApiSegmentAddCommand(this.tx, content, {
hash: data.hash, key: await SyncCryptoFactory.newKey(),
parent: data.hash,
type: SyncHashType.ObjPinDataDto, type: SyncHashType.ObjPinDataDto,
key: await SyncCryptoFactory.newKey() hash: data.hash
}).execute(); }).execute();
}; };
private syncPinDescription = async (data: ObjPinDescription, parent: string) => { private syncPinDescription = async (data: ObjPinDescription, parent: string) => {
const content = JSON.stringify(data); const content = JSON.stringify(data);
await new ApiSegmentAddCommand(this.tx, content, { await new ApiSegmentAddCommand(this.tx, content, {
hash: data.hash, key: await SyncCryptoFactory.newKey(),
parent,
type: SyncHashType.ObjPinDescription, type: SyncHashType.ObjPinDescription,
key: await SyncCryptoFactory.newKey() hash: data.hash,
parent
}).execute(); }).execute();
}; };
} }

@ -16,7 +16,7 @@
*/ */
import { ObjDto, ObjRemovedDto } from '../../../../common/model/obj/obj.dto'; import { ObjDto, ObjRemovedDto } from '../../../../common/model/obj/obj.dto';
import { ICommand } from '../../../../common/model/shared/common.dto'; import { ICommand } from '../../../../common/model/shared/common.dto';
import { SyncObjectStatus } from '../sync.model'; import { SyncObjectStatus } from '../../../../common/model/sync.model';
import { fnConsoleLog } from '../../../../common/fn/fn-console'; import { fnConsoleLog } from '../../../../common/fn/fn-console';
import { BeginTxResponse } from '../../api/store/api-store.model'; import { BeginTxResponse } from '../../api/store/api-store.model';

@ -20,7 +20,7 @@ import { ObjDto } from '../../../../common/model/obj/obj.dto';
import { ObjPageDto } from '../../../../common/model/obj/obj-page.dto'; import { ObjPageDto } from '../../../../common/model/obj/obj-page.dto';
import { PageSegmentGetCommand } from '../../../../common/command/snapshot/segment/page-segment-get.command'; import { PageSegmentGetCommand } from '../../../../common/command/snapshot/segment/page-segment-get.command';
import { SyncObjectCommand } from './sync-object.command'; import { SyncObjectCommand } from './sync-object.command';
import { SyncObjectStatus } from '../sync.model'; import { SyncObjectStatus } from '../../../../common/model/sync.model';
import { fnConsoleLog } from '../../../../common/fn/fn-console'; import { fnConsoleLog } from '../../../../common/fn/fn-console';
import { BeginTxResponse, SyncHashType } from '../../api/store/api-store.model'; import { BeginTxResponse, SyncHashType } from '../../api/store/api-store.model';
import { PageSnapshotDto } from '../../../../common/model/obj/page-snapshot.dto'; import { PageSnapshotDto } from '../../../../common/model/obj/page-snapshot.dto';
@ -55,10 +55,10 @@ export class SyncSnapshotCommand implements ICommand<Promise<SyncObjectStatus>>
const content = this.getSegmentContent(segment); const content = this.getSegmentContent(segment);
if (!content) return; if (!content) return;
await new ApiSegmentAddCommand(this.tx, content, { await new ApiSegmentAddCommand(this.tx, content, {
hash: segment.hash, key: await SyncCryptoFactory.newKey(),
parent,
type: SyncHashType.PageSnapshotFirstHash, type: SyncHashType.PageSnapshotFirstHash,
key: await SyncCryptoFactory.newKey() hash: segment.hash,
parent
}).execute(); }).execute();
await this.syncSegmentSnapshot(segment.content as SegmentPage, parent); await this.syncSegmentSnapshot(segment.content as SegmentPage, parent);
}; };
@ -66,17 +66,17 @@ export class SyncSnapshotCommand implements ICommand<Promise<SyncObjectStatus>>
private async syncSnapshot(snapshot: PageSnapshotDto, parent: string): Promise<void> { private async syncSnapshot(snapshot: PageSnapshotDto, parent: string): Promise<void> {
// snapshot->info // snapshot->info
await new ApiSegmentAddCommand(this.tx, JSON.stringify(snapshot.info), { await new ApiSegmentAddCommand(this.tx, JSON.stringify(snapshot.info), {
hash: snapshot.info.hash, key: await SyncCryptoFactory.newKey(),
parent,
type: SyncHashType.PageSnapshotInfoDto, type: SyncHashType.PageSnapshotInfoDto,
key: await SyncCryptoFactory.newKey() hash: snapshot.info.hash,
parent
}).execute(); }).execute();
// snapshot->data // snapshot->data
await new ApiSegmentAddCommand(this.tx, JSON.stringify(snapshot.data), { await new ApiSegmentAddCommand(this.tx, JSON.stringify(snapshot.data), {
hash: snapshot.data.hash, key: await SyncCryptoFactory.newKey(),
parent,
type: SyncHashType.PageSnapshotDataDto, type: SyncHashType.PageSnapshotDataDto,
key: await SyncCryptoFactory.newKey() hash: snapshot.data.hash,
parent
}).execute(); }).execute();
} }
// eslint-disable-next-line @typescript-eslint/require-await // eslint-disable-next-line @typescript-eslint/require-await
@ -91,10 +91,10 @@ export class SyncSnapshotCommand implements ICommand<Promise<SyncObjectStatus>>
if (!content) return; if (!content) return;
await new ApiSegmentAddCommand(this.tx, content, { await new ApiSegmentAddCommand(this.tx, content, {
hash, key: await SyncCryptoFactory.newKey(),
parent,
type: this.convertSegmentTypeSyncHashType(segment.type), type: this.convertSegmentTypeSyncHashType(segment.type),
key: await SyncCryptoFactory.newKey() hash,
parent
}).execute(); }).execute();
switch (segment.type) { switch (segment.type) {

@ -19,7 +19,7 @@ import { ICommand } from '../../../../common/model/shared/common.dto';
import { ObjDto } from '../../../../common/model/obj/obj.dto'; import { ObjDto } from '../../../../common/model/obj/obj.dto';
import { ObjGetCommand } from '../../../../common/command/obj/obj-get.command'; import { ObjGetCommand } from '../../../../common/command/obj/obj-get.command';
import { ObjectStoreKeys } from '../../../../common/keys/object.store.keys'; import { ObjectStoreKeys } from '../../../../common/keys/object.store.keys';
import { SyncProgress } from '../sync.model'; import { SyncProgress } from '../../../../common/model/sync.model';
export class SyncGetProgressCommand implements ICommand<Promise<SyncProgress>> { export class SyncGetProgressCommand implements ICommand<Promise<SyncProgress>> {
async execute(): Promise<SyncProgress> { async execute(): Promise<SyncProgress> {

@ -18,13 +18,14 @@ import { BrowserStorage } from '@pinmenote/browser-api';
import { ICommand } from '../../../../common/model/shared/common.dto'; import { ICommand } from '../../../../common/model/shared/common.dto';
import { ObjectStoreKeys } from '../../../../common/keys/object.store.keys'; import { ObjectStoreKeys } from '../../../../common/keys/object.store.keys';
import { SyncGetProgressCommand } from './sync-get-progress.command'; import { SyncGetProgressCommand } from './sync-get-progress.command';
import { SyncProgress } from '../sync.model';
import { ObjDto } from '../../../../common/model/obj/obj.dto'; import { ObjDto } from '../../../../common/model/obj/obj.dto';
import { fnDateKeyFormat } from '../../../../common/fn/fn-date-format'; import { fnDateKeyFormat } from '../../../../common/fn/fn-date-format';
import { ObjDateIndex } from '../../../../common/command/obj/index/obj-update-index-add.command'; import { ObjDateIndex } from '../../../../common/command/obj/index/obj-update-index-add.command';
import { fnConsoleLog } from '../../../../common/fn/fn-console'; import { fnConsoleLog } from '../../../../common/fn/fn-console';
import { SyncProgress } from '../../../../common/model/sync.model';
export class SyncResetProgressCommand implements ICommand<Promise<void>> { export class SyncResetProgressCommand implements ICommand<Promise<void>> {
constructor(private refreshUpdateList = false) {}
async execute(): Promise<void> { async execute(): Promise<void> {
const obj = await SyncGetProgressCommand.getFirstObject(); const obj = await SyncGetProgressCommand.getFirstObject();
const timestamp = obj?.createdAt || -1; const timestamp = obj?.createdAt || -1;
@ -58,17 +59,20 @@ export class SyncResetProgressCommand implements ICommand<Promise<void>> {
const yearMonth = fnDateKeyFormat(new Date(obj.updatedAt)); const yearMonth = fnDateKeyFormat(new Date(obj.updatedAt));
toSortSet.add(yearMonth); toSortSet.add(yearMonth);
const updateList = await this.getList(yearMonth); if (this.refreshUpdateList) await this.updateList(yearMonth, obj);
if (updateList.findIndex((o) => o.id === obj.id) > 0) continue;
updateList.push({ id: obj.id, dt: obj.updatedAt });
await this.setList(yearMonth, updateList);
} }
listId -= 1; listId -= 1;
fnConsoleLog('SyncResetProgressCommand', listId);
} }
// sort objects inside // sort objects inside
if (this.refreshUpdateList) await this.sortUpdateList(toSortSet);
// clear tx
await BrowserStorage.remove(ObjectStoreKeys.SYNC_TX);
fnConsoleLog('SyncResetProgressCommand->complete in ', Date.now() - a);
}
private sortUpdateList = async (toSortSet: Set<string>) => {
for (const yearMonth of toSortSet) { for (const yearMonth of toSortSet) {
const list = await this.getList(yearMonth); const list = await this.getList(yearMonth);
const s = new Set<number>(); const s = new Set<number>();
@ -88,11 +92,16 @@ export class SyncResetProgressCommand implements ICommand<Promise<void>> {
}); });
await this.setList(yearMonth, newList); await this.setList(yearMonth, newList);
} }
};
// clear tx private updateList = async (yearMonth: string, obj: ObjDto) => {
await BrowserStorage.remove(ObjectStoreKeys.SYNC_TX); const updateList = await this.getList(yearMonth);
fnConsoleLog('SyncResetProgressCommand->complete in ', Date.now() - a); if (updateList.findIndex((o) => o.id === obj.id) > 0) return;
}
updateList.push({ id: obj.id, dt: obj.updatedAt });
await this.setList(yearMonth, updateList);
};
private async setList(yearMonth: string, list: ObjDateIndex[]): Promise<void> { private async setList(yearMonth: string, list: ObjDateIndex[]): Promise<void> {
const updateListKey = `${ObjectStoreKeys.UPDATED_DT}:${yearMonth}`; const updateListKey = `${ObjectStoreKeys.UPDATED_DT}:${yearMonth}`;

@ -17,7 +17,7 @@
import { BrowserStorage } from '@pinmenote/browser-api'; import { BrowserStorage } from '@pinmenote/browser-api';
import { ICommand } from '../../../../common/model/shared/common.dto'; import { ICommand } from '../../../../common/model/shared/common.dto';
import { ObjectStoreKeys } from '../../../../common/keys/object.store.keys'; import { ObjectStoreKeys } from '../../../../common/keys/object.store.keys';
import { SyncProgress } from '../sync.model'; import { SyncProgress } from '../../../../common/model/sync.model';
export class SyncSetProgressCommand implements ICommand<Promise<void>> { export class SyncSetProgressCommand implements ICommand<Promise<void>> {
constructor(protected progress: SyncProgress) {} constructor(protected progress: SyncProgress) {}

@ -25,15 +25,14 @@ import { SyncNoteCommand } from './outgoing/sync-note.command';
import { SyncPageNoteCommand } from './outgoing/sync-page-note.command'; import { SyncPageNoteCommand } from './outgoing/sync-page-note.command';
import { SyncPdfCommand } from './outgoing/sync-pdf.command'; import { SyncPdfCommand } from './outgoing/sync-pdf.command';
import { SyncPinCommand } from './outgoing/sync-pin.command'; import { SyncPinCommand } from './outgoing/sync-pin.command';
import { SyncObjectStatus, SyncProgress } from './sync.model'; import { SyncObjectStatus } from '../../../common/model/sync.model';
import { SyncRemovedCommand } from './outgoing/sync-removed.command'; import { SyncRemovedCommand } from './outgoing/sync-removed.command';
import { SyncSnapshotCommand } from './outgoing/sync-snapshot.command'; import { SyncSnapshotCommand } from './outgoing/sync-snapshot.command';
import { fnConsoleLog } from '../../../common/fn/fn-console'; import { fnConsoleLog } from '../../../common/fn/fn-console';
import { BeginTxResponse } from '../api/store/api-store.model'; import { BeginTxResponse } from '../api/store/api-store.model';
import { fnSleep } from '../../../common/fn/fn-sleep';
export class SyncIndexCommand implements ICommand<Promise<SyncObjectStatus>> { export class SyncIndexCommand implements ICommand<Promise<SyncObjectStatus>> {
constructor(private progress: SyncProgress, private tx: BeginTxResponse, private id: number) {} constructor(private tx: BeginTxResponse, private id: number) {}
async execute(): Promise<SyncObjectStatus> { async execute(): Promise<SyncObjectStatus> {
const obj = await new ObjGetCommand(this.id).execute(); const obj = await new ObjGetCommand(this.id).execute();

@ -17,7 +17,7 @@
import { SyncIndexCommand } from './sync-index.command'; import { SyncIndexCommand } from './sync-index.command';
import { ICommand } from '../../../common/model/shared/common.dto'; import { ICommand } from '../../../common/model/shared/common.dto';
import { ObjectStoreKeys } from '../../../common/keys/object.store.keys'; import { ObjectStoreKeys } from '../../../common/keys/object.store.keys';
import { SyncObjectStatus, SyncProgress } from './sync.model'; import { SyncObjectStatus, SyncProgress } from '../../../common/model/sync.model';
import { SyncTxHelper } from './sync-tx.helper'; import { SyncTxHelper } from './sync-tx.helper';
import { fnConsoleLog } from '../../../common/fn/fn-console'; import { fnConsoleLog } from '../../../common/fn/fn-console';
import { ObjDateIndex } from '../../../common/command/obj/index/obj-update-index-add.command'; import { ObjDateIndex } from '../../../common/command/obj/index/obj-update-index-add.command';
@ -56,7 +56,7 @@ export class SyncMonthCommand implements ICommand<Promise<SyncObjectStatus>> {
let i = start; let i = start;
let status = SyncObjectStatus.OK; let status = SyncObjectStatus.OK;
for (i; i < indexList.length; i++) { for (i; i < indexList.length; i++) {
status = await new SyncIndexCommand(this.progress, begin, indexList[i].id).execute(); status = await new SyncIndexCommand(begin, indexList[i].id).execute();
switch (status) { switch (status) {
case SyncObjectStatus.SERVER_ERROR: { case SyncObjectStatus.SERVER_ERROR: {
fnConsoleLog('SERVER_ERROR !!!!!!!!!!!!!!!!!!!'); fnConsoleLog('SERVER_ERROR !!!!!!!!!!!!!!!!!!!');

@ -18,7 +18,7 @@ import { fnDateToMonthFirstDay, fnMonthLastDay } from '../../../common/fn/fn-dat
import { ICommand } from '../../../common/model/shared/common.dto'; import { ICommand } from '../../../common/model/shared/common.dto';
import { SyncGetProgressCommand } from './progress/sync-get-progress.command'; import { SyncGetProgressCommand } from './progress/sync-get-progress.command';
import { SyncMonthCommand } from './sync-month.command'; import { SyncMonthCommand } from './sync-month.command';
import { SyncProgress } from './sync.model'; import { SyncProgress } from '../../../common/model/sync.model';
import { SyncTxHelper } from './sync-tx.helper'; import { SyncTxHelper } from './sync-tx.helper';
import { fnConsoleLog } from '../../../common/fn/fn-console'; import { fnConsoleLog } from '../../../common/fn/fn-console';
import { fnDateKeyFormat } from '../../../common/fn/fn-date-format'; import { fnDateKeyFormat } from '../../../common/fn/fn-date-format';
@ -34,16 +34,13 @@ export class SyncServerCommand implements ICommand<Promise<void>> {
if (SyncServerCommand.isInSync) return; if (SyncServerCommand.isInSync) return;
if (!(await SyncTxHelper.shouldSync())) return; if (!(await SyncTxHelper.shouldSync())) return;
try { try {
// await new SyncResetProgressCommand().execute(); await new SyncResetProgressCommand(false).execute();
/*const token = await new TokenStorageGetCommand().execute();
if (token) console.log(jwtDecode<TokenDataDto>(token.access_token));*/
SyncServerCommand.isInSync = true; SyncServerCommand.isInSync = true;
const a = Date.now(); const a = Date.now();
const progress = await new SyncGetProgressCommand().execute(); const progress = await new SyncGetProgressCommand().execute();
await this.syncOutgoing(progress); // await this.syncOutgoing(progress);
await this.syncIncoming(progress); await this.syncIncoming(progress);
fnConsoleLog('SyncServerCommand->execute', progress, 'in', Date.now() - a); fnConsoleLog('SyncServerCommand->execute', progress, 'in', Date.now() - a);
@ -53,9 +50,9 @@ export class SyncServerCommand implements ICommand<Promise<void>> {
} }
private async syncIncoming(progress: SyncProgress): Promise<void> { private async syncIncoming(progress: SyncProgress): Promise<void> {
fnConsoleLog('SyncServerCommand->syncIncoming');
const changesResp = await new ApiObjGetChangesCommand(progress.serverId).execute(); const changesResp = await new ApiObjGetChangesCommand(progress.serverId).execute();
if (!changesResp) return; fnConsoleLog('SyncServerCommand->syncIncoming', changesResp);
if ('code' in changesResp) return;
for (let i = 0; i < changesResp.data.length; i++) { for (let i = 0; i < changesResp.data.length; i++) {
const change = changesResp.data[i]; const change = changesResp.data[i];
if (progress.serverId > change.serverId) continue; if (progress.serverId > change.serverId) continue;
@ -69,9 +66,11 @@ export class SyncServerCommand implements ICommand<Promise<void>> {
return; return;
} }
} }
fnConsoleLog('SyncServerCommand->syncIncoming->COMPLETE !!!');
} }
private async syncOutgoing(progress: SyncProgress): Promise<void> { private async syncOutgoing(progress: SyncProgress): Promise<void> {
fnConsoleLog('SyncServerCommand->syncOutgoing');
// Empty list - fresh install // Empty list - fresh install
if (progress.id == -1) return; if (progress.id == -1) return;
const dt = fnDateToMonthFirstDay(new Date(progress.timestamp)); const dt = fnDateToMonthFirstDay(new Date(progress.timestamp));
@ -88,5 +87,6 @@ export class SyncServerCommand implements ICommand<Promise<void>> {
dt.setMonth(dt.getMonth() + 1); dt.setMonth(dt.getMonth() + 1);
} }
fnConsoleLog('SyncServerCommand->syncOutgoing->COMPLETE !!!');
} }
} }

@ -37,6 +37,7 @@ import { SwInitSettingsCommand } from './command/sw/sw-init-settings.command';
import { SyncServerCommand } from './command/sync/sync-server.command'; import { SyncServerCommand } from './command/sync/sync-server.command';
import { TaskExecutor } from './task/task.executor'; import { TaskExecutor } from './task/task.executor';
import { fnConsoleLog } from '../common/fn/fn-console'; import { fnConsoleLog } from '../common/fn/fn-console';
import { SyncManualOutgoingCommand } from './command/sync/manual/sync-manual-outgoing.command';
const handleMessage = async ( const handleMessage = async (
msg: BusMessage<any>, msg: BusMessage<any>,
@ -93,6 +94,10 @@ const handleMessage = async (
case BusMessageType.POPUP_SERVER_QUOTA: case BusMessageType.POPUP_SERVER_QUOTA:
await new PopupServerQuotaCommand().execute(); await new PopupServerQuotaCommand().execute();
break; break;
case BusMessageType.OPTIONS_SYNC_OUTGOING_OBJECT: {
await new SyncManualOutgoingCommand(msg.data).execute();
break;
}
case BusMessageType.IFRAME_INDEX: case BusMessageType.IFRAME_INDEX:
case BusMessageType.IFRAME_INDEX_REGISTER: case BusMessageType.IFRAME_INDEX_REGISTER:
case BusMessageType.IFRAME_START_LISTENERS: case BusMessageType.IFRAME_START_LISTENERS:
@ -109,9 +114,11 @@ const handleMessage = async (
} }
// Sync command // Sync command
if ( if (
![PageComputeMessage.CONTENT_FETCH_CSS, PageComputeMessage.CONTENT_FETCH_IMAGE].includes( ![
msg.type as PageComputeMessage PageComputeMessage.CONTENT_FETCH_CSS,
) PageComputeMessage.CONTENT_FETCH_IMAGE,
BusMessageType.OPTIONS_SYNC_OUTGOING_OBJECT
].includes(msg.type as any)
) { ) {
await new SyncServerCommand().execute(); await new SyncServerCommand().execute();
} }