291 lines
10 KiB
TypeScript
Raw Normal View History

// (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 { CoreFileUploader, CoreFileUploaderStoreFilesResult } from '@features/fileuploader/services/fileuploader';
import { CoreFile } from '@services/file';
import { CoreSites } from '@services/sites';
import { CoreTextUtils } from '@services/utils/text';
import { CoreUtils } from '@services/utils/utils';
import { makeSingleton } from '@singletons';
import { AddonModDataAction, AddonModDataEntryWSField } from './data';
import { AddonModDataEntryDBRecord, DATA_ENTRY_TABLE } from './database/data';
/**
* Service to handle Offline data.
*/
@Injectable({ providedIn: 'root' })
export class AddonModDataOfflineProvider {
/**
* Delete all the actions of an entry.
*
* @param dataId Database ID.
* @param entryId Database entry ID.
* @param siteId Site ID. If not defined, current site.
* @return Promise resolved if deleted, rejected if failure.
*/
async deleteAllEntryActions(dataId: number, entryId: number, siteId?: string): Promise<void> {
const actions = await this.getEntryActions(dataId, entryId, siteId);
const promises = actions.map((action) => {
this.deleteEntry(dataId, entryId, action.action, siteId);
});
await Promise.all(promises);
}
/**
* Delete an stored entry.
*
* @param dataId Database ID.
* @param entryId Database entry Id.
* @param action Action to be done
* @param siteId Site ID. If not defined, current site.
* @return Promise resolved if deleted, rejected if failure.
*/
async deleteEntry(dataId: number, entryId: number, action: AddonModDataAction, siteId?: string): Promise<void> {
const site = await CoreSites.getSite(siteId);
await this.deleteEntryFiles(dataId, entryId, action, site.id);
await site.getDb().deleteRecords(DATA_ENTRY_TABLE, {
dataid: dataId,
entryid: entryId,
action,
});
}
/**
* Delete entry offline files.
*
* @param dataId Database ID.
* @param entryId Database entry ID.
* @param action Action to be done.
* @param siteId Site ID. If not defined, current site.
* @return Promise resolved if deleted, rejected if failure.
*/
protected async deleteEntryFiles(dataId: number, entryId: number, action: AddonModDataAction, siteId?: string): Promise<void> {
const site = await CoreSites.getSite(siteId);
const entry = await CoreUtils.ignoreErrors(this.getEntry(dataId, entryId, action, site.id));
if (!entry || !entry.fields) {
// Entry not found or no fields, ignore.
return;
}
const promises: Promise<void>[] = [];
entry.fields.forEach((field) => {
const value = CoreTextUtils.parseJSON<CoreFileUploaderStoreFilesResult | null>(field.value, null);
if (!value || !value.offline) {
return;
}
const promise = this.getEntryFieldFolder(dataId, entryId, field.fieldid, site.id).then((folderPath) =>
CoreFileUploader.getStoredFiles(folderPath)).then((files) =>
CoreFileUploader.clearTmpFiles(files)).catch(() => { // Files not found, ignore.
});
promises.push(promise);
});
await Promise.all(promises);
}
/**
* Get all the stored entry data from all the databases.
*
* @param siteId Site ID. If not defined, current site.
* @return Promise resolved with entries.
*/
async getAllEntries(siteId?: string): Promise<AddonModDataOfflineAction[]> {
const site = await CoreSites.getSite(siteId);
const entries = await site.getDb().getAllRecords<AddonModDataEntryDBRecord>(DATA_ENTRY_TABLE);
return entries.map(this.parseRecord.bind(this));
}
/**
* Get all the stored entry actions from a certain database, sorted by modification time.
*
* @param dataId Database ID.
* @param siteId Site ID. If not defined, current site.
* @return Promise resolved with entries.
*/
async getDatabaseEntries(dataId: number, siteId?: string): Promise<AddonModDataOfflineAction[]> {
const site = await CoreSites.getSite(siteId);
const entries = await site.getDb().getRecords<AddonModDataEntryDBRecord>(
DATA_ENTRY_TABLE,
{ dataid: dataId },
'timemodified',
);
return entries.map(this.parseRecord.bind(this));
}
/**
* Get an stored entry data.
*
* @param dataId Database ID.
* @param entryId Database entry Id.
* @param action Action to be done
* @param siteId Site ID. If not defined, current site.
* @return Promise resolved with entry.
*/
async getEntry(
dataId: number,
entryId: number,
action: AddonModDataAction,
siteId?: string,
): Promise<AddonModDataOfflineAction> {
const site = await CoreSites.getSite(siteId);
const entry = await site.getDb().getRecord<AddonModDataEntryDBRecord>(DATA_ENTRY_TABLE, {
dataid: dataId, entryid: entryId,
action,
});
return this.parseRecord(entry);
}
/**
* Get an all stored entry actions data.
*
* @param dataId Database ID.
* @param entryId Database entry Id.
* @param siteId Site ID. If not defined, current site.
* @return Promise resolved with entry actions.
*/
async getEntryActions(dataId: number, entryId: number, siteId?: string): Promise<AddonModDataOfflineAction[]> {
const site = await CoreSites.getSite(siteId);
const entries = await site.getDb().getRecords<AddonModDataEntryDBRecord>(
DATA_ENTRY_TABLE,
{ dataid: dataId, entryid: entryId },
);
return entries.map(this.parseRecord.bind(this));
}
/**
* Check if there are offline entries to send.
*
* @param dataId Database ID.
* @param siteId Site ID. If not defined, current site.
* @return Promise resolved with boolean: true if has offline answers, false otherwise.
*/
async hasOfflineData(dataId: number, siteId?: string): Promise<boolean> {
const site = await CoreSites.getSite(siteId);
return CoreUtils.promiseWorks(
site.getDb().recordExists(DATA_ENTRY_TABLE, { dataid: dataId }),
);
}
/**
* Get the path to the folder where to store files for offline files in a database.
*
* @param dataId Database ID.
* @param siteId Site ID. If not defined, current site.
* @return Promise resolved with the path.
*/
protected async getDatabaseFolder(dataId: number, siteId?: string): Promise<string> {
const site = await CoreSites.getSite(siteId);
const siteFolderPath = CoreFile.getSiteFolder(site.getId());
const folderPath = 'offlinedatabase/' + dataId;
return CoreTextUtils.concatenatePaths(siteFolderPath, folderPath);
}
/**
* Get the path to the folder where to store files for a new offline entry.
*
* @param dataId Database ID.
* @param entryId The ID of the entry.
* @param fieldId Field ID.
* @param siteId Site ID. If not defined, current site.
* @return Promise resolved with the path.
*/
async getEntryFieldFolder(dataId: number, entryId: number, fieldId: number, siteId?: string): Promise<string> {
const folderPath = await this.getDatabaseFolder(dataId, siteId);
return CoreTextUtils.concatenatePaths(folderPath, entryId + '_' + fieldId);
}
/**
* Parse "fields" of an offline record.
*
* @param record Record object
* @return Record object with columns parsed.
*/
protected parseRecord(record: AddonModDataEntryDBRecord): AddonModDataOfflineAction {
return Object.assign(record, {
fields: CoreTextUtils.parseJSON<AddonModDataEntryWSField[]>(record.fields),
});
}
/**
* Save an entry data to be sent later.
*
* @param dataId Database ID.
* @param entryId Database entry Id. If action is add entryId should be 0 and -timemodified will be used.
* @param action Action to be done to the entry: [add, edit, delete, approve, disapprove]
* @param courseId Course ID of the database.
* @param groupId Group ID. Only provided when adding.
* @param fields Array of field data of the entry if needed.
* @param timemodified The time the entry was modified. If not defined, current time.
* @param siteId Site ID. If not defined, current site.
* @return Promise resolved if stored, rejected if failure.
*/
async saveEntry(
dataId: number,
entryId: number,
action: AddonModDataAction,
courseId: number,
groupId = 0,
fields?: AddonModDataEntryWSField[],
timemodified?: number,
siteId?: string,
): Promise<AddonModDataEntryDBRecord> {
const site = await CoreSites.getSite(siteId);
timemodified = timemodified || new Date().getTime();
entryId = entryId === undefined || entryId === null ? -timemodified : entryId;
const entry: AddonModDataEntryDBRecord = {
dataid: dataId,
courseid: courseId,
groupid: groupId,
action,
entryid: entryId,
fields: JSON.stringify(fields || []),
timemodified,
};
await site.getDb().insertRecord(DATA_ENTRY_TABLE, entry);
return entry;
}
}
export const AddonModDataOffline = makeSingleton(AddonModDataOfflineProvider);
/**
* Entry action stored offline.
*/
export type AddonModDataOfflineAction = Omit<AddonModDataEntryDBRecord, 'fields'> & {
fields: AddonModDataEntryWSField[];
};