Skip to content
Draft
46 changes: 46 additions & 0 deletions app/components/settings/sync/SyncListSettings.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,12 @@
import type WebdavDataSyncSettings__SvelteComponent_ from '~/components/settings/sync/webdav/WebdavDataSyncSettings.svelte';
import type WebdavImageSyncSettings__SvelteComponent_ from '~/components/settings/sync/webdav/WebdavImageSyncSettings.svelte';
import type WebdavPDFSyncSettings__SvelteComponent_ from '~/components/settings/sync/webdav/WebdavPDFSyncSettings.svelte';
import type PaperlessNgxPDFSyncSettings__SvelteComponent_ from '~/components/settings/sync/paperless/PaperlessNgxPDFSyncSettings.svelte';
import { BaseDataSyncServiceOptions } from '~/services/sync/BaseDataSyncService';
import { BaseSyncServiceOptions } from '~/services/sync/BaseSyncService';
import { GoogleDriveSyncOptions } from '~/services/sync/gdrive/GoogleDrive';
import { OneDriveSyncOptions } from '~/services/sync/onedrive/OneDrive';
import type { PaperlessNgxSyncOptions } from '~/services/sync/paperless/PaperlessNgx';
interface SettingsComponentReturnType {
[SyncTypes.folder_image]: typeof FolderImageSyncSettings__SvelteComponent_;
[SyncTypes.folder_pdf]: typeof FolderPDFSyncSettings__SvelteComponent_;
Expand All @@ -47,6 +49,7 @@
[SyncTypes.gdrive_data]: typeof GoogleDriveDataSyncSettings__SvelteComponent_;
[SyncTypes.gdrive_image]: typeof GoogleDriveImageSyncSettings__SvelteComponent_;
[SyncTypes.gdrive_pdf]: typeof GoogleDrivePDFSyncSettings__SvelteComponent_;
[SyncTypes.paperless_pdf]: typeof PaperlessNgxPDFSyncSettings__SvelteComponent_;
}
async function getSettingsComponent<T extends SyncTypes>(syncType: T): Promise<SettingsComponentReturnType[T]> {
switch (syncType) {
Expand All @@ -72,6 +75,8 @@
return (await import('~/components/settings/sync/gdrive/GoogleDriveImageSyncSettings.svelte')).default as any;
case 'gdrive_pdf':
return (await import('~/components/settings/sync/gdrive/GoogleDrivePDFSyncSettings.svelte')).default as any;
case 'paperless_pdf':
return (await import('~/components/settings/sync/paperless/PaperlessNgxPDFSyncSettings.svelte')).default as any;
}
}
</script>
Expand Down Expand Up @@ -188,6 +193,21 @@
}
break;
}
case SyncTypes.paperless_pdf: {
const type = item.type;
const page = await getSettingsComponent(type);
const result: BaseSyncServiceOptions & PaperlessNgxSyncOptions = await showModal({
page,
fullscreen: true,
props: {
data: item as BaseSyncServiceOptions & PaperlessNgxSyncOptions
}
});
if (result) {
configToUpdate = result;
}
break;
}
}
if (configToUpdate) {
syncService.updateService(configToUpdate);
Expand Down Expand Up @@ -311,6 +331,20 @@
}
break;
}
case SyncTypes.paperless_pdf: {
const page = await getSettingsComponent(SyncTypes.paperless_pdf);
const result: BaseSyncServiceOptions & PaperlessNgxSyncOptions = await showModal({
page,
fullscreen: true,
props: {
data
}
});
if (result) {
configToAdd = result;
}
break;
}
}
if (configToAdd) {
const data = syncService.addService(selection?.data, configToAdd);
Expand Down Expand Up @@ -343,6 +377,18 @@
case 'onedrive_pdf':
case 'onedrive_image':
return 'OneDrive';
case 'paperless_pdf': {
const serverUrl = (item as BaseSyncServiceOptions & PaperlessNgxSyncOptions).serverUrl;
if (serverUrl) {
try {
const url = serverUrl.startsWith('http') ? serverUrl : 'https://' + serverUrl;
return result + url.split('//')[1].split('/')[0];
} catch (e) {
return result + serverUrl;
}
}
return 'Paperless-ngx';
}
case 'webdav_data':
case 'webdav_pdf':
case 'webdav_image':
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<script lang="ts">
import { Template } from '@nativescript-community/svelte-native/components';
import { Writable } from 'svelte/store';
import PDFSyncSettings from '~/components/settings/sync/PDFSyncSettings.svelte';
import PaperlessNgxSettingsView from '~/components/settings/sync/paperless/PaperlessNgxSettingsView.svelte';
import { lc } from '~/helpers/locale';
import { PaperlessNgxPDFSyncServiceOptions } from '~/services/sync/paperless/PaperlessNgxPDFSyncService';

export let data: PaperlessNgxPDFSyncServiceOptions = {} as any;

let updateItem;
let store: Writable<PaperlessNgxPDFSyncServiceOptions>;

const topItems = [
{
type: 'header',
title: lc('paperless_config')
},
{
type: 'paperless'
}
];
let paperlessView: PaperlessNgxSettingsView;

async function validateSave() {
return paperlessView?.validateSave();
}
</script>

<PDFSyncSettings {data} serviceType="paperless_pdf" {topItems} {validateSave} bind:updateItem bind:store>
<Template key="paperless" let:item>
<PaperlessNgxSettingsView bind:this={paperlessView} {store} />
</Template>
</PDFSyncSettings>
137 changes: 137 additions & 0 deletions app/components/settings/sync/paperless/PaperlessNgxSettingsView.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
<script lang="ts">
import { SilentError } from '@akylas/nativescript-app-utils/error';
import { showError } from '@shared/utils/showError';
import { Writable, get } from 'svelte/store';
import { lc } from '~/helpers/locale';
import { acquireToken, testPaperlessConnection } from '~/services/sync/paperless/PaperlessNgx';
import type { PaperlessNgxSyncOptions } from '~/services/sync/paperless/PaperlessNgx';
import { colors } from '~/variables';

$: ({ colorError, colorOnError, colorSecondary } = $colors);

const variant = 'outline';
export let store: Writable<PaperlessNgxSyncOptions & { token?: string }>;
let testing = false;
let testConnectionSuccess = 0;

async function testConnection() {
try {
const data = get(store);
if (!data.serverUrl?.length) {
throw new SilentError(lc('missing_paperless_server_url'));
}
if (!data.token?.length && !(data.username?.length && data.password?.length)) {
throw new SilentError(lc('missing_paperless_credentials'));
}
testing = true;
testConnectionSuccess = 0;

// If only username/password provided, acquire a token first
if (!data.token && data.username && data.password) {
const acquiredToken = await acquireToken({ serverUrl: data.serverUrl }, data.username, data.password);
$store.token = acquiredToken;
// Clear password from store so it is not persisted; the token is used going forward
$store.password = '';
}

const result = await testPaperlessConnection(get(store));
testConnectionSuccess = result ? 1 : -1;
} catch (error) {
showError(error);
testConnectionSuccess = -1;
} finally {
testing = false;
}
}

export async function validateSave() {
const data = get(store);
if (!data.serverUrl?.length) {
return false;
}
// Must have a token OR credentials (we acquired a token already during test)
if (!data.token?.length && !(data.username?.length && data.password?.length)) {
return false;
}
if (testConnectionSuccess === 0) {
await testConnection();
}
return testConnectionSuccess > 0;
}
</script>

<stacklayout padding="4 10 4 10">
<textfield
autocapitalizationType="none"
hint={lc('server_address')}
keyboardType="url"
margin="5 0 5 0"
placeholder={lc('server_address') + ' https://...'}
returnKeyType="next"
text={$store.serverUrl}
{variant}
on:textChange={(e) => {
$store.serverUrl = e['value'];
testConnectionSuccess = 0;
}} />

<label fontSize={12} margin="2 0 6 2" opacity={0.7} text={lc('paperless_token_hint')} textWrap={true} />

<textfield
autocapitalizationType="none"
autocorrect={false}
hint={lc('api_token')}
margin="5 0 5 0"
placeholder={lc('api_token')}
returnKeyType="next"
text={$store.token}
{variant}
on:textChange={(e) => {
$store.token = e['value'];
testConnectionSuccess = 0;
}} />

<label fontSize={12} margin="6 0 2 2" opacity={0.7} text={lc('or')} />

<textfield
autocapitalizationType="none"
autocorrect={false}
hint={lc('username')}
margin="5 0 5 0"
placeholder={lc('username')}
returnKeyType="next"
text={$store.username}
{variant}
on:textChange={(e) => {
$store.username = e['value'];
testConnectionSuccess = 0;
}} />
<textfield
autocapitalizationType="none"
autocorrect={false}
hint={lc('password_not_saved')}
margin="5 0 5 0"
placeholder={lc('password')}
placeholderColor="gray"
returnKeyType="done"
secure={true}
text={$store.password}
{variant}
on:textChange={(e) => {
$store.password = e['value'];
testConnectionSuccess = 0;
}} />

<gridlayout columns="*,*" margin="5 0 0 0" rows="auto">
<gridlayout col={1} columns="auto" horizontalAlignment="right" rows="auto" verticalAlignment="middle">
<mdbutton
backgroundColor={testConnectionSuccess < 0 ? colorError : testConnectionSuccess > 0 ? 'lightgreen' : colorSecondary}
color={colorOnError}
text={testConnectionSuccess < 0 ? lc('failed') : testConnectionSuccess > 0 ? lc('successful') : lc('test')}
verticalAlignment="middle"
visibility={testing ? 'hidden' : 'visible'}
on:tap={testConnection} />
<activityindicator busy={testing} height={20} horizontalAlignment="center" verticalAlignment="middle" visibility={testing ? 'visible' : 'hidden'} />
</gridlayout>
</gridlayout>
</stacklayout>
6 changes: 6 additions & 0 deletions app/i18n/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -317,6 +317,12 @@
"page_margin": "page margin",
"page_padding": "margins",
"paper_size": "paper size",
"paperless_config": "Paperless-ngx configuration",
"paperless_token_hint": "Enter an API token (generated in your Paperless-ngx profile) or provide username/password to acquire one automatically",
"api_token": "API token",
"missing_paperless_server_url": "Server URL is required",
"missing_paperless_credentials": "An API token or username/password is required",
"or": "or",
"passbooks_saved": "Passbooks exported",
"passcode_creation": "Create a PIN code",
"password": "password",
Expand Down
2 changes: 2 additions & 0 deletions app/models/OCRDocument.ts
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,7 @@ export interface DocumentExtra {
color?: string;
[k: string]:
| string
| number
| boolean
| {
type: string;
Expand Down Expand Up @@ -542,6 +543,7 @@ export class OCRDocument extends Observable implements Document {
// // TODO: fix why do we need to clear the whole cache? wrong cache key?
// getImagePipeline().clearCaches();
// } else {
// eslint-disable-next-line @typescript-eslint/await-thenable
await getImagePipeline().evictFromCache(croppedImagePath);
// }
await this.updatePage(
Expand Down
4 changes: 2 additions & 2 deletions app/services/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -192,7 +192,7 @@ export async function request<T = any>(requestParams: HttpRequestOptions, retry
const requestStartTime = Date.now();
if (__DEV__) {
// DEV_LOG && console.log(requestParams.url, JSON.stringify(requestParams));
// logRequestAsCurl(requestParams.url, requestParams as any);
logRequestAsCurl(requestParams.url, requestParams as any);
}
try {
const response = await https.request<T>(requestParams);
Expand Down Expand Up @@ -239,7 +239,7 @@ export async function handleRequestResponseError<U = any>(response: https.HttpsR
// if (statusCode === 401 && jsonReturn.error === 'invalid_grant') {
// return this.handleRequestRetry(requestParams, retry);
// }
const error = jsonReturn.error_description || jsonReturn.error || jsonReturn;
const error = jsonReturn.error_description || jsonReturn.error || JSON.stringify(jsonReturn);
throw new HTTPError({
statusCode: error.code || statusCode,
message: error.error_description || error.form || error.message || error.error || error,
Expand Down
10 changes: 8 additions & 2 deletions app/services/sync.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,8 @@ export const SERVICES_SYNC_TITLES: { [key in SYNC_TYPES]: string } = {
gdrive_data: 'Google Drive',
onedrive_image: 'OneDrive',
onedrive_pdf: 'OneDrive',
onedrive_data: 'OneDrive'
onedrive_data: 'OneDrive',
paperless_pdf: 'Paperless-ngx'
};

export interface SyncStateEventData extends EventData {
Expand Down Expand Up @@ -173,6 +174,11 @@ export class SyncService extends BaseWorkerHandler<SyncWorker> {
sendImageEvent(event: DocumentPagesAddedEventData) {
DEV_LOG && console.log('Sync', 'sendImageEvent');
// only used for image sync
this.syncDocumentsInternal({ event, type: SyncType.IMAGE, fromEvent: event.eventName });
}
sendImagesEvent(event: DocumentPagesAddedEventData) {
DEV_LOG && console.log('Sync', 'sendImagesEvent');
// only used for image sync
this.syncDocumentsInternal({ event, type: SyncType.IMAGE | SyncType.PDF, fromEvent: event.eventName });
}
sendDataEvent(event: FolderUpdatedEventData) {
Expand Down Expand Up @@ -238,7 +244,7 @@ export class SyncService extends BaseWorkerHandler<SyncWorker> {
documentsService.on(EVENT_DOCUMENT_UPDATED, this.onDocumentUpdated, this);
documentsService.on(EVENT_DOCUMENT_DELETED, this.onDocumentDeleted, this);
documentsService.on(EVENT_DOCUMENT_PAGE_UPDATED, this.sendImageEvent, this);
documentsService.on(EVENT_DOCUMENT_PAGES_ADDED, this.sendImageEvent, this);
documentsService.on(EVENT_DOCUMENT_PAGES_ADDED, this.sendImagesEvent, this);
documentsService.on(EVENT_DOCUMENT_MOVED_FOLDER, this.sendDataEvent, this);
documentsService.on(EVENT_FOLDER_UPDATED, this.sendDataEvent, this);
documentsService.on(EVENT_FOLDER_ADDED, this.sendDataEvent, this);
Expand Down
Loading