252 lines
10 KiB
TypeScript
252 lines
10 KiB
TypeScript
// (C) Copyright 2015 Moodle Pty Ltd.
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
import { Injectable } from '@angular/core';
|
|
import { CoreSyncBaseProvider, CoreSyncBlockedError } from '@classes/base-sync';
|
|
import { CoreFileUploader } from '@features/fileuploader/services/fileuploader';
|
|
import { CoreSites, CoreSitesReadingStrategy } from '@services/sites';
|
|
import { CoreSync, CoreSyncResult } from '@services/sync';
|
|
import { CoreUtils } from '@services/utils/utils';
|
|
import { makeSingleton, Translate } from '@singletons';
|
|
import { CoreEvents } from '@singletons/events';
|
|
import { ADDON_BLOG_AUTO_SYNCED, ADDON_BLOG_SYNC_ID } from '../constants';
|
|
import { AddonBlog, AddonBlogAddEntryOption, AddonBlogAddEntryWSParams, AddonBlogProvider } from './blog';
|
|
import { AddonBlogOffline, AddonBlogOfflineEntry } from './blog-offline';
|
|
import { AddonBlogOfflineEntryDBRecord } from './database/blog';
|
|
|
|
/**
|
|
* Service to sync blog.
|
|
*/
|
|
@Injectable({ providedIn: 'root' })
|
|
export class AddonBlogSyncProvider extends CoreSyncBaseProvider<AddonBlogSyncResult> {
|
|
|
|
protected componentTranslatableString = 'addon.blog.blog';
|
|
|
|
constructor() {
|
|
super('AddonBlogSyncService');
|
|
}
|
|
|
|
/**
|
|
* Try to synchronize all the entries in a certain site or in all sites.
|
|
*
|
|
* @param siteId Site ID to sync. If not defined, sync all sites.
|
|
* @param force Force sync.
|
|
* @returns Promise resolved if sync is successful, rejected if sync fails.
|
|
*/
|
|
async syncAllEntries(siteId?: string, force?: boolean): Promise<void> {
|
|
await this.syncOnSites('All entries', (siteId) => this.syncAllEntriesFunc(siteId, !!force), siteId);
|
|
}
|
|
|
|
/**
|
|
* Sync all entries on a site.
|
|
*
|
|
* @param siteId Site ID to sync.
|
|
* @param force Force sync.
|
|
*/
|
|
protected async syncAllEntriesFunc(siteId: string, force = false): Promise<void> {
|
|
const needed = force ? true : await this.isSyncNeeded(ADDON_BLOG_SYNC_ID, siteId);
|
|
|
|
if (!needed) {
|
|
return;
|
|
}
|
|
|
|
const result = await this.syncEntriesForSite(siteId);
|
|
|
|
if (!result.updated) {
|
|
return;
|
|
}
|
|
|
|
CoreEvents.trigger(ADDON_BLOG_AUTO_SYNCED, undefined, siteId);
|
|
}
|
|
|
|
/**
|
|
* Perform entries syncronization for specified site.
|
|
*
|
|
* @param siteId Site id.
|
|
* @returns Syncronization result.
|
|
*/
|
|
async syncEntriesForSite(siteId: string): Promise<AddonBlogSyncResult> {
|
|
const currentSyncPromise = this.getOngoingSync(ADDON_BLOG_SYNC_ID, siteId);
|
|
|
|
if (currentSyncPromise) {
|
|
return currentSyncPromise;
|
|
}
|
|
|
|
this.logger.debug('Try to sync ' + ADDON_BLOG_SYNC_ID + ' in site ' + siteId);
|
|
|
|
return await this.addOngoingSync(ADDON_BLOG_SYNC_ID, this.performEntriesSync(siteId), siteId);
|
|
}
|
|
|
|
/**
|
|
* Performs entries syncronization.
|
|
*
|
|
* @param siteId Site ID.
|
|
* @returns Syncronization result.
|
|
*/
|
|
async performEntriesSync(siteId: string): Promise<AddonBlogSyncResult> {
|
|
const result: AddonBlogSyncResult = { updated: false, warnings: [] };
|
|
const entriesToSync = await this.syncEntriesToRemove(siteId);
|
|
|
|
for (const entry of entriesToSync.entries) {
|
|
if (CoreSync.isBlocked(AddonBlogProvider.COMPONENT, entry.id ?? entry.created, siteId)) {
|
|
this.logger.debug('Cannot sync entry ' + entry.created + ' because it is blocked.');
|
|
|
|
throw new CoreSyncBlockedError(Translate.instant('core.errorsyncblocked', { $a: this.componentTranslate }));
|
|
}
|
|
|
|
const formattedEntry: AddonBlogAddEntryWSParams = {
|
|
subject: entry.subject,
|
|
summary: entry.summary,
|
|
summaryformat: entry.summaryformat,
|
|
options: JSON.parse(entry.options),
|
|
};
|
|
|
|
try {
|
|
if (entry.id) {
|
|
await this.syncUpdatedEntry({ ...entry, id: entry.id, options: formattedEntry.options }, siteId);
|
|
result.updated = true;
|
|
continue;
|
|
}
|
|
|
|
const draftId = await this.uploadAttachments({ created: entry.created, options: formattedEntry.options }, siteId);
|
|
const option = formattedEntry.options.find(option => option.name === 'attachmentsid');
|
|
|
|
if (draftId) {
|
|
option ? option.value = draftId : formattedEntry.options.push({ name: 'attachmentsid', value: draftId });
|
|
}
|
|
|
|
await AddonBlog.addEntryOnline(formattedEntry, siteId);
|
|
await AddonBlogOffline.deleteOfflineEntryRecord({ created: entry.created }, siteId);
|
|
result.updated = true;
|
|
} catch (error) {
|
|
if (!error || !CoreUtils.isWebServiceError(error)) {
|
|
throw error;
|
|
}
|
|
|
|
await AddonBlogOffline.deleteOfflineEntryRecord(entry.id ? { id: entry.id } : { created: entry.created }, siteId);
|
|
this.addOfflineDataDeletedWarning(result.warnings, entry.subject, error);
|
|
result.updated = true;
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* Sync offline blog entry.
|
|
*
|
|
* @param entry Entry to update.
|
|
* @param siteId Site ID.
|
|
*/
|
|
protected async syncUpdatedEntry(entry: AddonBlogSyncEntryToSync, siteId?: string): Promise<void> {
|
|
const { attachmentsid } = await AddonBlog.prepareEntryForEdition({ entryid: entry.id }, siteId);
|
|
await this.uploadAttachments({ entryId: entry.id, attachmentsId: attachmentsid, options: entry.options }, siteId);
|
|
const optionsAttachmentsId = entry.options.find(option => option.name === 'attachmentsid');
|
|
|
|
if (optionsAttachmentsId) {
|
|
optionsAttachmentsId.value = attachmentsid;
|
|
} else {
|
|
entry.options.push({ name: 'attachmentsid', value: attachmentsid });
|
|
}
|
|
|
|
const { options, subject, summary, summaryformat, id } = entry;
|
|
await AddonBlog.updateEntryOnline({ options, subject, summary, summaryformat, entryid: id }, siteId);
|
|
await AddonBlogOffline.deleteOfflineEntryRecord({ id }, siteId);
|
|
}
|
|
|
|
/**
|
|
* Upload attachments.
|
|
*
|
|
* @param params entry creation date or entry ID and attachments ID.
|
|
*
|
|
* @returns draftId.
|
|
*/
|
|
protected async uploadAttachments(params: AddonBlogSyncUploadAttachmentsParams, siteId?: string): Promise<number | undefined> {
|
|
const site = await CoreSites.getSite(siteId);
|
|
const folder = 'created' in params ? { created: params.created } : { id: params.entryId };
|
|
const offlineFiles = await AddonBlogOffline.getOfflineFiles(folder, site.id);
|
|
|
|
if ('created' in params) {
|
|
return await CoreFileUploader.uploadOrReuploadFiles(
|
|
offlineFiles,
|
|
AddonBlogProvider.COMPONENT,
|
|
params.created,
|
|
site.id,
|
|
);
|
|
}
|
|
|
|
const { entries } = await AddonBlog.getEntries(
|
|
{ entryid: params.entryId },
|
|
{ readingStrategy: CoreSitesReadingStrategy.PREFER_NETWORK, siteId: site.id },
|
|
);
|
|
|
|
const onlineEntry = entries.find(entry => entry.id === params.entryId);
|
|
const attachments = AddonBlog.getAttachmentFilesFromOptions(params.options);
|
|
const filesToDelete = CoreFileUploader.getFilesToDelete(onlineEntry?.attachmentfiles ?? [], attachments.online);
|
|
|
|
if (filesToDelete.length) {
|
|
await CoreFileUploader.deleteDraftFiles(params.attachmentsId, filesToDelete, site.id);
|
|
}
|
|
|
|
await CoreFileUploader.uploadFiles(params.attachmentsId, [...attachments.online, ...offlineFiles], site.id);
|
|
}
|
|
|
|
/**
|
|
* Sync entries to remove.
|
|
*
|
|
* @param siteId Site ID.
|
|
* @returns Entries to remove and result.
|
|
*/
|
|
protected async syncEntriesToRemove(siteId?: string): Promise<AddonBlogSyncGetPendingToSyncEntries> {
|
|
let entriesToSync = await AddonBlogOffline.getOfflineEntries(undefined, siteId);
|
|
const entriesToBeRemoved = await AddonBlogOffline.getEntriesToRemove(siteId);
|
|
const warnings = [];
|
|
|
|
await Promise.all(entriesToBeRemoved.map(async (entry) => {
|
|
try {
|
|
await AddonBlog.deleteEntryOnline({ entryid: entry.id }, siteId);
|
|
const entriesPendingToSync = entriesToSync.filter(entryToSync => entryToSync.id !== entry.id);
|
|
|
|
if (entriesPendingToSync.length !== entriesToSync.length) {
|
|
await AddonBlogOffline.deleteOfflineEntryRecord({ id: entry.id }, siteId);
|
|
entriesToSync = entriesPendingToSync;
|
|
}
|
|
} catch (error) {
|
|
if (!CoreUtils.isWebServiceError(error)) {
|
|
throw error;
|
|
}
|
|
|
|
await AddonBlogOffline.unmarkEntryAsRemoved(entry.id, siteId);
|
|
this.addOfflineDataDeletedWarning(warnings, entry.subject, error);
|
|
}
|
|
}));
|
|
|
|
return { entries: entriesToSync, result: { updated: entriesToBeRemoved.length > 0, warnings } };
|
|
}
|
|
|
|
}
|
|
|
|
export const AddonBlogSync = makeSingleton(AddonBlogSyncProvider);
|
|
|
|
export type AddonBlogSyncResult = CoreSyncResult;
|
|
|
|
export type AddonBlogSyncUploadAttachmentsParams =
|
|
({ entryId: number; attachmentsId: number } | { created: number })
|
|
& { options: AddonBlogAddEntryOption[] };
|
|
|
|
export type AddonBlogSyncEntryToSync = Omit<AddonBlogOfflineEntryDBRecord, 'id'|'options'>
|
|
& { options: AddonBlogAddEntryOption[]; id: number };
|
|
|
|
export type AddonBlogSyncGetPendingToSyncEntries = { entries: AddonBlogOfflineEntry[]; result: AddonBlogSyncResult };
|