1
0
mirror of https://github.com/laurent22/joplin.git synced 2025-02-01 19:15:01 +02:00

Desktop, Mobile: Resolves #9263: Do not allow switching the sync target if not all resources are downloaded

This commit is contained in:
Laurent Cozic 2024-01-27 18:20:51 +00:00
parent 8abd9b401b
commit 07b4117aa1
6 changed files with 85 additions and 11 deletions

View File

@ -738,6 +738,7 @@ packages/lib/models/Tag.test.js
packages/lib/models/Tag.js
packages/lib/models/dateTimeFormats.test.js
packages/lib/models/settings/FileHandler.js
packages/lib/models/settings/settingValidations.js
packages/lib/models/utils/isItemId.js
packages/lib/models/utils/itemCanBeEncrypted.js
packages/lib/models/utils/paginatedFeed.js

1
.gitignore vendored
View File

@ -718,6 +718,7 @@ packages/lib/models/Tag.test.js
packages/lib/models/Tag.js
packages/lib/models/dateTimeFormats.test.js
packages/lib/models/settings/FileHandler.js
packages/lib/models/settings/settingValidations.js
packages/lib/models/utils/isItemId.js
packages/lib/models/utils/itemCanBeEncrypted.js
packages/lib/models/utils/paginatedFeed.js

View File

@ -68,7 +68,7 @@ class ConfigScreenComponent extends React.Component<any, any> {
public componentDidMount() {
if (this.props.defaultSection) {
this.setState({ selectedSectionName: this.props.defaultSection }, () => {
this.switchSection(this.props.defaultSection);
void this.switchSection(this.props.defaultSection);
});
}
}
@ -112,7 +112,7 @@ class ConfigScreenComponent extends React.Component<any, any> {
throw new Error(`Invalid screen name: ${screenName}`);
}
public switchSection(name: string) {
public async switchSection(name: string) {
const section = this.sectionByName(name);
let screenName = '';
if (section.isScreen) {
@ -120,7 +120,9 @@ class ConfigScreenComponent extends React.Component<any, any> {
if (this.hasChanges()) {
const ok = confirm(_('This will open a new screen. Save your current changes?'));
if (ok) shared.saveSettings(this);
if (ok) {
await shared.saveSettings(this);
}
}
}
@ -128,7 +130,7 @@ class ConfigScreenComponent extends React.Component<any, any> {
}
private sidebar_selectionChange(event: any) {
this.switchSection(event.section.name);
void this.switchSection(event.section.name);
}
public renderSectionDescription(section: any) {
@ -655,12 +657,15 @@ class ConfigScreenComponent extends React.Component<any, any> {
}
public async onApplyClick() {
shared.saveSettings(this);
const done = await shared.saveSettings(this);
if (!done) return;
await this.checkNeedRestart();
}
public async onSaveClick() {
shared.saveSettings(this);
const done = await shared.saveSettings(this);
if (!done) return;
await this.checkNeedRestart();
this.props.dispatch({ type: 'NAV_BACK' });
}

View File

@ -109,7 +109,8 @@ class ConfigScreenComponent extends BaseScreenComponent<ConfigScreenProps, Confi
// changedSettingKeys is cleared in shared.saveSettings so reading it now
const shouldSetIgnoreTlsErrors = this.state.changedSettingKeys.includes('net.ignoreTlsErrors');
await shared.saveSettings(this);
const done = await shared.saveSettings(this);
if (!done) return;
if (shouldSetIgnoreTlsErrors) {
await setIgnoreTlsErrors(Setting.value('net.ignoreTlsErrors'));

View File

@ -7,6 +7,7 @@ import Logger from '@joplin/utils/Logger';
import { type ReactNode } from 'react';
import { type Registry } from '../../../registry';
import settingValidations from '../../../models/settings/settingValidations';
const logger = Logger.create('config-shared');
@ -78,7 +79,7 @@ export const checkSyncConfig = async (comp: ConfigScreenComponent, settings: any
if (result.ok) {
// Users often expect config to be auto-saved at this point, if the config check was successful
saveSettings(comp);
await saveSettings(comp);
}
return result;
};
@ -132,15 +133,21 @@ let scheduleSaveSettingsIID: ReturnType<typeof setTimeout>|null = null;
export const scheduleSaveSettings = (comp: ConfigScreenComponent) => {
if (scheduleSaveSettingsIID) clearTimeout(scheduleSaveSettingsIID);
scheduleSaveSettingsIID = setTimeout(() => {
scheduleSaveSettingsIID = setTimeout(async () => {
scheduleSaveSettingsIID = null;
saveSettings(comp);
await saveSettings(comp);
}, 100);
};
export const saveSettings = (comp: ConfigScreenComponent) => {
export const saveSettings = async (comp: ConfigScreenComponent) => {
const savedSettingKeys = comp.state.changedSettingKeys.slice();
const validationMessage = await settingValidations(savedSettingKeys, comp.state.settings);
if (validationMessage) {
alert(validationMessage);
return false;
}
for (const key in comp.state.settings) {
if (!comp.state.settings.hasOwnProperty(key)) continue;
if (comp.state.changedSettingKeys.indexOf(key) < 0) continue;
@ -150,6 +157,8 @@ export const saveSettings = (comp: ConfigScreenComponent) => {
comp.setState({ changedSettingKeys: [] });
onSettingsSaved({ savedSettingKeys });
return true;
};
export const settingsToComponents = (comp: ConfigScreenComponent, device: AppType, settings: any) => {

View File

@ -0,0 +1,57 @@
import { _ } from '../../locale';
import shim from '../../shim';
import BaseItem from '../BaseItem';
import Resource from '../Resource';
import Setting from '../Setting';
// Should return an error message if there's a problem, and an empty string if not.
type ValidationHandler = (oldValue: any, newValue: any)=> Promise<string>;
const validations: Record<string, ValidationHandler> = {
'sync.target': async (oldValue: number, newValue: number) => {
if (oldValue === 0 || newValue === 0) return '';
const preventChangeMessage = async () => {
const needToBeFetched = await Resource.needToBeFetched('always', 1);
if (needToBeFetched.length) return _('Some attachments need to be downloaded. Set the attachment download mode to "always" and try again.');
const downloadErrorCount = await Resource.downloadStatusCounts(Resource.FETCH_STATUS_ERROR);
if (downloadErrorCount > 0) return _('Some attachments could not be downloaded. Please try to download them again.');
const disabledCount = await BaseItem.syncDisabledItemsCount(Setting.value('sync.target'));
if (disabledCount > 0) return _('Some items could not be synchronised. Please try to synchronise them first.');
return '';
};
const message = await preventChangeMessage();
if (message) {
const resolutionMessage = shim.isReactNative() ?
_('Uninstall and reinstall the application. Make sure you create a backup first by exporting all your notes as JEX from the desktop application.') :
_('Close the application, then delete your profile in "%s", and start the application again. Make sure you create a backup first by exporting all your notes as JEX.', Setting.value('profileDir'));
return _('The sync target cannot be changed for the following reason: %s\n\nIf the issue cannot be resolved, you may need to clear your data first by following these instructions:\n\n%s', message, resolutionMessage);
}
return '';
},
};
const validateSetting = async (settingName: string, oldValue: any, newValue: any) => {
if (oldValue === newValue) return '';
if (!validations[settingName]) return '';
return await validations[settingName](oldValue, newValue);
};
export default async (settingKeys: string[], newValues: Record<string, any>) => {
for (const key of settingKeys) {
const oldValue = Setting.value(key);
const newValue = newValues[key];
const message = await validateSetting(key, oldValue, newValue);
return message;
}
return '';
};