2021-03-18 17:17:47 +01:00
|
|
|
// (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) => {
|
2021-06-21 09:11:51 +02:00
|
|
|
const value = CoreTextUtils.parseJSON<CoreFileUploaderStoreFilesResult | null>(field.value, null);
|
2021-03-18 17:17:47 +01:00
|
|
|
|
2021-06-21 09:11:51 +02:00
|
|
|
if (!value || !value.offline) {
|
2021-03-18 17:17:47 +01:00
|
|
|
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();
|
2021-12-16 10:46:40 +01:00
|
|
|
entryId = entryId === undefined || entryId === null ? -timemodified : entryId;
|
2021-03-18 17:17:47 +01:00
|
|
|
|
|
|
|
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[];
|
|
|
|
};
|