1
0
mirror of https://github.com/laurent22/joplin.git synced 2025-01-23 18:53:36 +02:00

Desktop: Made sync more reliable by making it skip items that time out, and improved sync status screen

This commit is contained in:
Laurent Cozic 2021-05-15 20:56:49 +02:00
parent 0b46880a00
commit 15fe119256
5 changed files with 107 additions and 19 deletions

View File

@ -87,12 +87,15 @@ function StatusScreen(props: Props) {
itemsHtml.push(renderSectionTitleHtml(section.title, section.title));
let currentListKey = '';
let listItems: any[] = [];
for (const n in section.body) {
if (!section.body.hasOwnProperty(n)) continue;
const item = section.body[n];
let text = '';
let retryLink = null;
let itemType = null;
if (typeof item === 'object') {
if (item.canRetry) {
const onClick = async () => {
@ -107,18 +110,40 @@ function StatusScreen(props: Props) {
);
}
text = item.text;
itemType = item.type;
} else {
text = item;
}
if (itemType === 'openList') {
currentListKey = item.key;
continue;
}
if (itemType === 'closeList') {
itemsHtml.push(<ul key={currentListKey}>{listItems}</ul>);
currentListKey = '';
listItems = [];
continue;
}
if (!text) text = '\xa0';
itemsHtml.push(
<div style={theme.textStyle} key={`item_${n}`}>
<span>{text}</span>
{retryLink}
</div>
);
if (currentListKey) {
listItems.push(
<li style={theme.textStyle} key={`item_${n}`}>
<span>{text}</span>
{retryLink}
</li>
);
} else {
itemsHtml.push(
<div style={theme.textStyle} key={`item_${n}`}>
<span>{text}</span>
{retryLink}
</div>
);
}
}
if (section.canRetryAll) {

View File

@ -28,6 +28,22 @@ interface RemoteItem {
type_?: number;
}
function isCannotSyncError(error: any): boolean {
if (!error) return false;
if (['rejectedByTarget', 'fileNotFound'].indexOf(error.code) >= 0) return true;
// If the request times out we give up too because sometimes it's due to the
// file being large or some other connection issues, and we don't want that
// file to block the sync process. The user can choose to retry later on.
//
// message: "network timeout at: .....
// name: "FetchError"
// type: "request-timeout"
if (error.type === 'request-timeout' || error.message.includes('network timeout')) return true;
return false;
}
export default class Synchronizer {
private db_: any;
@ -514,7 +530,7 @@ export default class Synchronizer {
await this.apiCall('put', remoteContentPath, null, { path: localResourceContentPath, source: 'file', shareId: local.share_id });
} catch (error) {
if (error && ['rejectedByTarget', 'fileNotFound'].indexOf(error.code) >= 0) {
if (isCannotSyncError(error)) {
await handleCannotSyncItem(ItemClass, syncTargetId, local, error.message);
action = null;
} else {

View File

@ -578,7 +578,7 @@ function localesFromLanguageCode(languageCode: string, locales: string[]): strin
});
}
function _(s: string, ...args: any[]) {
function _(s: string, ...args: any[]): string {
const strings = localeStrings(currentLocale_);
let result = strings[s];
if (result === '' || result === undefined) result = s;

View File

@ -756,6 +756,10 @@ export default class BaseItem extends BaseModel {
return this.db().transactionExecBatch(queries);
}
public static async saveSyncEnabled(itemType: ModelType, itemId: string) {
await this.db().exec('DELETE FROM sync_items WHERE item_type = ? AND item_id = ?', [itemType, itemId]);
}
// When an item is deleted, its associated sync_items data is not immediately deleted for
// performance reason. So this function is used to look for these remaining sync_items and
// delete them.

View File

@ -10,6 +10,36 @@ import Resource from '../models/Resource';
import { _ } from '../locale';
const { toTitleCase } = require('../string-utils.js');
enum CanRetryType {
E2EE = 'e2ee',
ResourceDownload = 'resourceDownload',
ItemSync = 'itemSync',
}
enum ReportItemType {
OpenList = 'openList',
CloseList = 'closeList',
}
type RerportItemOrString = ReportItem | string;
interface ReportSection {
title: string;
body: RerportItemOrString[];
name?: string;
canRetryAll?: boolean;
retryAllHandler?: ()=> void;
}
interface ReportItem {
type?: ReportItemType;
key?: string;
text?: string;
canRetry?: boolean;
canRetryType?: CanRetryType;
retryHandler?: ()=> void;
}
export default class ReportService {
csvEscapeCell(cell: string) {
cell = this.csvValueToString(cell);
@ -110,10 +140,10 @@ export default class ReportService {
return output;
}
async status(syncTarget: number) {
async status(syncTarget: number): Promise<ReportSection[]> {
const r = await this.syncStatus(syncTarget);
const sections = [];
let section: any = null;
const sections: ReportSection[] = [];
let section: ReportSection = null;
const disabledItems = await BaseItem.syncDisabledItems(syncTarget);
@ -122,17 +152,29 @@ export default class ReportService {
section.body.push(_('These items will remain on the device but will not be uploaded to the sync target. In order to find these items, either search for the title or the ID (which is displayed in brackets above).'));
section.body.push('');
section.body.push({ type: ReportItemType.OpenList, key: 'disabledSyncItems' });
for (let i = 0; i < disabledItems.length; i++) {
const row = disabledItems[i];
let msg: string = '';
if (row.location === BaseItem.SYNC_ITEM_LOCATION_LOCAL) {
section.body.push(_('%s (%s) could not be uploaded: %s', row.item.title, row.item.id, row.syncInfo.sync_disabled_reason));
msg = _('%s (%s) could not be uploaded: %s', row.item.title, row.item.id, row.syncInfo.sync_disabled_reason);
} else {
section.body.push(_('Item "%s" could not be downloaded: %s', row.syncInfo.item_id, row.syncInfo.sync_disabled_reason));
msg = _('Item "%s" could not be downloaded: %s', row.syncInfo.item_id, row.syncInfo.sync_disabled_reason);
}
section.body.push({
text: msg,
canRetry: true,
canRetryType: CanRetryType.ItemSync,
retryHandler: async () => {
await BaseItem.saveSyncEnabled(row.item.type_, row.item.id);
},
});
}
section.body.push({ type: ReportItemType.CloseList });
sections.push(section);
}
@ -150,7 +192,7 @@ export default class ReportService {
section.body.push({
text: _('%s: %s', toTitleCase(BaseModel.modelTypeToName(row.type_)), row.id),
canRetry: true,
canRetryType: 'e2ee',
canRetryType: CanRetryType.E2EE,
retryHandler: async () => {
await DecryptionWorker.instance().clearDisabledItem(row.type_, row.id);
void DecryptionWorker.instance().scheduleStart();
@ -158,11 +200,12 @@ export default class ReportService {
});
}
const retryHandlers: any[] = [];
const retryHandlers: Function[] = [];
for (let i = 0; i < section.body.length; i++) {
if (section.body[i].canRetry) {
retryHandlers.push(section.body[i].retryHandler);
const item: RerportItemOrString = section.body[i];
if (typeof item !== 'string' && item.canRetry) {
retryHandlers.push(item.retryHandler);
}
}
@ -210,7 +253,7 @@ export default class ReportService {
section.body.push({
text: _('%s (%s): %s', row.resource_title, row.resource_id, row.fetch_error),
canRetry: true,
canRetryType: 'resourceDownload',
canRetryType: CanRetryType.ResourceDownload,
retryHandler: async () => {
await Resource.resetErrorStatus(row.resource_id);
void ResourceFetcher.instance().autoAddResources();