diff --git a/CHANGELOG.md b/CHANGELOG.md index 7439c60e..33b01bbd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). - (**Migration**) Add option to abort entries that are searching or are waiting to get migrated - (**Migration**) Add "in library" indicator - (**Settings/WebView**) Add setting to enable/disable WebView +- (**Settings/Sync**) Add sync settings ### Changed diff --git a/src/features/backup/Backup.utils.ts b/src/features/backup/Backup.utils.ts index 96ac96fa..ad35c81c 100644 --- a/src/features/backup/Backup.utils.ts +++ b/src/features/backup/Backup.utils.ts @@ -13,6 +13,7 @@ import type { BackupFlagInclusionState, } from '@/features/backup/Backup.types.ts'; import { BACKUP_FLAGS_TO_TRANSLATION } from '@/features/backup/Backup.constants.ts'; +import omit from 'lodash/fp/omit'; export const convertToAutoBackupFlags = (flags: BackupFlagInclusionState): AutoBackupFlagInclusionState => ({ autoBackupIncludeCategories: flags.includeCategories, @@ -46,11 +47,14 @@ const getIncludeExcludeText = (count: number, allCount: number, specificText: st return specificText; }; -export const getAutoBackupFlagsInfo = (autoFlags: AutoBackupFlagInclusionState): Record<`${boolean}`, string> => { - const flags = convertToBackupFlags(autoFlags); +export const getAutoBackupFlagsInfo = ( + autoFlags: AutoBackupFlagInclusionState, + hiddenFlags: BackupFlag[] = [], +): Record<`${boolean}`, string> => { + const flags = omit(hiddenFlags, convertToBackupFlags(autoFlags)); const totalFlags = Object.keys(flags).length; - const flagsByState = Object.groupBy(Object.entries(flags), ([, value]) => value.toString()); + const flagsByState = Object.groupBy(Object.entries(flags), ([, value]) => (value as boolean).toString()); const includedFlagsString = flagsByState.true?.map(([key]) => t(BACKUP_FLAGS_TO_TRANSLATION[key as BackupFlag])).join(', ') ?? ''; diff --git a/src/features/backup/component/BackupFlagInclusionDialog.tsx b/src/features/backup/component/BackupFlagInclusionDialog.tsx index fbb45f2e..7aafd936 100644 --- a/src/features/backup/component/BackupFlagInclusionDialog.tsx +++ b/src/features/backup/component/BackupFlagInclusionDialog.tsx @@ -24,7 +24,7 @@ import { BACKUP_FLAGS_BY_GROUP, BACKUP_FLAGS_TO_TRANSLATION, } from '@/features/backup/Backup.constants.ts'; -import type { BackupFlagGroup, BackupFlagInclusionState } from '@/features/backup/Backup.types.ts'; +import type { BackupFlag, BackupFlagGroup, BackupFlagInclusionState } from '@/features/backup/Backup.types.ts'; export const BackupFlagInclusionDialog = ({ onDismiss, @@ -33,11 +33,18 @@ export const BackupFlagInclusionDialog = ({ onExitComplete, title, flags, -}: AwaitableComponentProps & { title: string; flags?: BackupFlagInclusionState }) => { + hiddenFlags, +}: AwaitableComponentProps & { + title: string; + flags?: BackupFlagInclusionState; + hiddenFlags?: BackupFlag[]; +}) => { const { t } = useLingui(); const [includeStateByFlag, setIncludeStateByFlag] = useState( - Object.fromEntries(BACKUP_FLAGS.map((flag) => [flag, flags?.[flag] ?? true])) as BackupFlagInclusionState, + Object.fromEntries( + BACKUP_FLAGS.map((flag) => [flag, flags?.[flag] ?? !hiddenFlags?.includes(flag)]), + ) as BackupFlagInclusionState, ); return ( @@ -45,24 +52,28 @@ export const BackupFlagInclusionDialog = ({ {title} - {Object.entries(BACKUP_FLAGS_BY_GROUP).map(([group, groupFlags]) => ( - - {t(BACKUP_FLAG_GROUP_TO_TRANSLATION[group as BackupFlagGroup])} - {groupFlags.map((flag) => ( - { - setIncludeStateByFlag({ - ...includeStateByFlag, - [flag]: checked, - }); - }} - /> - ))} - - ))} + {Object.entries(BACKUP_FLAGS_BY_GROUP) + .filter(([_group, groupFlags]) => groupFlags.some((flag) => !hiddenFlags?.includes(flag))) + .map(([group, groupFlags]) => ( + + {t(BACKUP_FLAG_GROUP_TO_TRANSLATION[group as BackupFlagGroup])} + {groupFlags + .filter((flag) => !hiddenFlags?.includes(flag)) + .map((flag) => ( + { + setIncludeStateByFlag({ + ...includeStateByFlag, + [flag]: checked, + }); + }} + /> + ))} + + ))} diff --git a/src/features/settings/Settings.constants.ts b/src/features/settings/Settings.constants.ts index 86057323..ec5894b6 100644 --- a/src/features/settings/Settings.constants.ts +++ b/src/features/settings/Settings.constants.ts @@ -30,6 +30,7 @@ import { } from '@/lib/graphql/generated/graphql-base.types.ts'; import { ThemeMode } from '@/features/theme/AppTheme.types.ts'; import { getPreferredISOLanguageCodes } from '@/lib/ISOLanguageUtil.ts'; +import type { BackupFlag } from '@/features/backup/Backup.types.ts'; export const MANGA_GRID_WIDTH = { min: 100, @@ -317,3 +318,45 @@ export const IMAGE_PROCESSING_TYPE_TO_SETTING: Record< [ImageProcessingType.DOWNLOAD]: 'downloadConversions', [ImageProcessingType.SERVE]: 'serveConversions', }; + +const SYNC_INTERVAL_TO_TRANSLATION: Record = { + [d(0).seconds.asWholeMinutes.toISOString()]: { + text: msg`Disabled`, + }, + [d(30).minutes.asWholeMinutes.toISOString()]: { + text: msg`Every 30 minutes`, + }, + [d(1).hours.asWholeMinutes.toISOString()]: { + text: msg`Every hour`, + }, + [d(3).hours.asWholeMinutes.toISOString()]: { + text: msg`Every 3 hours`, + }, + [d(6).hours.asWholeMinutes.toISOString()]: { + text: msg`Every 6 hours`, + }, + [d(12).hours.asWholeMinutes.toISOString()]: { + text: msg`Every 12 hours`, + }, + [d(1).days.asWholeMinutes.toISOString()]: { + text: msg`Daily`, + }, + [d(7).days.asWholeMinutes.toISOString()]: { + text: msg`Weekly`, + }, +}; +export const SYNC_INTERVAL_CUSTOM_VALUE = d(10).days.asWholeMinutes.toISOString(); +export const SYNC_INTERVAL_VALUES = Object.keys(SYNC_INTERVAL_TO_TRANSLATION); +export const SYNC_INTERVAL_SELECT_VALUES: SelectSettingValue[] = SYNC_INTERVAL_VALUES.map((value) => [ + value, + SYNC_INTERVAL_TO_TRANSLATION[value], +]); +export const SYNC_INTERVAL_SELECT_VALUES_WITH_CUSTOM: SelectSettingValue[] = [ + ...SYNC_INTERVAL_SELECT_VALUES, + [SYNC_INTERVAL_CUSTOM_VALUE, { text: msg`Custom` }], +]; + +export const SYNC_SETTINGS_HIDDEN_BACKUP_FLAGS = [ + 'includeServerSettings', + 'includeClientData', +] as const satisfies BackupFlag[]; diff --git a/src/features/settings/screens/ServerSettings.tsx b/src/features/settings/screens/ServerSettings.tsx index e2a98d83..e5d9569f 100644 --- a/src/features/settings/screens/ServerSettings.tsx +++ b/src/features/settings/screens/ServerSettings.tsx @@ -31,17 +31,46 @@ import { makeToast } from '@/base/utils/Toast.ts'; import type { MetadataUpdateSettings } from '@/features/app-updates/AppUpdateChecker.types.ts'; import { getErrorMessage } from '@/lib/HelperFunctions.ts'; import { useAppTitle } from '@/features/navigation-bar/hooks/useAppTitle.ts'; +import type { PartialSettingsTypeInput } from '@/lib/graphql/generated/graphql-base.types.ts'; import { AuthMode, CbzMediaType, DatabaseType, SortOrder } from '@/lib/graphql/generated/graphql-base.types.ts'; import { AUTH_MODES_SELECT_VALUES, JWT_ACCESS_TOKEN_EXPIRY, JWT_REFRESH_TOKEN_EXPIRY, + SYNC_INTERVAL_CUSTOM_VALUE, + SYNC_INTERVAL_SELECT_VALUES, + SYNC_INTERVAL_SELECT_VALUES_WITH_CUSTOM, + SYNC_INTERVAL_VALUES, + SYNC_SETTINGS_HIDDEN_BACKUP_FLAGS, } from '@/features/settings/Settings.constants.ts'; import { ServerAddressSetting } from '@/features/settings/components/ServerAddressSetting.tsx'; import { AuthManager } from '@/features/authentication/AuthManager.ts'; import type { ServerSettings as ServerSettingsType } from '@/features/settings/Settings.types.ts'; import { KoreaderSyncSettings } from '@/features/settings/components/koreaderSync/KoreaderSyncSettings.tsx'; import { Confirmation } from '@/base/AppAwaitableComponent.ts'; +import { BackupFlagInclusionDialog } from '@/features/backup/component/BackupFlagInclusionDialog.tsx'; +import ListItemButton from '@mui/material/ListItemButton'; +import { AwaitableComponent } from 'awaitable-component'; +import { getAutoBackupFlagsInfo } from '@/features/backup/Backup.utils.ts'; +import type { BackupFlagInclusionState } from '@/features/backup/Backup.types.ts'; + +const convertSyncDataToBackupFlags = (settings: ServerSettingsType): BackupFlagInclusionState => ({ + includeManga: settings.syncDataManga, + includeChapters: settings.syncDataChapters, + includeCategories: settings.syncDataCategories, + includeHistory: settings.syncDataHistory, + includeTracking: settings.syncDataTracking, + includeClientData: false, + includeServerSettings: false, +}); + +const convertBackupFlagsToSyncData = (flags: BackupFlagInclusionState): PartialSettingsTypeInput => ({ + syncDataManga: flags.includeManga, + syncDataChapters: flags.includeChapters, + syncDataCategories: flags.includeCategories, + syncDataHistory: flags.includeHistory, + syncDataTracking: flags.includeTracking, +}); const getLogFilesCleanupDisplayValue = (ttl: number): string => { if (ttl === 0) { @@ -91,6 +120,15 @@ export const ServerSettings = () => { onCompletion?.(false); } }; + const updateSettings = async (settings: PartialSettingsTypeInput, onCompletion?: (success: boolean) => void) => { + try { + await mutateSettings({ variables: { input: { settings } } }); + onCompletion?.(true); + } catch (e) { + makeToast(t`Failed to save changes`, 'error', getErrorMessage(e)); + onCompletion?.(false); + } + }; const localSettings = useMemo( () => ( @@ -176,6 +214,13 @@ export const ServerSettings = () => { const authModeDisabled = !serverSettings.authUsername?.trim() || !serverSettings.authPassword?.trim(); const isH2Database = serverSettings.databaseType === DatabaseType.H2; + const isCustomSyncInterval = !SYNC_INTERVAL_VALUES.includes( + d(serverSettings.syncInterval).minutes.asWholeMinutes.toISOString(), + ); + const syncDataFlagsInfo = getAutoBackupFlagsInfo(serverSettings); + const includedSyncDataText = syncDataFlagsInfo.true; + const excludedSyncDataText = syncDataFlagsInfo.false; + return ( {localSettings} @@ -669,6 +714,81 @@ export const ServerSettings = () => { /> + + {t`Sync`} + + } + > + { + try { + const flags = await AwaitableComponent.show(BackupFlagInclusionDialog, { + title: t`Sync data`, + flags: convertSyncDataToBackupFlags(serverSettings), + hiddenFlags: SYNC_SETTINGS_HIDDEN_BACKUP_FLAGS, + }); + + await updateSettings(convertBackupFlagsToSyncData(flags)); + } catch (e) { + // Ignore + } + }} + > + + {t`Include: ${includedSyncDataText}`} + {t`Exclude: ${excludedSyncDataText}`} + + } + slotProps={{ + secondary: { sx: { display: 'flex', flexDirection: 'column' } }, + }} + /> + + updateSetting('syncInterval', syncInterval)} + /> + + {t`SyncYomi`} + + } + > + + + updateSetting('syncYomiEnabled', e.target.checked)} + /> + + updateSetting('syncYomiHost', host)} + /> + updateSetting('syncYomiApiKey', apiKey)} + isPassword + /> + + diff --git a/src/i18n/locales/en.po b/src/i18n/locales/en.po index 625c91f5..b3d18f3c 100644 --- a/src/i18n/locales/en.po +++ b/src/i18n/locales/en.po @@ -567,6 +567,7 @@ msgid "Backup cleanup" msgstr "Backup cleanup" #: src/features/backup/screens/Backup.tsx +#: src/features/settings/screens/ServerSettings.tsx msgid "Backup data" msgstr "Backup data" @@ -1239,6 +1240,10 @@ msgstr "Cycle reading direction" msgid "Cycle reading mode" msgstr "Cycle reading mode" +#: src/features/settings/Settings.constants.ts +msgid "Daily" +msgstr "Daily" + #: src/features/settings/screens/Appearance.tsx msgid "Dark" msgstr "Dark" @@ -1535,6 +1540,10 @@ msgstr "Enable debug logs" msgid "Enable page read progress" msgstr "Enable page read progress" +#: src/features/settings/screens/ServerSettings.tsx +msgid "Enable the WebView via CEF (Chromium)" +msgstr "Enable the WebView via CEF (Chromium)" + #: src/features/downloads/screens/DownloadSettings.tsx msgid "Entries in excluded categories will not be downloaded even if they are also in included categories" msgstr "Entries in excluded categories will not be downloaded even if they are also in included categories" @@ -1560,6 +1569,26 @@ msgstr "Entry \"{id}\" not found" msgid "Error" msgstr "Error" +#: src/features/settings/Settings.constants.ts +msgid "Every 12 hours" +msgstr "Every 12 hours" + +#: src/features/settings/Settings.constants.ts +msgid "Every 3 hours" +msgstr "Every 3 hours" + +#: src/features/settings/Settings.constants.ts +msgid "Every 30 minutes" +msgstr "Every 30 minutes" + +#: src/features/settings/Settings.constants.ts +msgid "Every 6 hours" +msgstr "Every 6 hours" + +#: src/features/settings/Settings.constants.ts +msgid "Every hour" +msgstr "Every hour" + #: src/features/settings/screens/ServerSettings.tsx msgid "Example for possible values: 1 (bytes), 1KB (kilobytes), 1MB (megabytes), 1GB (gigabytes)" msgstr "Example for possible values: 1 (bytes), 1KB (kilobytes), 1MB (megabytes), 1GB (gigabytes)" @@ -1577,6 +1606,10 @@ msgstr "Exclude scanlators" msgid "Exclude: {excludedCategoriesText}" msgstr "Exclude: {excludedCategoriesText}" +#: src/features/settings/screens/ServerSettings.tsx +msgid "Exclude: {excludedSyncDataText}" +msgstr "Exclude: {excludedSyncDataText}" + #: src/features/migration/Migration.constants.ts msgid "Excluded" msgstr "Excluded" @@ -1959,6 +1992,10 @@ msgstr "Include" msgid "Include: {includedCategoriesText}" msgstr "Include: {includedCategoriesText}" +#: src/features/settings/screens/ServerSettings.tsx +msgid "Include: {includedSyncDataText}" +msgstr "Include: {includedSyncDataText}" + #: src/features/reader/hotkeys/settings/components/ReaderSettingHotkey.tsx msgid "Increase auto scroll speed" msgstr "Increase auto scroll speed" @@ -3476,6 +3513,18 @@ msgstr "Successfully migrated" msgid "Successfully migrated manga" msgstr "Successfully migrated manga" +#: src/features/settings/screens/ServerSettings.tsx +msgid "Sync" +msgstr "Sync" + +#: src/features/settings/screens/ServerSettings.tsx +msgid "Sync data" +msgstr "Sync data" + +#: src/features/settings/screens/ServerSettings.tsx +msgid "Sync interval" +msgstr "Sync interval" + #: src/features/settings/components/koreaderSync/KoreaderSyncSettings.tsx msgid "Sync status" msgstr "Sync status" @@ -3492,6 +3541,22 @@ msgstr "Sync to an older state" msgid "Sync will not be triggered if the progress difference is within this tolerance." msgstr "Sync will not be triggered if the progress difference is within this tolerance." +#: src/features/settings/screens/ServerSettings.tsx +msgid "SyncYomi" +msgstr "SyncYomi" + +#: src/features/settings/screens/ServerSettings.tsx +msgid "SyncYomi API key" +msgstr "SyncYomi API key" + +#: src/features/settings/screens/ServerSettings.tsx +msgid "SyncYomi enabled" +msgstr "SyncYomi enabled" + +#: src/features/settings/screens/ServerSettings.tsx +msgid "SyncYomi host" +msgstr "SyncYomi host" + #: src/features/settings/screens/Appearance.tsx msgid "System" msgstr "System" @@ -3973,6 +4038,14 @@ msgstr "WebUI version {0} ({1}) available for download" msgid "WebUI was updated to version {newVersion} ({0})" msgstr "WebUI was updated to version {newVersion} ({0})" +#: src/features/settings/screens/ServerSettings.tsx +msgid "WebView" +msgstr "WebView" + +#: src/features/settings/Settings.constants.ts +msgid "Weekly" +msgstr "Weekly" + #: src/features/settings/Settings.constants.ts msgid "When you enable this, you may need to refresh this tab for the login page to appear." msgstr "When you enable this, you may need to refresh this tab for the login page to appear." diff --git a/src/lib/graphql/generated/apollo-helpers.ts b/src/lib/graphql/generated/apollo-helpers.ts index 481f7367..a92280dc 100644 --- a/src/lib/graphql/generated/apollo-helpers.ts +++ b/src/lib/graphql/generated/apollo-helpers.ts @@ -922,6 +922,7 @@ export type MutationKeySpecifier = ( | 'setSourceMeta' | 'setSourceMetas' | 'startDownloader' + | 'startSync' | 'stopDownloader' | 'trackProgress' | 'unbindTrack' @@ -1000,6 +1001,7 @@ export type MutationFieldPolicy = { setSourceMeta?: FieldPolicy | FieldReadFunction; setSourceMetas?: FieldPolicy | FieldReadFunction; startDownloader?: FieldPolicy | FieldReadFunction; + startSync?: FieldPolicy | FieldReadFunction; stopDownloader?: FieldPolicy | FieldReadFunction; trackProgress?: FieldPolicy | FieldReadFunction; unbindTrack?: FieldPolicy | FieldReadFunction; @@ -1122,6 +1124,15 @@ export type PartialSettingsTypeKeySpecifier = ( | 'socksProxyPort' | 'socksProxyUsername' | 'socksProxyVersion' + | 'syncDataCategories' + | 'syncDataChapters' + | 'syncDataHistory' + | 'syncDataManga' + | 'syncDataTracking' + | 'syncInterval' + | 'syncYomiApiKey' + | 'syncYomiEnabled' + | 'syncYomiHost' | 'systemTrayEnabled' | 'updateMangas' | 'useHikariConnectionPool' @@ -1211,6 +1222,15 @@ export type PartialSettingsTypeFieldPolicy = { socksProxyPort?: FieldPolicy | FieldReadFunction; socksProxyUsername?: FieldPolicy | FieldReadFunction; socksProxyVersion?: FieldPolicy | FieldReadFunction; + syncDataCategories?: FieldPolicy | FieldReadFunction; + syncDataChapters?: FieldPolicy | FieldReadFunction; + syncDataHistory?: FieldPolicy | FieldReadFunction; + syncDataManga?: FieldPolicy | FieldReadFunction; + syncDataTracking?: FieldPolicy | FieldReadFunction; + syncInterval?: FieldPolicy | FieldReadFunction; + syncYomiApiKey?: FieldPolicy | FieldReadFunction; + syncYomiEnabled?: FieldPolicy | FieldReadFunction; + syncYomiHost?: FieldPolicy | FieldReadFunction; systemTrayEnabled?: FieldPolicy | FieldReadFunction; updateMangas?: FieldPolicy | FieldReadFunction; useHikariConnectionPool?: FieldPolicy | FieldReadFunction; @@ -1255,6 +1275,7 @@ export type QueryKeySpecifier = ( | 'extensions' | 'getWebUIUpdateStatus' | 'koSyncStatus' + | 'lastSyncStatus' | 'lastUpdateTimestamp' | 'libraryUpdateStatus' | 'manga' @@ -1288,6 +1309,7 @@ export type QueryFieldPolicy = { extensions?: FieldPolicy | FieldReadFunction; getWebUIUpdateStatus?: FieldPolicy | FieldReadFunction; koSyncStatus?: FieldPolicy | FieldReadFunction; + lastSyncStatus?: FieldPolicy | FieldReadFunction; lastUpdateTimestamp?: FieldPolicy | FieldReadFunction; libraryUpdateStatus?: FieldPolicy | FieldReadFunction; manga?: FieldPolicy | FieldReadFunction; @@ -1509,6 +1531,15 @@ export type SettingsKeySpecifier = ( | 'socksProxyPort' | 'socksProxyUsername' | 'socksProxyVersion' + | 'syncDataCategories' + | 'syncDataChapters' + | 'syncDataHistory' + | 'syncDataManga' + | 'syncDataTracking' + | 'syncInterval' + | 'syncYomiApiKey' + | 'syncYomiEnabled' + | 'syncYomiHost' | 'systemTrayEnabled' | 'updateMangas' | 'useHikariConnectionPool' @@ -1598,6 +1629,15 @@ export type SettingsFieldPolicy = { socksProxyPort?: FieldPolicy | FieldReadFunction; socksProxyUsername?: FieldPolicy | FieldReadFunction; socksProxyVersion?: FieldPolicy | FieldReadFunction; + syncDataCategories?: FieldPolicy | FieldReadFunction; + syncDataChapters?: FieldPolicy | FieldReadFunction; + syncDataHistory?: FieldPolicy | FieldReadFunction; + syncDataManga?: FieldPolicy | FieldReadFunction; + syncDataTracking?: FieldPolicy | FieldReadFunction; + syncInterval?: FieldPolicy | FieldReadFunction; + syncYomiApiKey?: FieldPolicy | FieldReadFunction; + syncYomiEnabled?: FieldPolicy | FieldReadFunction; + syncYomiHost?: FieldPolicy | FieldReadFunction; systemTrayEnabled?: FieldPolicy | FieldReadFunction; updateMangas?: FieldPolicy | FieldReadFunction; useHikariConnectionPool?: FieldPolicy | FieldReadFunction; @@ -1738,6 +1778,15 @@ export type SettingsTypeKeySpecifier = ( | 'socksProxyPort' | 'socksProxyUsername' | 'socksProxyVersion' + | 'syncDataCategories' + | 'syncDataChapters' + | 'syncDataHistory' + | 'syncDataManga' + | 'syncDataTracking' + | 'syncInterval' + | 'syncYomiApiKey' + | 'syncYomiEnabled' + | 'syncYomiHost' | 'systemTrayEnabled' | 'updateMangas' | 'useHikariConnectionPool' @@ -1827,6 +1876,15 @@ export type SettingsTypeFieldPolicy = { socksProxyPort?: FieldPolicy | FieldReadFunction; socksProxyUsername?: FieldPolicy | FieldReadFunction; socksProxyVersion?: FieldPolicy | FieldReadFunction; + syncDataCategories?: FieldPolicy | FieldReadFunction; + syncDataChapters?: FieldPolicy | FieldReadFunction; + syncDataHistory?: FieldPolicy | FieldReadFunction; + syncDataManga?: FieldPolicy | FieldReadFunction; + syncDataTracking?: FieldPolicy | FieldReadFunction; + syncInterval?: FieldPolicy | FieldReadFunction; + syncYomiApiKey?: FieldPolicy | FieldReadFunction; + syncYomiEnabled?: FieldPolicy | FieldReadFunction; + syncYomiHost?: FieldPolicy | FieldReadFunction; systemTrayEnabled?: FieldPolicy | FieldReadFunction; updateMangas?: FieldPolicy | FieldReadFunction; useHikariConnectionPool?: FieldPolicy | FieldReadFunction; @@ -1907,6 +1965,11 @@ export type StartDownloaderPayloadFieldPolicy = { clientMutationId?: FieldPolicy | FieldReadFunction; downloadStatus?: FieldPolicy | FieldReadFunction; }; +export type StartSyncPayloadKeySpecifier = ('clientMutationId' | 'result' | StartSyncPayloadKeySpecifier)[]; +export type StartSyncPayloadFieldPolicy = { + clientMutationId?: FieldPolicy | FieldReadFunction; + result?: FieldPolicy | FieldReadFunction; +}; export type StopDownloaderPayloadKeySpecifier = ( | 'clientMutationId' | 'downloadStatus' @@ -1920,6 +1983,7 @@ export type SubscriptionKeySpecifier = ( | 'downloadChanged' | 'downloadStatusChanged' | 'libraryUpdateStatusChanged' + | 'syncStatusChanged' | 'updateStatusChanged' | 'webUIUpdateStatusChange' | SubscriptionKeySpecifier @@ -1928,6 +1992,7 @@ export type SubscriptionFieldPolicy = { downloadChanged?: FieldPolicy | FieldReadFunction; downloadStatusChanged?: FieldPolicy | FieldReadFunction; libraryUpdateStatusChanged?: FieldPolicy | FieldReadFunction; + syncStatusChanged?: FieldPolicy | FieldReadFunction; updateStatusChanged?: FieldPolicy | FieldReadFunction; webUIUpdateStatusChange?: FieldPolicy | FieldReadFunction; }; @@ -1955,6 +2020,21 @@ export type SyncConflictInfoTypeFieldPolicy = { deviceName?: FieldPolicy | FieldReadFunction; remotePage?: FieldPolicy | FieldReadFunction; }; +export type SyncStatusKeySpecifier = ( + | 'backupRestoreId' + | 'endDate' + | 'errorMessage' + | 'startDate' + | 'state' + | SyncStatusKeySpecifier +)[]; +export type SyncStatusFieldPolicy = { + backupRestoreId?: FieldPolicy | FieldReadFunction; + endDate?: FieldPolicy | FieldReadFunction; + errorMessage?: FieldPolicy | FieldReadFunction; + startDate?: FieldPolicy | FieldReadFunction; + state?: FieldPolicy | FieldReadFunction; +}; export type TextFilterKeySpecifier = ('default' | 'name' | TextFilterKeySpecifier)[]; export type TextFilterFieldPolicy = { default?: FieldPolicy | FieldReadFunction; @@ -2918,6 +2998,10 @@ export type StrictTypedTypePolicies = { keyFields?: false | StartDownloaderPayloadKeySpecifier | (() => undefined | StartDownloaderPayloadKeySpecifier); fields?: StartDownloaderPayloadFieldPolicy; }; + StartSyncPayload?: Omit & { + keyFields?: false | StartSyncPayloadKeySpecifier | (() => undefined | StartSyncPayloadKeySpecifier); + fields?: StartSyncPayloadFieldPolicy; + }; StopDownloaderPayload?: Omit & { keyFields?: false | StopDownloaderPayloadKeySpecifier | (() => undefined | StopDownloaderPayloadKeySpecifier); fields?: StopDownloaderPayloadFieldPolicy; @@ -2934,6 +3018,10 @@ export type StrictTypedTypePolicies = { keyFields?: false | SyncConflictInfoTypeKeySpecifier | (() => undefined | SyncConflictInfoTypeKeySpecifier); fields?: SyncConflictInfoTypeFieldPolicy; }; + SyncStatus?: Omit & { + keyFields?: false | SyncStatusKeySpecifier | (() => undefined | SyncStatusKeySpecifier); + fields?: SyncStatusFieldPolicy; + }; TextFilter?: Omit & { keyFields?: false | TextFilterKeySpecifier | (() => undefined | TextFilterKeySpecifier); fields?: TextFilterFieldPolicy; diff --git a/src/lib/graphql/generated/graphql-base.types.ts b/src/lib/graphql/generated/graphql-base.types.ts index af8c1ffe..f231d71a 100644 --- a/src/lib/graphql/generated/graphql-base.types.ts +++ b/src/lib/graphql/generated/graphql-base.types.ts @@ -1363,6 +1363,7 @@ export type Mutation = { setSourceMeta?: Maybe; setSourceMetas?: Maybe; startDownloader?: Maybe; + startSync: StartSyncPayload; stopDownloader?: Maybe; trackProgress?: Maybe; unbindTrack: UnbindTrackPayload; @@ -1598,6 +1599,10 @@ export type MutationStartDownloaderArgs = { input: StartDownloaderInput; }; +export type MutationStartSyncArgs = { + input: StartSyncInput; +}; + export type MutationStopDownloaderArgs = { input: StopDownloaderInput; }; @@ -1824,6 +1829,15 @@ export type PartialSettingsType = Settings & { socksProxyPort?: Maybe; socksProxyUsername?: Maybe; socksProxyVersion?: Maybe; + syncDataCategories?: Maybe; + syncDataChapters?: Maybe; + syncDataHistory?: Maybe; + syncDataManga?: Maybe; + syncDataTracking?: Maybe; + syncInterval?: Maybe; + syncYomiApiKey?: Maybe; + syncYomiEnabled?: Maybe; + syncYomiHost?: Maybe; systemTrayEnabled?: Maybe; updateMangas?: Maybe; useHikariConnectionPool?: Maybe; @@ -1903,6 +1917,15 @@ export type PartialSettingsTypeInput = { socksProxyPort?: InputMaybe; socksProxyUsername?: InputMaybe; socksProxyVersion?: InputMaybe; + syncDataCategories?: InputMaybe; + syncDataChapters?: InputMaybe; + syncDataHistory?: InputMaybe; + syncDataManga?: InputMaybe; + syncDataTracking?: InputMaybe; + syncInterval?: InputMaybe; + syncYomiApiKey?: InputMaybe; + syncYomiEnabled?: InputMaybe; + syncYomiHost?: InputMaybe; systemTrayEnabled?: InputMaybe; updateMangas?: InputMaybe; useHikariConnectionPool?: InputMaybe; @@ -1958,6 +1981,7 @@ export type Query = { extensions: ExtensionNodeList; getWebUIUpdateStatus: WebUiUpdateStatus; koSyncStatus: KoSyncStatusPayload; + lastSyncStatus?: Maybe; lastUpdateTimestamp: LastUpdateTimestampPayload; libraryUpdateStatus: LibraryUpdateStatus; manga: MangaType; @@ -2412,6 +2436,15 @@ export type Settings = { socksProxyPort?: Maybe; socksProxyUsername?: Maybe; socksProxyVersion?: Maybe; + syncDataCategories?: Maybe; + syncDataChapters?: Maybe; + syncDataHistory?: Maybe; + syncDataManga?: Maybe; + syncDataTracking?: Maybe; + syncInterval?: Maybe; + syncYomiApiKey?: Maybe; + syncYomiEnabled?: Maybe; + syncYomiHost?: Maybe; systemTrayEnabled?: Maybe; updateMangas?: Maybe; useHikariConnectionPool?: Maybe; @@ -2556,6 +2589,15 @@ export type SettingsType = Settings & { socksProxyPort: Scalars['String']['output']; socksProxyUsername: Scalars['String']['output']; socksProxyVersion: Scalars['Int']['output']; + syncDataCategories: Scalars['Boolean']['output']; + syncDataChapters: Scalars['Boolean']['output']; + syncDataHistory: Scalars['Boolean']['output']; + syncDataManga: Scalars['Boolean']['output']; + syncDataTracking: Scalars['Boolean']['output']; + syncInterval: Scalars['Duration']['output']; + syncYomiApiKey: Scalars['String']['output']; + syncYomiEnabled: Scalars['Boolean']['output']; + syncYomiHost: Scalars['String']['output']; systemTrayEnabled: Scalars['Boolean']['output']; updateMangas: Scalars['Boolean']['output']; useHikariConnectionPool: Scalars['Boolean']['output']; @@ -2685,6 +2727,22 @@ export type StartDownloaderPayload = { downloadStatus: DownloadStatus; }; +export type StartSyncInput = { + clientMutationId?: InputMaybe; +}; + +export type StartSyncPayload = { + __typename?: 'StartSyncPayload'; + clientMutationId?: Maybe; + result: StartSyncResult; +}; + +export enum StartSyncResult { + Success = 'SUCCESS', + SyncDisabled = 'SYNC_DISABLED', + SyncInProgress = 'SYNC_IN_PROGRESS', +} + export type StopDownloaderInput = { clientMutationId?: InputMaybe; }; @@ -2777,6 +2835,7 @@ export type Subscription = { downloadChanged: DownloadStatus; downloadStatusChanged: DownloadUpdates; libraryUpdateStatusChanged: UpdaterUpdates; + syncStatusChanged: SyncStatus; /** @deprecated Replaced with updates, replace with updates(input) */ updateStatusChanged: UpdateStatus; webUIUpdateStatusChange: WebUiUpdateStatus; @@ -2807,6 +2866,26 @@ export type SyncConflictInfoType = { remotePage: Scalars['Int']['output']; }; +export enum SyncState { + CreatingBackup = 'CREATING_BACKUP', + Downloading = 'DOWNLOADING', + Error = 'ERROR', + Merging = 'MERGING', + Restoring = 'RESTORING', + Started = 'STARTED', + Success = 'SUCCESS', + Uploading = 'UPLOADING', +} + +export type SyncStatus = { + __typename?: 'SyncStatus'; + backupRestoreId?: Maybe; + endDate?: Maybe; + errorMessage?: Maybe; + startDate: Scalars['LongString']['output']; + state: SyncState; +}; + export type TextFilter = { __typename?: 'TextFilter'; default: Scalars['String']['output']; diff --git a/src/lib/graphql/generated/graphql.ts b/src/lib/graphql/generated/graphql.ts index 85d5aaf5..b037bcd1 100644 --- a/src/lib/graphql/generated/graphql.ts +++ b/src/lib/graphql/generated/graphql.ts @@ -2795,6 +2795,15 @@ export type ServerSettingsFragment = { databasePassword: string; useHikariConnectionPool: boolean; kcefEnabled: boolean; + syncDataCategories: boolean; + syncDataChapters: boolean; + syncDataHistory: boolean; + syncDataManga: boolean; + syncDataTracking: boolean; + syncInterval: string; + syncYomiApiKey: string; + syncYomiEnabled: boolean; + syncYomiHost: string; downloadConversions: Array<{ __typename: 'SettingsDownloadConversionType'; mimeType: string; @@ -2899,6 +2908,15 @@ export type ResetServerSettingsMutation = { databasePassword: string; useHikariConnectionPool: boolean; kcefEnabled: boolean; + syncDataCategories: boolean; + syncDataChapters: boolean; + syncDataHistory: boolean; + syncDataManga: boolean; + syncDataTracking: boolean; + syncInterval: string; + syncYomiApiKey: string; + syncYomiEnabled: boolean; + syncYomiHost: string; downloadConversions: Array<{ __typename: 'SettingsDownloadConversionType'; mimeType: string; @@ -3013,6 +3031,15 @@ export type UpdateServerSettingsMutation = { databasePassword: string; useHikariConnectionPool: boolean; kcefEnabled: boolean; + syncDataCategories: boolean; + syncDataChapters: boolean; + syncDataHistory: boolean; + syncDataManga: boolean; + syncDataTracking: boolean; + syncInterval: string; + syncYomiApiKey: string; + syncYomiEnabled: boolean; + syncYomiHost: string; downloadConversions: Array<{ __typename: 'SettingsDownloadConversionType'; mimeType: string; @@ -3123,6 +3150,15 @@ export type GetServerSettingsQuery = { databasePassword: string; useHikariConnectionPool: boolean; kcefEnabled: boolean; + syncDataCategories: boolean; + syncDataChapters: boolean; + syncDataHistory: boolean; + syncDataManga: boolean; + syncDataTracking: boolean; + syncInterval: string; + syncYomiApiKey: string; + syncYomiEnabled: boolean; + syncYomiHost: string; downloadConversions: Array<{ __typename: 'SettingsDownloadConversionType'; mimeType: string; @@ -3689,6 +3725,52 @@ export type GetMigratableSourcesQuery = { }; }; +export type SyncStatusFieldsFragment = { + __typename: 'SyncStatus'; + backupRestoreId: string | null; + endDate: string | null; + errorMessage: string | null; + startDate: string; + state: Types.SyncState; +}; + +export type StartSyncMutationVariables = Exact<{ + input?: Types.StartSyncInput | null | undefined; +}>; + +export type StartSyncMutation = { + __typename: 'Mutation'; + startSync: { __typename: 'StartSyncPayload'; result: Types.StartSyncResult }; +}; + +export type GetSyncStatusQueryVariables = Exact<{ [key: string]: never }>; + +export type GetSyncStatusQuery = { + __typename: 'Query'; + lastSyncStatus: { + __typename: 'SyncStatus'; + backupRestoreId: string | null; + endDate: string | null; + errorMessage: string | null; + startDate: string; + state: Types.SyncState; + } | null; +}; + +export type SyncSubscriptionVariables = Exact<{ [key: string]: never }>; + +export type SyncSubscription = { + __typename: 'Subscription'; + syncStatusChanged: { + __typename: 'SyncStatus'; + backupRestoreId: string | null; + endDate: string | null; + errorMessage: string | null; + startDate: string; + state: Types.SyncState; + }; +}; + export type TrackerBaseFieldsFragment = { __typename: 'TrackerType'; id: number; diff --git a/src/lib/graphql/settings/SettingsFragments.ts b/src/lib/graphql/settings/SettingsFragments.ts index 5fbc19a4..2baf4d6e 100644 --- a/src/lib/graphql/settings/SettingsFragments.ts +++ b/src/lib/graphql/settings/SettingsFragments.ts @@ -138,5 +138,16 @@ export const SERVER_SETTINGS = gql` # WebView kcefEnabled + + # Sync + syncDataCategories + syncDataChapters + syncDataHistory + syncDataManga + syncDataTracking + syncInterval + syncYomiApiKey + syncYomiEnabled + syncYomiHost } `; diff --git a/src/lib/graphql/sync/SyncFragments.ts b/src/lib/graphql/sync/SyncFragments.ts new file mode 100644 index 00000000..7f3acb6b --- /dev/null +++ b/src/lib/graphql/sync/SyncFragments.ts @@ -0,0 +1,19 @@ +/* + * Copyright (C) Contributors to the Suwayomi project + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + */ + +import gql from 'graphql-tag'; + +export const SYNC_STATUS_FIELDS = gql` + fragment SYNC_STATUS_FIELDS on SyncStatus { + backupRestoreId + endDate + errorMessage + startDate + state + } +`; diff --git a/src/lib/graphql/sync/SyncMutation.ts b/src/lib/graphql/sync/SyncMutation.ts new file mode 100644 index 00000000..f912ae4c --- /dev/null +++ b/src/lib/graphql/sync/SyncMutation.ts @@ -0,0 +1,17 @@ +/* + * Copyright (C) Contributors to the Suwayomi project + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + */ + +import gql from 'graphql-tag'; + +export const START_SYNC = gql` + mutation START_SYNC($input: StartSyncInput = {}) { + startSync(input: $input) { + result + } + } +`; diff --git a/src/lib/graphql/sync/SyncQuery.ts b/src/lib/graphql/sync/SyncQuery.ts new file mode 100644 index 00000000..58faa92b --- /dev/null +++ b/src/lib/graphql/sync/SyncQuery.ts @@ -0,0 +1,20 @@ +/* + * Copyright (C) Contributors to the Suwayomi project + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + */ + +import gql from 'graphql-tag'; +import { SYNC_STATUS_FIELDS } from '@/lib/graphql/sync/SyncFragments.ts'; + +export const GET_SYNC_STATUS = gql` + ${SYNC_STATUS_FIELDS} + + query GET_SYNC_STATUS { + lastSyncStatus { + ...SYNC_STATUS_FIELDS + } + } +`; diff --git a/src/lib/graphql/sync/SyncSubscription.ts b/src/lib/graphql/sync/SyncSubscription.ts new file mode 100644 index 00000000..2145836b --- /dev/null +++ b/src/lib/graphql/sync/SyncSubscription.ts @@ -0,0 +1,20 @@ +/* + * Copyright (C) Contributors to the Suwayomi project + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + */ + +import gql from 'graphql-tag'; +import { SYNC_STATUS_FIELDS } from '@/lib/graphql/sync/SyncFragments.ts'; + +export const SYNC_SUBSCRIPTION = gql` + ${SYNC_STATUS_FIELDS} + + subscription SYNC_SUBSCRIPTION { + syncStatusChanged { + ...SYNC_STATUS_FIELDS + } + } +`;