commit
df7c68b07c
|
@ -2224,6 +2224,21 @@
|
|||
"@types/cordova": "^0.0.34"
|
||||
}
|
||||
},
|
||||
"@ionic-native/ionic-webview": {
|
||||
"version": "5.28.0",
|
||||
"resolved": "https://registry.npmjs.org/@ionic-native/ionic-webview/-/ionic-webview-5.28.0.tgz",
|
||||
"integrity": "sha512-Ex/IH/LIa+4X4yGFgo4/W00IWaVsF6KkZuwIG2s3zZQEgXU3tvcgxAOEzkNCbcDC5dXcFH0z/41twZ+YC6gu+A==",
|
||||
"requires": {
|
||||
"@types/cordova": "^0.0.34"
|
||||
},
|
||||
"dependencies": {
|
||||
"@types/cordova": {
|
||||
"version": "0.0.34",
|
||||
"resolved": "https://registry.npmjs.org/@types/cordova/-/cordova-0.0.34.tgz",
|
||||
"integrity": "sha1-6nrd907Ow9dimCegw54smt3HPQQ="
|
||||
}
|
||||
}
|
||||
},
|
||||
"@ionic-native/keyboard": {
|
||||
"version": "5.28.0",
|
||||
"resolved": "https://registry.npmjs.org/@ionic-native/keyboard/-/keyboard-5.28.0.tgz",
|
||||
|
@ -3053,6 +3068,11 @@
|
|||
"cordova-plugin-file-transfer": "*"
|
||||
}
|
||||
},
|
||||
"@types/dom-mediacapture-record": {
|
||||
"version": "1.0.7",
|
||||
"resolved": "https://registry.npmjs.org/@types/dom-mediacapture-record/-/dom-mediacapture-record-1.0.7.tgz",
|
||||
"integrity": "sha512-ddDIRTO1ajtbxaNo2o7fPJggpN54PZf1ZUJKOjto2ENMJE/9GKUvaw3ZRuQzlS/p0E+PnIcssxfoqYJ4yiXSBw=="
|
||||
},
|
||||
"@types/glob": {
|
||||
"version": "7.1.3",
|
||||
"resolved": "https://registry.npmjs.org/@types/glob/-/glob-7.1.3.tgz",
|
||||
|
|
|
@ -45,6 +45,7 @@
|
|||
"@ionic-native/globalization": "^5.28.0",
|
||||
"@ionic-native/http": "^5.28.0",
|
||||
"@ionic-native/in-app-browser": "^5.28.0",
|
||||
"@ionic-native/ionic-webview": "^5.28.0",
|
||||
"@ionic-native/keyboard": "^5.28.0",
|
||||
"@ionic-native/local-notifications": "^5.28.0",
|
||||
"@ionic-native/network": "^5.28.0",
|
||||
|
@ -60,6 +61,7 @@
|
|||
"@ngx-translate/http-loader": "^6.0.0",
|
||||
"@types/cordova": "0.0.34",
|
||||
"@types/cordova-plugin-file-transfer": "^1.6.2",
|
||||
"@types/dom-mediacapture-record": "^1.0.7",
|
||||
"com-darryncampbell-cordova-plugin-intent": "^2.0.0",
|
||||
"cordova": "^10.0.0",
|
||||
"cordova-android": "^8.1.0",
|
||||
|
|
|
@ -15,11 +15,13 @@
|
|||
import { NgModule, Injector } from '@angular/core';
|
||||
import { BrowserModule } from '@angular/platform-browser';
|
||||
import { RouteReuseStrategy } from '@angular/router';
|
||||
import { HttpClient, HttpClientModule, HTTP_INTERCEPTORS } from '@angular/common/http';
|
||||
|
||||
import { IonicModule, IonicRouteStrategy, Platform } from '@ionic/angular';
|
||||
|
||||
import { AppComponent } from './app.component';
|
||||
import { AppRoutingModule } from './app-routing.module';
|
||||
import { CoreInterceptor } from '@classes/interceptor';
|
||||
|
||||
// Import core services.
|
||||
import { CoreAppProvider } from '@services/app';
|
||||
|
@ -49,23 +51,41 @@ import { CoreTimeUtilsProvider } from '@services/utils/time';
|
|||
import { CoreUrlUtilsProvider } from '@services/utils/url';
|
||||
import { CoreUtilsProvider } from '@services/utils/utils';
|
||||
|
||||
// Import core modules.
|
||||
import { CoreEmulatorModule } from '@core/emulator/emulator.module';
|
||||
import { CoreLoginModule } from '@core/login/login.module';
|
||||
|
||||
import { setSingletonsInjector } from '@singletons/core.singletons';
|
||||
|
||||
import { TranslateModule, TranslateLoader } from '@ngx-translate/core';
|
||||
import { TranslateHttpLoader } from '@ngx-translate/http-loader';
|
||||
|
||||
// For translate loader. AoT requires an exported function for factories.
|
||||
export function createTranslateLoader(http: HttpClient): TranslateHttpLoader {
|
||||
return new TranslateHttpLoader(http, './assets/lang/', '.json');
|
||||
}
|
||||
|
||||
@NgModule({
|
||||
declarations: [AppComponent],
|
||||
entryComponents: [],
|
||||
imports: [
|
||||
BrowserModule,
|
||||
IonicModule.forRoot(),
|
||||
HttpClientModule, // HttpClient is used to make JSON requests. It fails for HEAD requests because there is no content.
|
||||
TranslateModule.forRoot({
|
||||
loader: {
|
||||
provide: TranslateLoader,
|
||||
useFactory: (createTranslateLoader),
|
||||
deps: [HttpClient],
|
||||
},
|
||||
}),
|
||||
AppRoutingModule,
|
||||
CoreEmulatorModule,
|
||||
CoreLoginModule,
|
||||
],
|
||||
providers: [
|
||||
{ provide: RouteReuseStrategy, useClass: IonicRouteStrategy },
|
||||
{ provide: HTTP_INTERCEPTORS, useClass: CoreInterceptor, multi: true },
|
||||
CoreAppProvider,
|
||||
CoreConfigProvider,
|
||||
CoreCronDelegate,
|
||||
|
@ -96,8 +116,8 @@ import { setSingletonsInjector } from '@singletons/core.singletons';
|
|||
bootstrap: [AppComponent],
|
||||
})
|
||||
export class AppModule {
|
||||
constructor(injector: Injector, platform: Platform) {
|
||||
|
||||
constructor(injector: Injector, platform: Platform) {
|
||||
// Set the injector.
|
||||
setSingletonsInjector(injector);
|
||||
|
||||
|
@ -133,4 +153,5 @@ export class AppModule {
|
|||
// Execute the init processes.
|
||||
CoreInit.instance.executeInitProcesses();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -78,7 +78,7 @@ export class CoreDelegate {
|
|||
/**
|
||||
* Function to resolve the handlers init promise.
|
||||
*/
|
||||
protected handlersInitResolve: (value?: any) => void;
|
||||
protected handlersInitResolve: () => void;
|
||||
|
||||
/**
|
||||
* Constructor of the Delegate.
|
||||
|
@ -110,8 +110,8 @@ export class CoreDelegate {
|
|||
* @param params Parameters to pass to the function.
|
||||
* @return Function returned value or default value.
|
||||
*/
|
||||
protected executeFunctionOnEnabled(handlerName: string, fnName: string, params?: any[]): any {
|
||||
return this.execute(this.enabledHandlers[handlerName], fnName, params);
|
||||
protected executeFunctionOnEnabled<T = unknown>(handlerName: string, fnName: string, params?: unknown[]): T {
|
||||
return this.execute<T>(this.enabledHandlers[handlerName], fnName, params);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -123,7 +123,7 @@ export class CoreDelegate {
|
|||
* @param params Parameters to pass to the function.
|
||||
* @return Function returned value or default value.
|
||||
*/
|
||||
protected executeFunction(handlerName: string, fnName: string, params?: any[]): any {
|
||||
protected executeFunction<T = unknown>(handlerName: string, fnName: string, params?: unknown[]): T {
|
||||
return this.execute(this.handlers[handlerName], fnName, params);
|
||||
}
|
||||
|
||||
|
@ -136,7 +136,7 @@ export class CoreDelegate {
|
|||
* @param params Parameters to pass to the function.
|
||||
* @return Function returned value or default value.
|
||||
*/
|
||||
private execute(handler: CoreDelegateHandler, fnName: string, params?: any[]): any {
|
||||
private execute<T = unknown>(handler: CoreDelegateHandler, fnName: string, params?: unknown[]): T {
|
||||
if (handler && handler[fnName]) {
|
||||
return handler[fnName].apply(handler, params);
|
||||
} else if (this.defaultHandler && this.defaultHandler[fnName]) {
|
||||
|
@ -180,10 +180,10 @@ export class CoreDelegate {
|
|||
* @param onlyEnabled If check only enabled handlers or all.
|
||||
* @return Function returned value or default value.
|
||||
*/
|
||||
protected hasFunction(handlerName: string, fnName: string, onlyEnabled: boolean = true): any {
|
||||
protected hasFunction(handlerName: string, fnName: string, onlyEnabled: boolean = true): boolean {
|
||||
const handler = onlyEnabled ? this.enabledHandlers[handlerName] : this.handlers[handlerName];
|
||||
|
||||
return handler && handler[fnName];
|
||||
return handler && typeof handler[fnName] == 'function';
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -240,7 +240,7 @@ export class CoreDelegate {
|
|||
* @param time Time this update process started.
|
||||
* @return Resolved when done.
|
||||
*/
|
||||
protected updateHandler(handler: CoreDelegateHandler, time: number): Promise<void> {
|
||||
protected updateHandler(handler: CoreDelegateHandler): Promise<void> {
|
||||
const siteId = CoreSites.instance.getCurrentSiteId();
|
||||
const currentSite = CoreSites.instance.getCurrentSite();
|
||||
let promise: Promise<boolean>;
|
||||
|
@ -255,7 +255,7 @@ export class CoreDelegate {
|
|||
if (!CoreSites.instance.isLoggedIn() || this.isFeatureDisabled(handler, currentSite)) {
|
||||
promise = Promise.resolve(false);
|
||||
} else {
|
||||
promise = handler.isEnabled().catch(() => false);
|
||||
promise = Promise.resolve(handler.isEnabled()).catch(() => false);
|
||||
}
|
||||
|
||||
// Checks if the handler is enabled.
|
||||
|
@ -304,7 +304,7 @@ export class CoreDelegate {
|
|||
|
||||
// Loop over all the handlers.
|
||||
for (const name in this.handlers) {
|
||||
promises.push(this.updateHandler(this.handlers[name], now));
|
||||
promises.push(this.updateHandler(this.handlers[name]));
|
||||
}
|
||||
|
||||
try {
|
||||
|
@ -326,7 +326,7 @@ export class CoreDelegate {
|
|||
* Update handlers Data.
|
||||
* Override this function to update handlers data.
|
||||
*/
|
||||
updateData(): any {
|
||||
updateData(): void {
|
||||
// To be overridden.
|
||||
}
|
||||
|
||||
|
|
|
@ -11,9 +11,10 @@
|
|||
// 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 Faker from 'faker';
|
||||
|
||||
import { CoreError } from './error';
|
||||
import { CoreError } from '@classes/errors/error';
|
||||
|
||||
describe('CoreError', () => {
|
||||
|
||||
|
|
|
@ -0,0 +1,30 @@
|
|||
// (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 { CoreError } from '@classes/errors/error';
|
||||
|
||||
/**
|
||||
* Generic error returned by an Ajax call.
|
||||
*/
|
||||
export class CoreAjaxError extends CoreError {
|
||||
|
||||
available?: number; // Whether the AJAX call is available. 0 if unknown, 1 if available, -1 if not available.
|
||||
|
||||
constructor(message: string, available?: number) {
|
||||
super(message);
|
||||
|
||||
this.available = typeof available == 'undefined' ? 0 : available;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,53 @@
|
|||
// (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 { CoreError } from '@classes/errors/error';
|
||||
|
||||
/**
|
||||
* Error returned by WS.
|
||||
*/
|
||||
export class CoreAjaxWSError extends CoreError {
|
||||
|
||||
exception?: string; // Name of the Moodle exception.
|
||||
errorcode?: string;
|
||||
warningcode?: string;
|
||||
link?: string; // Link to the site.
|
||||
moreinfourl?: string; // Link to a page with more info.
|
||||
debuginfo?: string; // Debug info. Only if debug mode is enabled.
|
||||
backtrace?: string; // Backtrace. Only if debug mode is enabled.
|
||||
available?: number; // Whether the AJAX call is available. 0 if unknown, 1 if available, -1 if not available.
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any
|
||||
constructor(error: any, available?: number) {
|
||||
super(error.message);
|
||||
|
||||
this.exception = error.exception;
|
||||
this.errorcode = error.errorcode;
|
||||
this.warningcode = error.warningcode;
|
||||
this.link = error.link;
|
||||
this.moreinfourl = error.moreinfourl;
|
||||
this.debuginfo = error.debuginfo;
|
||||
this.backtrace = error.backtrace;
|
||||
|
||||
this.available = available;
|
||||
if (typeof this.available == 'undefined') {
|
||||
if (this.errorcode) {
|
||||
this.available = this.errorcode == 'invalidrecord' ? -1 : 1;
|
||||
} else {
|
||||
this.available = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
// (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 { CoreSilentError } from '@classes/errors/silenterror';
|
||||
|
||||
/**
|
||||
* User canceled an action.
|
||||
*/
|
||||
export class CoreCanceledError extends CoreSilentError { }
|
|
@ -0,0 +1,20 @@
|
|||
// (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 { CoreError } from '@classes/errors/error';
|
||||
|
||||
/**
|
||||
* Error that won't be displayed to the user.
|
||||
*/
|
||||
export class CoreSilentError extends CoreError { }
|
|
@ -0,0 +1,41 @@
|
|||
// (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 { CoreError } from '@classes/errors/error';
|
||||
|
||||
/**
|
||||
* Error returned when performing operations regarding a site (check if it exists, authenticate user, etc.).
|
||||
*/
|
||||
export class CoreSiteError extends CoreError {
|
||||
|
||||
errorcode?: string;
|
||||
critical?: boolean;
|
||||
loggedOut?: boolean;
|
||||
|
||||
constructor(protected error: SiteError) {
|
||||
super(error.message);
|
||||
|
||||
this.errorcode = error.errorcode;
|
||||
this.critical = error.critical;
|
||||
this.loggedOut = error.loggedOut;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export type SiteError = {
|
||||
message: string;
|
||||
errorcode?: string;
|
||||
critical?: boolean; // Whether the error is important enough to abort the operation.
|
||||
loggedOut?: boolean; // Whether site has been marked as logged out.
|
||||
};
|
|
@ -0,0 +1,42 @@
|
|||
// (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 { CoreError } from '@classes/errors/error';
|
||||
|
||||
/**
|
||||
* Error returned by WS.
|
||||
*/
|
||||
export class CoreWSError extends CoreError {
|
||||
|
||||
exception?: string; // Name of the Moodle exception.
|
||||
errorcode?: string;
|
||||
warningcode?: string;
|
||||
link?: string; // Link to the site.
|
||||
moreinfourl?: string; // Link to a page with more info.
|
||||
debuginfo?: string; // Debug info. Only if debug mode is enabled.
|
||||
backtrace?: string; // Backtrace. Only if debug mode is enabled.
|
||||
|
||||
constructor(error: any) {
|
||||
super(error.message);
|
||||
|
||||
this.exception = error.exception;
|
||||
this.errorcode = error.errorcode;
|
||||
this.warningcode = error.warningcode;
|
||||
this.link = error.link;
|
||||
this.moreinfourl = error.moreinfourl;
|
||||
this.debuginfo = error.debuginfo;
|
||||
this.backtrace = error.backtrace;
|
||||
}
|
||||
|
||||
}
|
|
@ -30,28 +30,26 @@ export class CoreInterceptor implements HttpInterceptor {
|
|||
* @param addNull Add null values to the serialized as empty parameters.
|
||||
* @return Serialization of the object.
|
||||
*/
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any
|
||||
static serialize(obj: any, addNull?: boolean): string {
|
||||
let query = '';
|
||||
let fullSubName: string;
|
||||
let subValue;
|
||||
let innerObj;
|
||||
|
||||
for (const name in obj) {
|
||||
const value = obj[name];
|
||||
|
||||
if (value instanceof Array) {
|
||||
for (let i = 0; i < value.length; ++i) {
|
||||
subValue = value[i];
|
||||
fullSubName = name + '[' + i + ']';
|
||||
innerObj = {};
|
||||
const subValue = value[i];
|
||||
const fullSubName = name + '[' + i + ']';
|
||||
const innerObj = {};
|
||||
innerObj[fullSubName] = subValue;
|
||||
query += this.serialize(innerObj) + '&';
|
||||
}
|
||||
} else if (value instanceof Object) {
|
||||
for (const subName in value) {
|
||||
subValue = value[subName];
|
||||
fullSubName = name + '[' + subName + ']';
|
||||
innerObj = {};
|
||||
const subValue = value[subName];
|
||||
const fullSubName = name + '[' + subName + ']';
|
||||
const innerObj = {};
|
||||
innerObj[fullSubName] = subValue;
|
||||
query += this.serialize(innerObj) + '&';
|
||||
}
|
||||
|
@ -63,6 +61,7 @@ export class CoreInterceptor implements HttpInterceptor {
|
|||
return query.length ? query.substr(0, query.length - 1) : query;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any
|
||||
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<any> {
|
||||
// Add the header and serialize the body if needed.
|
||||
const newReq = req.clone({
|
||||
|
|
|
@ -0,0 +1,55 @@
|
|||
// (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 { CoreUtils } from '@services/utils/utils';
|
||||
|
||||
/**
|
||||
* Class to improve the behaviour of HTMLIonLoadingElement.
|
||||
* It's not a subclass of HTMLIonLoadingElement because we cannot override the dismiss function.
|
||||
*/
|
||||
export class CoreIonLoadingElement {
|
||||
|
||||
protected isPresented = false;
|
||||
protected isDismissed = false;
|
||||
|
||||
constructor(public loading: HTMLIonLoadingElement) { }
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any
|
||||
async dismiss(data?: any, role?: string): Promise<boolean> {
|
||||
if (!this.isPresented || this.isDismissed) {
|
||||
this.isDismissed = true;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
this.isDismissed = true;
|
||||
|
||||
return this.loading.dismiss(data, role);
|
||||
}
|
||||
|
||||
/**
|
||||
* Present the loading.
|
||||
*/
|
||||
async present(): Promise<void> {
|
||||
// Wait a bit before presenting the modal, to prevent it being displayed if dissmiss is called fast.
|
||||
await CoreUtils.instance.wait(40);
|
||||
|
||||
if (!this.isDismissed) {
|
||||
this.isPresented = true;
|
||||
|
||||
await this.loading.present();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -17,11 +17,13 @@ import { CoreUtils, PromiseDefer } from '@services/utils/utils';
|
|||
/**
|
||||
* Function to add to the queue.
|
||||
*/
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
export type CoreQueueRunnerFunction<T> = (...args: any[]) => T | Promise<T>;
|
||||
|
||||
/**
|
||||
* Queue item.
|
||||
*/
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
export type CoreQueueRunnerItem<T = any> = {
|
||||
/**
|
||||
* Item ID.
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -15,6 +15,7 @@
|
|||
import { SQLiteObject } from '@ionic-native/sqlite/ngx';
|
||||
|
||||
import { SQLite, Platform } from '@singletons/core.singletons';
|
||||
import { CoreError } from '@classes/errors/error';
|
||||
|
||||
/**
|
||||
* Schema of a table.
|
||||
|
@ -411,6 +412,7 @@ export class SQLiteDB {
|
|||
* @param params Query parameters.
|
||||
* @return Promise resolved with the result.
|
||||
*/
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
async execute(sql: string, params?: SQLiteDBRecordValue[]): Promise<any> {
|
||||
await this.ready();
|
||||
|
||||
|
@ -425,7 +427,8 @@ export class SQLiteDB {
|
|||
* @param sqlStatements SQL statements to execute.
|
||||
* @return Promise resolved with the result.
|
||||
*/
|
||||
async executeBatch(sqlStatements: (string | SQLiteDBRecordValue[])[][]): Promise<void> {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
async executeBatch(sqlStatements: (string | string[] | any)[]): Promise<void> {
|
||||
await this.ready();
|
||||
|
||||
await this.db.sqlBatch(sqlStatements);
|
||||
|
@ -453,9 +456,10 @@ export class SQLiteDB {
|
|||
* Format the data to where params.
|
||||
*
|
||||
* @param data Object data.
|
||||
* @return List of params.
|
||||
*/
|
||||
protected formatDataToSQLParams(data: SQLiteDBRecordValues): SQLiteDBRecordValue[] {
|
||||
return Object.keys(data).map((key) => data[key]);
|
||||
return Object.keys(data).map((key) => data[key]);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -464,7 +468,7 @@ export class SQLiteDB {
|
|||
* @param table The table to query.
|
||||
* @return Promise resolved with the records.
|
||||
*/
|
||||
async getAllRecords(table: string): Promise<SQLiteDBRecordValues[]> {
|
||||
async getAllRecords<T = unknown>(table: string): Promise<T[]> {
|
||||
return this.getRecords(table);
|
||||
}
|
||||
|
||||
|
@ -510,7 +514,7 @@ export class SQLiteDB {
|
|||
async getFieldSql(sql: string, params?: SQLiteDBRecordValue[]): Promise<SQLiteDBRecordValue> {
|
||||
const record = await this.getRecordSql(sql, params);
|
||||
if (!record) {
|
||||
throw null;
|
||||
throw new CoreError('No record found.');
|
||||
}
|
||||
|
||||
return record[Object.keys(record)[0]];
|
||||
|
@ -574,10 +578,10 @@ export class SQLiteDB {
|
|||
* @param fields A comma separated list of fields to return.
|
||||
* @return Promise resolved with the record, rejected if not found.
|
||||
*/
|
||||
getRecord(table: string, conditions?: SQLiteDBRecordValues, fields: string = '*'): Promise<SQLiteDBRecordValues> {
|
||||
getRecord<T = unknown>(table: string, conditions?: SQLiteDBRecordValues, fields: string = '*'): Promise<T> {
|
||||
const selectAndParams = this.whereClause(conditions);
|
||||
|
||||
return this.getRecordSelect(table, selectAndParams[0], selectAndParams[1], fields);
|
||||
return this.getRecordSelect<T>(table, selectAndParams[0], selectAndParams[1], fields);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -589,13 +593,13 @@ export class SQLiteDB {
|
|||
* @param fields A comma separated list of fields to return.
|
||||
* @return Promise resolved with the record, rejected if not found.
|
||||
*/
|
||||
getRecordSelect(table: string, select: string = '', params: SQLiteDBRecordValue[] = [], fields: string = '*'):
|
||||
Promise<SQLiteDBRecordValues> {
|
||||
getRecordSelect<T = unknown>(table: string, select: string = '', params: SQLiteDBRecordValue[] = [], fields: string = '*'):
|
||||
Promise<T> {
|
||||
if (select) {
|
||||
select = ' WHERE ' + select;
|
||||
}
|
||||
|
||||
return this.getRecordSql(`SELECT ${fields} FROM ${table} ${select}`, params);
|
||||
return this.getRecordSql<T>(`SELECT ${fields} FROM ${table} ${select}`, params);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -608,11 +612,11 @@ export class SQLiteDB {
|
|||
* @param params List of sql parameters
|
||||
* @return Promise resolved with the records.
|
||||
*/
|
||||
async getRecordSql(sql: string, params?: SQLiteDBRecordValue[]): Promise<SQLiteDBRecordValues> {
|
||||
const result = await this.getRecordsSql(sql, params, 0, 1);
|
||||
async getRecordSql<T = unknown>(sql: string, params?: SQLiteDBRecordValue[]): Promise<T> {
|
||||
const result = await this.getRecordsSql<T>(sql, params, 0, 1);
|
||||
if (!result || !result.length) {
|
||||
// Not found, reject.
|
||||
throw null;
|
||||
throw new CoreError('No records found.');
|
||||
}
|
||||
|
||||
return result[0];
|
||||
|
@ -629,11 +633,11 @@ export class SQLiteDB {
|
|||
* @param limitNum Return a subset comprising this many records in total.
|
||||
* @return Promise resolved with the records.
|
||||
*/
|
||||
getRecords(table: string, conditions?: SQLiteDBRecordValues, sort: string = '', fields: string = '*', limitFrom: number = 0,
|
||||
limitNum: number = 0): Promise<SQLiteDBRecordValues[]> {
|
||||
getRecords<T = unknown>(table: string, conditions?: SQLiteDBRecordValues, sort: string = '', fields: string = '*',
|
||||
limitFrom: number = 0, limitNum: number = 0): Promise<T[]> {
|
||||
const selectAndParams = this.whereClause(conditions);
|
||||
|
||||
return this.getRecordsSelect(table, selectAndParams[0], selectAndParams[1], sort, fields, limitFrom, limitNum);
|
||||
return this.getRecordsSelect<T>(table, selectAndParams[0], selectAndParams[1], sort, fields, limitFrom, limitNum);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -648,11 +652,11 @@ export class SQLiteDB {
|
|||
* @param limitNum Return a subset comprising this many records in total.
|
||||
* @return Promise resolved with the records.
|
||||
*/
|
||||
getRecordsList(table: string, field: string, values: SQLiteDBRecordValue[], sort: string = '', fields: string = '*',
|
||||
limitFrom: number = 0, limitNum: number = 0): Promise<SQLiteDBRecordValues[]> {
|
||||
getRecordsList<T = unknown>(table: string, field: string, values: SQLiteDBRecordValue[], sort: string = '',
|
||||
fields: string = '*', limitFrom: number = 0, limitNum: number = 0): Promise<T[]> {
|
||||
const selectAndParams = this.whereClauseList(field, values);
|
||||
|
||||
return this.getRecordsSelect(table, selectAndParams[0], selectAndParams[1], sort, fields, limitFrom, limitNum);
|
||||
return this.getRecordsSelect<T>(table, selectAndParams[0], selectAndParams[1], sort, fields, limitFrom, limitNum);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -667,8 +671,8 @@ export class SQLiteDB {
|
|||
* @param limitNum Return a subset comprising this many records in total.
|
||||
* @return Promise resolved with the records.
|
||||
*/
|
||||
getRecordsSelect(table: string, select: string = '', params: SQLiteDBRecordValue[] = [], sort: string = '',
|
||||
fields: string = '*', limitFrom: number = 0, limitNum: number = 0): Promise<SQLiteDBRecordValues[]> {
|
||||
getRecordsSelect<T = unknown>(table: string, select: string = '', params: SQLiteDBRecordValue[] = [], sort: string = '',
|
||||
fields: string = '*', limitFrom: number = 0, limitNum: number = 0): Promise<T[]> {
|
||||
if (select) {
|
||||
select = ' WHERE ' + select;
|
||||
}
|
||||
|
@ -678,7 +682,7 @@ export class SQLiteDB {
|
|||
|
||||
const sql = `SELECT ${fields} FROM ${table} ${select} ${sort}`;
|
||||
|
||||
return this.getRecordsSql(sql, params, limitFrom, limitNum);
|
||||
return this.getRecordsSql<T>(sql, params, limitFrom, limitNum);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -690,8 +694,8 @@ export class SQLiteDB {
|
|||
* @param limitNum Return a subset comprising this many records.
|
||||
* @return Promise resolved with the records.
|
||||
*/
|
||||
async getRecordsSql(sql: string, params?: SQLiteDBRecordValue[], limitFrom?: number, limitNum?: number):
|
||||
Promise<SQLiteDBRecordValues[]> {
|
||||
async getRecordsSql<T = unknown>(sql: string, params?: SQLiteDBRecordValue[], limitFrom?: number, limitNum?: number):
|
||||
Promise<T[]> {
|
||||
const limits = this.normaliseLimitFromNum(limitFrom, limitNum);
|
||||
|
||||
if (limits[0] || limits[1]) {
|
||||
|
@ -768,7 +772,7 @@ export class SQLiteDB {
|
|||
*/
|
||||
async insertRecords(table: string, dataObjects: SQLiteDBRecordValues[]): Promise<void> {
|
||||
if (!Array.isArray(dataObjects)) {
|
||||
throw null;
|
||||
throw new CoreError('Invalid parameter supplied to insertRecords, it should be an array.');
|
||||
}
|
||||
|
||||
const statements = dataObjects.map((dataObject) => {
|
||||
|
@ -854,7 +858,7 @@ export class SQLiteDB {
|
|||
async recordExists(table: string, conditions?: SQLiteDBRecordValues): Promise<void> {
|
||||
const record = await this.getRecord(table, conditions);
|
||||
if (!record) {
|
||||
throw null;
|
||||
throw new CoreError('Record does not exist.');
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -869,7 +873,7 @@ export class SQLiteDB {
|
|||
async recordExistsSelect(table: string, select: string = '', params: SQLiteDBRecordValue[] = []): Promise<void> {
|
||||
const record = await this.getRecordSelect(table, select, params);
|
||||
if (!record) {
|
||||
throw null;
|
||||
throw new CoreError('Record does not exist.');
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -883,7 +887,7 @@ export class SQLiteDB {
|
|||
async recordExistsSql(sql: string, params?: SQLiteDBRecordValue[]): Promise<void> {
|
||||
const record = await this.getRecordSql(sql, params);
|
||||
if (!record) {
|
||||
throw null;
|
||||
throw new CoreError('Record does not exist.');
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -23,7 +23,9 @@ import { FileOpener } from '@ionic-native/file-opener/ngx';
|
|||
import { FileTransfer } from '@ionic-native/file-transfer/ngx';
|
||||
import { Geolocation } from '@ionic-native/geolocation/ngx';
|
||||
import { Globalization } from '@ionic-native/globalization/ngx';
|
||||
import { HTTP } from '@ionic-native/http/ngx';
|
||||
import { InAppBrowser } from '@ionic-native/in-app-browser/ngx';
|
||||
import { WebView } from '@ionic-native/ionic-webview/ngx';
|
||||
import { Keyboard } from '@ionic-native/keyboard/ngx';
|
||||
import { LocalNotifications } from '@ionic-native/local-notifications/ngx';
|
||||
import { Network } from '@ionic-native/network/ngx';
|
||||
|
@ -58,6 +60,7 @@ import { Zip } from '@ionic-native/zip/ngx';
|
|||
FileTransfer,
|
||||
Geolocation,
|
||||
Globalization,
|
||||
HTTP,
|
||||
InAppBrowser,
|
||||
Keyboard,
|
||||
LocalNotifications,
|
||||
|
@ -68,6 +71,7 @@ import { Zip } from '@ionic-native/zip/ngx';
|
|||
SQLite,
|
||||
StatusBar,
|
||||
WebIntent,
|
||||
WebView,
|
||||
Zip,
|
||||
],
|
||||
})
|
||||
|
|
|
@ -21,6 +21,7 @@ import { Pipe, PipeTransform } from '@angular/core';
|
|||
name: 'coreCreateLinks',
|
||||
})
|
||||
export class CoreCreateLinksPipe implements PipeTransform {
|
||||
|
||||
protected static replacePattern = /(\b(https?|ftp):\/\/[-A-Z0-9+&@#\/%?=~_|!:,.;]*[-A-Z0-9+&@#\/%=~_|])(?![^<]*>|[^<>]*<\/)/gim;
|
||||
|
||||
/**
|
||||
|
@ -32,4 +33,5 @@ export class CoreCreateLinksPipe implements PipeTransform {
|
|||
transform(text: string): string {
|
||||
return text.replace(CoreCreateLinksPipe.replacePattern, '<a href="$1">$1</a>');
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -31,4 +31,5 @@ export class CoreNoTagsPipe implements PipeTransform {
|
|||
transform(text: string): string {
|
||||
return text.replace(/(<([^>]+)>)/ig, '');
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -28,6 +28,6 @@ import { CoreTimeAgoPipe } from './time-ago.pipe';
|
|||
CoreCreateLinksPipe,
|
||||
CoreNoTagsPipe,
|
||||
CoreTimeAgoPipe,
|
||||
]
|
||||
],
|
||||
})
|
||||
export class CorePipesModule {}
|
||||
|
|
|
@ -24,6 +24,7 @@ import moment from 'moment';
|
|||
name: 'coreTimeAgo',
|
||||
})
|
||||
export class CoreTimeAgoPipe implements PipeTransform {
|
||||
|
||||
private logger: CoreLogger;
|
||||
|
||||
constructor() {
|
||||
|
@ -48,6 +49,7 @@ export class CoreTimeAgoPipe implements PipeTransform {
|
|||
timestamp = numberTimestamp;
|
||||
}
|
||||
|
||||
return Translate.instance.instant('core.ago', {$a: moment(timestamp * 1000).fromNow(true)});
|
||||
return Translate.instance.instant('core.ago', { $a: moment(timestamp * 1000).fromNow(true) });
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -17,12 +17,16 @@ import { Connection } from '@ionic-native/network/ngx';
|
|||
|
||||
import { CoreDB } from '@services/db';
|
||||
import { CoreEvents, CoreEventsProvider } from '@services/events';
|
||||
import { CoreUtils, PromiseDefer } from '@services/utils/utils';
|
||||
import { SQLiteDB, SQLiteDBTableSchema } from '@classes/sqlitedb';
|
||||
import CoreConfigConstants from '@app/config.json';
|
||||
|
||||
import { makeSingleton, Keyboard, Network, StatusBar, Platform } from '@singletons/core.singletons';
|
||||
import { CoreLogger } from '@singletons/logger';
|
||||
|
||||
const DBNAME = 'MoodleMobile';
|
||||
const SCHEMA_VERSIONS_TABLE = 'schema_versions';
|
||||
|
||||
/**
|
||||
* Factory to provide some global functionalities, like access to the global app database.
|
||||
*
|
||||
|
@ -38,23 +42,22 @@ import { CoreLogger } from '@singletons/logger';
|
|||
*/
|
||||
@Injectable()
|
||||
export class CoreAppProvider {
|
||||
protected DBNAME = 'MoodleMobile';
|
||||
|
||||
protected db: SQLiteDB;
|
||||
protected logger: CoreLogger;
|
||||
protected ssoAuthenticationPromise: Promise<any>;
|
||||
protected ssoAuthenticationDeferred: PromiseDefer<void>;
|
||||
protected isKeyboardShown = false;
|
||||
protected _isKeyboardOpening = false;
|
||||
protected _isKeyboardClosing = false;
|
||||
protected backActions = [];
|
||||
protected keyboardOpening = false;
|
||||
protected keyboardClosing = false;
|
||||
protected backActions: {callback: () => boolean; priority: number}[] = [];
|
||||
protected mainMenuId = 0;
|
||||
protected mainMenuOpen: number;
|
||||
protected forceOffline = false;
|
||||
|
||||
// Variables for DB.
|
||||
protected createVersionsTableReady: Promise<any>;
|
||||
protected SCHEMA_VERSIONS_TABLE = 'schema_versions';
|
||||
protected createVersionsTableReady: Promise<void>;
|
||||
protected versionsTableSchema: SQLiteDBTableSchema = {
|
||||
name: this.SCHEMA_VERSIONS_TABLE,
|
||||
name: SCHEMA_VERSIONS_TABLE,
|
||||
columns: [
|
||||
{
|
||||
name: 'name',
|
||||
|
@ -68,11 +71,9 @@ export class CoreAppProvider {
|
|||
],
|
||||
};
|
||||
|
||||
constructor(appRef: ApplicationRef,
|
||||
zone: NgZone) {
|
||||
|
||||
constructor(appRef: ApplicationRef, zone: NgZone) {
|
||||
this.logger = CoreLogger.getInstance('CoreAppProvider');
|
||||
this.db = CoreDB.instance.getDB(this.DBNAME);
|
||||
this.db = CoreDB.instance.getDB(DBNAME);
|
||||
|
||||
// Create the schema versions table.
|
||||
this.createVersionsTableReady = this.db.createTableFromSchema(this.versionsTableSchema);
|
||||
|
@ -87,7 +88,7 @@ export class CoreAppProvider {
|
|||
CoreEvents.instance.trigger(CoreEventsProvider.KEYBOARD_CHANGE, data.keyboardHeight);
|
||||
});
|
||||
});
|
||||
Keyboard.instance.onKeyboardHide().subscribe((data) => {
|
||||
Keyboard.instance.onKeyboardHide().subscribe(() => {
|
||||
// Execute the callback in the Angular zone, so change detection doesn't stop working.
|
||||
zone.run(() => {
|
||||
document.body.classList.remove('keyboard-is-open');
|
||||
|
@ -95,18 +96,18 @@ export class CoreAppProvider {
|
|||
CoreEvents.instance.trigger(CoreEventsProvider.KEYBOARD_CHANGE, 0);
|
||||
});
|
||||
});
|
||||
Keyboard.instance.onKeyboardWillShow().subscribe((data) => {
|
||||
Keyboard.instance.onKeyboardWillShow().subscribe(() => {
|
||||
// Execute the callback in the Angular zone, so change detection doesn't stop working.
|
||||
zone.run(() => {
|
||||
this._isKeyboardOpening = true;
|
||||
this._isKeyboardClosing = false;
|
||||
this.keyboardOpening = true;
|
||||
this.keyboardClosing = false;
|
||||
});
|
||||
});
|
||||
Keyboard.instance.onKeyboardWillHide().subscribe((data) => {
|
||||
Keyboard.instance.onKeyboardWillHide().subscribe(() => {
|
||||
// Execute the callback in the Angular zone, so change detection doesn't stop working.
|
||||
zone.run(() => {
|
||||
this._isKeyboardOpening = false;
|
||||
this._isKeyboardClosing = true;
|
||||
this.keyboardOpening = false;
|
||||
this.keyboardClosing = true;
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -116,8 +117,8 @@ export class CoreAppProvider {
|
|||
|
||||
// Export the app provider and appRef to control the application in Behat tests.
|
||||
if (CoreAppProvider.isAutomated()) {
|
||||
(<any> window).appProvider = this;
|
||||
(<any> window).appRef = appRef;
|
||||
(<WindowForAutomatedTests> window).appProvider = this;
|
||||
(<WindowForAutomatedTests> window).appRef = appRef;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -145,7 +146,7 @@ export class CoreAppProvider {
|
|||
* @return Whether the function is supported.
|
||||
*/
|
||||
canRecordMedia(): boolean {
|
||||
return !!(<any> window).MediaRecorder;
|
||||
return !!window.MediaRecorder;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -163,7 +164,7 @@ export class CoreAppProvider {
|
|||
* @param schema The schema to create.
|
||||
* @return Promise resolved when done.
|
||||
*/
|
||||
async createTablesFromSchema(schema: CoreAppSchema): Promise<any> {
|
||||
async createTablesFromSchema(schema: CoreAppSchema): Promise<void> {
|
||||
this.logger.debug(`Apply schema to app DB: ${schema.name}`);
|
||||
|
||||
let oldVersion;
|
||||
|
@ -173,7 +174,8 @@ export class CoreAppProvider {
|
|||
await this.createVersionsTableReady;
|
||||
|
||||
// Fetch installed version of the schema.
|
||||
const entry = await this.db.getRecord(this.SCHEMA_VERSIONS_TABLE, {name: schema.name});
|
||||
const entry = await this.db.getRecord<SchemaVersionsDBEntry>(SCHEMA_VERSIONS_TABLE, { name: schema.name });
|
||||
|
||||
oldVersion = entry.version;
|
||||
} catch (error) {
|
||||
// No installed version yet.
|
||||
|
@ -195,7 +197,7 @@ export class CoreAppProvider {
|
|||
}
|
||||
|
||||
// Set installed version.
|
||||
await this.db.insertRecord(this.SCHEMA_VERSIONS_TABLE, {name: schema.name, version: schema.version});
|
||||
await this.db.insertRecord(SCHEMA_VERSIONS_TABLE, { name: schema.name, version: schema.version });
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -222,7 +224,7 @@ export class CoreAppProvider {
|
|||
* @param storesConfig Config params to send the user to the right place.
|
||||
* @return Store URL.
|
||||
*/
|
||||
getAppStoreUrl(storesConfig: CoreStoreConfig): string {
|
||||
getAppStoreUrl(storesConfig: CoreStoreConfig): string {
|
||||
if (this.isMac() && storesConfig.mac) {
|
||||
return 'itms-apps://itunes.apple.com/app/' + storesConfig.mac;
|
||||
}
|
||||
|
@ -260,9 +262,7 @@ export class CoreAppProvider {
|
|||
* @return Whether the app is running in a 64 bits desktop environment (not browser).
|
||||
*/
|
||||
is64Bits(): boolean {
|
||||
const process = (<any> window).process;
|
||||
|
||||
return this.isDesktop() && process.arch == 'x64';
|
||||
return this.isDesktop() && window.process.arch == 'x64';
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -280,9 +280,8 @@ export class CoreAppProvider {
|
|||
* @return Whether the app is running in a desktop environment (not browser).
|
||||
*/
|
||||
isDesktop(): boolean {
|
||||
const process = (<any> window).process;
|
||||
|
||||
return !!(process && process.versions && typeof process.versions.electron != 'undefined');
|
||||
// @todo
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -300,7 +299,7 @@ export class CoreAppProvider {
|
|||
* @return Whether keyboard is closing (animating).
|
||||
*/
|
||||
isKeyboardClosing(): boolean {
|
||||
return this._isKeyboardClosing;
|
||||
return this.keyboardClosing;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -309,7 +308,7 @@ export class CoreAppProvider {
|
|||
* @return Whether keyboard is opening (animating).
|
||||
*/
|
||||
isKeyboardOpening(): boolean {
|
||||
return this._isKeyboardOpening;
|
||||
return this.keyboardOpening;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -462,8 +461,8 @@ export class CoreAppProvider {
|
|||
*/
|
||||
protected setKeyboardShown(shown: boolean): void {
|
||||
this.isKeyboardShown = shown;
|
||||
this._isKeyboardOpening = false;
|
||||
this._isKeyboardClosing = false;
|
||||
this.keyboardOpening = false;
|
||||
this.keyboardClosing = false;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -487,23 +486,15 @@ export class CoreAppProvider {
|
|||
* NOT when the browser is opened.
|
||||
*/
|
||||
startSSOAuthentication(): void {
|
||||
let cancelTimeout;
|
||||
let resolvePromise;
|
||||
this.ssoAuthenticationDeferred = CoreUtils.instance.promiseDefer<void>();
|
||||
|
||||
this.ssoAuthenticationPromise = new Promise((resolve, reject): void => {
|
||||
resolvePromise = resolve;
|
||||
|
||||
// Resolve it automatically after 10 seconds (it should never take that long).
|
||||
cancelTimeout = setTimeout(() => {
|
||||
this.finishSSOAuthentication();
|
||||
}, 10000);
|
||||
});
|
||||
|
||||
// Store the resolve function in the promise itself.
|
||||
(<any> this.ssoAuthenticationPromise).resolve = resolvePromise;
|
||||
// Resolve it automatically after 10 seconds (it should never take that long).
|
||||
const cancelTimeout = setTimeout(() => {
|
||||
this.finishSSOAuthentication();
|
||||
}, 10000);
|
||||
|
||||
// If the promise is resolved because finishSSOAuthentication is called, stop the cancel promise.
|
||||
this.ssoAuthenticationPromise.then(() => {
|
||||
this.ssoAuthenticationDeferred.promise.then(() => {
|
||||
clearTimeout(cancelTimeout);
|
||||
});
|
||||
}
|
||||
|
@ -512,9 +503,9 @@ export class CoreAppProvider {
|
|||
* Finish an SSO authentication process.
|
||||
*/
|
||||
finishSSOAuthentication(): void {
|
||||
if (this.ssoAuthenticationPromise) {
|
||||
(<any> this.ssoAuthenticationPromise).resolve && (<any> this.ssoAuthenticationPromise).resolve();
|
||||
this.ssoAuthenticationPromise = undefined;
|
||||
if (this.ssoAuthenticationDeferred) {
|
||||
this.ssoAuthenticationDeferred.resolve();
|
||||
this.ssoAuthenticationDeferred = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -524,7 +515,7 @@ export class CoreAppProvider {
|
|||
* @return Whether there's a SSO authentication ongoing.
|
||||
*/
|
||||
isSSOAuthenticationOngoing(): boolean {
|
||||
return !!this.ssoAuthenticationPromise;
|
||||
return !!this.ssoAuthenticationDeferred;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -532,8 +523,8 @@ export class CoreAppProvider {
|
|||
*
|
||||
* @return Promise resolved once SSO authentication finishes.
|
||||
*/
|
||||
waitForSSOAuthentication(): Promise<any> {
|
||||
return this.ssoAuthenticationPromise || Promise.resolve();
|
||||
async waitForSSOAuthentication(): Promise<void> {
|
||||
await this.ssoAuthenticationDeferred && this.ssoAuthenticationDeferred.promise;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -542,27 +533,24 @@ export class CoreAppProvider {
|
|||
* @param timeout Maximum time to wait, use null to wait forever.
|
||||
*/
|
||||
async waitForResume(timeout: number | null = null): Promise<void> {
|
||||
let resolve: (value?: any) => void;
|
||||
let resumeSubscription: any;
|
||||
let timeoutId: NodeJS.Timer | false;
|
||||
let deferred = CoreUtils.instance.promiseDefer<void>();
|
||||
|
||||
const promise = new Promise((r): any => resolve = r);
|
||||
const stopWaiting = (): any => {
|
||||
if (!resolve) {
|
||||
const stopWaiting = () => {
|
||||
if (!deferred) {
|
||||
return;
|
||||
}
|
||||
|
||||
resolve();
|
||||
deferred.resolve();
|
||||
resumeSubscription.unsubscribe();
|
||||
timeoutId && clearTimeout(timeoutId);
|
||||
|
||||
resolve = null;
|
||||
deferred = null;
|
||||
};
|
||||
|
||||
resumeSubscription = Platform.instance.resume.subscribe(stopWaiting);
|
||||
timeoutId = timeout ? setTimeout(stopWaiting, timeout) : false;
|
||||
const resumeSubscription = Platform.instance.resume.subscribe(stopWaiting);
|
||||
const timeoutId = timeout ? setTimeout(stopWaiting, timeout) : false;
|
||||
|
||||
await promise;
|
||||
await deferred.promise;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -626,23 +614,19 @@ export class CoreAppProvider {
|
|||
* button is pressed. This method decides which of the registered back button
|
||||
* actions has the highest priority and should be called.
|
||||
*
|
||||
* @param fn Called when the back button is pressed,
|
||||
* if this registered action has the highest priority.
|
||||
* @param callback Called when the back button is pressed, if this registered action has the highest priority.
|
||||
* @param priority Set the priority for this action. All actions sorted by priority will be executed since one of
|
||||
* them returns true.
|
||||
* * Priorities higher or equal than 1000 will go before closing modals
|
||||
* * Priorities lower than 500 will only be executed if you are in the first state of the app (before exit).
|
||||
* @return A function that, when called, will unregister
|
||||
* the back button action.
|
||||
* - Priorities higher or equal than 1000 will go before closing modals
|
||||
* - Priorities lower than 500 will only be executed if you are in the first state of the app (before exit).
|
||||
* @return A function that, when called, will unregister the back button action.
|
||||
*/
|
||||
registerBackButtonAction(fn: any, priority: number = 0): any {
|
||||
const action = { fn, priority };
|
||||
registerBackButtonAction(callback: () => boolean, priority: number = 0): () => boolean {
|
||||
const action = { callback, priority };
|
||||
|
||||
this.backActions.push(action);
|
||||
|
||||
this.backActions.sort((a, b) => {
|
||||
return b.priority - a.priority;
|
||||
});
|
||||
this.backActions.sort((a, b) => b.priority - a.priority);
|
||||
|
||||
return (): boolean => {
|
||||
const index = this.backActions.indexOf(action);
|
||||
|
@ -700,6 +684,7 @@ export class CoreAppProvider {
|
|||
setForceOffline(value: boolean): void {
|
||||
this.forceOffline = !!value;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export class CoreApp extends makeSingleton(CoreAppProvider) {}
|
||||
|
@ -802,5 +787,18 @@ export type CoreAppSchema = {
|
|||
* @param oldVersion Old version of the schema or 0 if not installed.
|
||||
* @return Promise resolved when done.
|
||||
*/
|
||||
migrate?(db: SQLiteDB, oldVersion: number): Promise<any>;
|
||||
migrate?(db: SQLiteDB, oldVersion: number): Promise<void>;
|
||||
};
|
||||
|
||||
/**
|
||||
* Extended window type for automated tests.
|
||||
*/
|
||||
export type WindowForAutomatedTests = Window & {
|
||||
appProvider?: CoreAppProvider;
|
||||
appRef?: ApplicationRef;
|
||||
};
|
||||
|
||||
type SchemaVersionsDBEntry = {
|
||||
name: string;
|
||||
version: number;
|
||||
};
|
||||
|
|
|
@ -18,36 +18,38 @@ import { CoreApp, CoreAppSchema } from '@services/app';
|
|||
import { SQLiteDB } from '@classes/sqlitedb';
|
||||
import { makeSingleton } from '@singletons/core.singletons';
|
||||
|
||||
const TABLE_NAME = 'core_config';
|
||||
|
||||
/**
|
||||
* Factory to provide access to dynamic and permanent config and settings.
|
||||
* It should not be abused into a temporary storage.
|
||||
*/
|
||||
@Injectable()
|
||||
export class CoreConfigProvider {
|
||||
|
||||
protected appDB: SQLiteDB;
|
||||
protected TABLE_NAME = 'core_config';
|
||||
protected tableSchema: CoreAppSchema = {
|
||||
name: 'CoreConfigProvider',
|
||||
version: 1,
|
||||
tables: [
|
||||
{
|
||||
name: this.TABLE_NAME,
|
||||
name: TABLE_NAME,
|
||||
columns: [
|
||||
{
|
||||
name: 'name',
|
||||
type: 'TEXT',
|
||||
unique: true,
|
||||
notNull: true
|
||||
notNull: true,
|
||||
},
|
||||
{
|
||||
name: 'value'
|
||||
name: 'value',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
protected dbReady: Promise<any>; // Promise resolved when the app DB is initialized.
|
||||
protected dbReady: Promise<void>; // Promise resolved when the app DB is initialized.
|
||||
|
||||
constructor() {
|
||||
this.appDB = CoreApp.instance.getDB();
|
||||
|
@ -62,10 +64,10 @@ export class CoreConfigProvider {
|
|||
* @param name The config name.
|
||||
* @return Promise resolved when done.
|
||||
*/
|
||||
async delete(name: string): Promise<any> {
|
||||
async delete(name: string): Promise<void> {
|
||||
await this.dbReady;
|
||||
|
||||
return this.appDB.deleteRecords(this.TABLE_NAME, { name });
|
||||
await this.appDB.deleteRecords(TABLE_NAME, { name });
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -75,11 +77,11 @@ export class CoreConfigProvider {
|
|||
* @param defaultValue Default value to use if the entry is not found.
|
||||
* @return Resolves upon success along with the config data. Reject on failure.
|
||||
*/
|
||||
async get(name: string, defaultValue?: any): Promise<any> {
|
||||
async get<T>(name: string, defaultValue?: T): Promise<T> {
|
||||
await this.dbReady;
|
||||
|
||||
try {
|
||||
const entry = await this.appDB.getRecord(this.TABLE_NAME, { name });
|
||||
const entry = await this.appDB.getRecord<ConfigDBEntry>(TABLE_NAME, { name });
|
||||
|
||||
return entry.value;
|
||||
} catch (error) {
|
||||
|
@ -98,11 +100,18 @@ export class CoreConfigProvider {
|
|||
* @param value The config value. Can only store number or strings.
|
||||
* @return Promise resolved when done.
|
||||
*/
|
||||
async set(name: string, value: number | string): Promise<any> {
|
||||
async set(name: string, value: number | string): Promise<void> {
|
||||
await this.dbReady;
|
||||
|
||||
return this.appDB.insertRecord(this.TABLE_NAME, { name, value });
|
||||
await this.appDB.insertRecord(TABLE_NAME, { name, value });
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export class CoreConfig extends makeSingleton(CoreConfigProvider) {}
|
||||
|
||||
type ConfigDBEntry = {
|
||||
name: string;
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
value: any;
|
||||
};
|
||||
|
|
|
@ -19,107 +19,52 @@ import { CoreConfig } from '@services/config';
|
|||
import { CoreUtils } from '@services/utils/utils';
|
||||
import { CoreConstants } from '@core/constants';
|
||||
import { SQLiteDB } from '@classes/sqlitedb';
|
||||
import { CoreError } from '@classes/errors/error';
|
||||
|
||||
import { makeSingleton, Network } from '@singletons/core.singletons';
|
||||
import { CoreLogger } from '@singletons/logger';
|
||||
|
||||
/**
|
||||
* Interface that all cron handlers must implement.
|
||||
*/
|
||||
export interface CoreCronHandler {
|
||||
/**
|
||||
* A name to identify the handler.
|
||||
*/
|
||||
name: string;
|
||||
|
||||
/**
|
||||
* Whether the handler is running. Used internally by the provider, there's no need to set it.
|
||||
*/
|
||||
running?: boolean;
|
||||
|
||||
/**
|
||||
* Timeout ID for the handler scheduling. Used internally by the provider, there's no need to set it.
|
||||
*/
|
||||
timeout?: number;
|
||||
|
||||
/**
|
||||
* Returns handler's interval in milliseconds. Defaults to CoreCronDelegate.DEFAULT_INTERVAL.
|
||||
*
|
||||
* @return Interval time (in milliseconds).
|
||||
*/
|
||||
getInterval?(): number;
|
||||
|
||||
/**
|
||||
* Check whether the process uses network or not. True if not defined.
|
||||
*
|
||||
* @return Whether the process uses network or not
|
||||
*/
|
||||
usesNetwork?(): boolean;
|
||||
|
||||
/**
|
||||
* Check whether it's a synchronization process or not. True if not defined.
|
||||
*
|
||||
* @return Whether it's a synchronization process or not.
|
||||
*/
|
||||
isSync?(): boolean;
|
||||
|
||||
/**
|
||||
* Check whether the sync can be executed manually. Call isSync if not defined.
|
||||
*
|
||||
* @return Whether the sync can be executed manually.
|
||||
*/
|
||||
canManualSync?(): boolean;
|
||||
|
||||
/**
|
||||
* Execute the process.
|
||||
*
|
||||
* @param siteId ID of the site affected. If not defined, all sites.
|
||||
* @param force Determines if it's a forced execution.
|
||||
* @return Promise resolved when done. If the promise is rejected, this function will be called again often,
|
||||
* it shouldn't be abused.
|
||||
*/
|
||||
execute?(siteId?: string, force?: boolean): Promise<any>;
|
||||
}
|
||||
const CRON_TABLE = 'cron';
|
||||
|
||||
/*
|
||||
* Service to handle cron processes. The registered processes will be executed every certain time.
|
||||
*/
|
||||
@Injectable()
|
||||
export class CoreCronDelegate {
|
||||
|
||||
// Constants.
|
||||
static DEFAULT_INTERVAL = 3600000; // Default interval is 1 hour.
|
||||
static MIN_INTERVAL = 300000; // Minimum interval is 5 minutes.
|
||||
static DESKTOP_MIN_INTERVAL = 60000; // Minimum interval in desktop is 1 minute.
|
||||
static MAX_TIME_PROCESS = 120000; // Max time a process can block the queue. Defaults to 2 minutes.
|
||||
static readonly DEFAULT_INTERVAL = 3600000; // Default interval is 1 hour.
|
||||
static readonly MIN_INTERVAL = 300000; // Minimum interval is 5 minutes.
|
||||
static readonly DESKTOP_MIN_INTERVAL = 60000; // Minimum interval in desktop is 1 minute.
|
||||
static readonly MAX_TIME_PROCESS = 120000; // Max time a process can block the queue. Defaults to 2 minutes.
|
||||
|
||||
// Variables for database.
|
||||
protected CRON_TABLE = 'cron';
|
||||
protected tableSchema: CoreAppSchema = {
|
||||
name: 'CoreCronDelegate',
|
||||
version: 1,
|
||||
tables: [
|
||||
{
|
||||
name: this.CRON_TABLE,
|
||||
name: CRON_TABLE,
|
||||
columns: [
|
||||
{
|
||||
name: 'id',
|
||||
type: 'TEXT',
|
||||
primaryKey: true
|
||||
primaryKey: true,
|
||||
},
|
||||
{
|
||||
name: 'value',
|
||||
type: 'INTEGER'
|
||||
type: 'INTEGER',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
protected logger;
|
||||
protected logger: CoreLogger;
|
||||
protected appDB: SQLiteDB;
|
||||
protected dbReady: Promise<any>; // Promise resolved when the app DB is initialized.
|
||||
protected dbReady: Promise<void>; // Promise resolved when the app DB is initialized.
|
||||
protected handlers: { [s: string]: CoreCronHandler } = {};
|
||||
protected queuePromise = Promise.resolve();
|
||||
protected queuePromise: Promise<void> = Promise.resolve();
|
||||
|
||||
constructor(zone: NgZone) {
|
||||
this.logger = CoreLogger.getInstance('CoreCronDelegate');
|
||||
|
@ -139,7 +84,7 @@ export class CoreCronDelegate {
|
|||
|
||||
// Export the sync provider so Behat tests can trigger cron tasks without waiting.
|
||||
if (CoreAppProvider.isAutomated()) {
|
||||
(<any> window).cronProvider = this;
|
||||
(<WindowForAutomatedTests> window).cronProvider = this;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -152,12 +97,13 @@ export class CoreCronDelegate {
|
|||
* @param siteId Site ID. If not defined, all sites.
|
||||
* @return Promise resolved if handler is executed successfully, rejected otherwise.
|
||||
*/
|
||||
protected checkAndExecuteHandler(name: string, force?: boolean, siteId?: string): Promise<any> {
|
||||
protected checkAndExecuteHandler(name: string, force?: boolean, siteId?: string): Promise<void> {
|
||||
if (!this.handlers[name] || !this.handlers[name].execute) {
|
||||
// Invalid handler.
|
||||
this.logger.debug('Cannot execute handler because is invalid: ' + name);
|
||||
const message = `Cannot execute handler because is invalid: ${name}`;
|
||||
this.logger.debug(message);
|
||||
|
||||
return Promise.reject(null);
|
||||
return Promise.reject(new CoreError(message));
|
||||
}
|
||||
|
||||
const usesNetwork = this.handlerUsesNetwork(name);
|
||||
|
@ -166,17 +112,17 @@ export class CoreCronDelegate {
|
|||
|
||||
if (usesNetwork && !CoreApp.instance.isOnline()) {
|
||||
// Offline, stop executing.
|
||||
this.logger.debug('Cannot execute handler because device is offline: ' + name);
|
||||
const message = `Cannot execute handler because device is offline: ${name}`;
|
||||
this.logger.debug(message);
|
||||
this.stopHandler(name);
|
||||
|
||||
return Promise.reject(null);
|
||||
return Promise.reject(new CoreError(message));
|
||||
}
|
||||
|
||||
if (isSync) {
|
||||
// Check network connection.
|
||||
promise = CoreConfig.instance.get(CoreConstants.SETTINGS_SYNC_ONLY_ON_WIFI, false).then((syncOnlyOnWifi) => {
|
||||
return !syncOnlyOnWifi || CoreApp.instance.isWifi();
|
||||
});
|
||||
promise = CoreConfig.instance.get(CoreConstants.SETTINGS_SYNC_ONLY_ON_WIFI, false)
|
||||
.then((syncOnlyOnWifi) => !syncOnlyOnWifi || CoreApp.instance.isWifi());
|
||||
} else {
|
||||
promise = Promise.resolve(true);
|
||||
}
|
||||
|
@ -184,30 +130,30 @@ export class CoreCronDelegate {
|
|||
return promise.then((execute: boolean) => {
|
||||
if (!execute) {
|
||||
// Cannot execute in this network connection, retry soon.
|
||||
this.logger.debug('Cannot execute handler because device is using limited connection: ' + name);
|
||||
const message = `Cannot execute handler because device is using limited connection: ${name}`;
|
||||
this.logger.debug(message);
|
||||
this.scheduleNextExecution(name, CoreCronDelegate.MIN_INTERVAL);
|
||||
|
||||
return Promise.reject(null);
|
||||
return Promise.reject(new CoreError(message));
|
||||
}
|
||||
|
||||
// Add the execution to the queue.
|
||||
this.queuePromise = this.queuePromise.catch(() => {
|
||||
// Ignore errors in previous handlers.
|
||||
}).then(() => {
|
||||
return this.executeHandler(name, force, siteId).then(() => {
|
||||
this.logger.debug(`Execution of handler '${name}' was a success.`);
|
||||
}).then(() => this.executeHandler(name, force, siteId).then(() => {
|
||||
this.logger.debug(`Execution of handler '${name}' was a success.`);
|
||||
|
||||
return this.setHandlerLastExecutionTime(name, Date.now()).then(() => {
|
||||
this.scheduleNextExecution(name);
|
||||
});
|
||||
}, (error) => {
|
||||
// Handler call failed. Retry soon.
|
||||
this.logger.error(`Execution of handler '${name}' failed.`, error);
|
||||
this.scheduleNextExecution(name, CoreCronDelegate.MIN_INTERVAL);
|
||||
|
||||
return Promise.reject(null);
|
||||
return this.setHandlerLastExecutionTime(name, Date.now()).then(() => {
|
||||
this.scheduleNextExecution(name);
|
||||
});
|
||||
});
|
||||
}, (error) => {
|
||||
// Handler call failed. Retry soon.
|
||||
const message = `Execution of handler '${name}' failed.`;
|
||||
this.logger.error(message, error);
|
||||
this.scheduleNextExecution(name, CoreCronDelegate.MIN_INTERVAL);
|
||||
|
||||
return Promise.reject(new CoreError(message));
|
||||
}));
|
||||
|
||||
return this.queuePromise;
|
||||
});
|
||||
|
@ -221,10 +167,8 @@ export class CoreCronDelegate {
|
|||
* @param siteId Site ID. If not defined, all sites.
|
||||
* @return Promise resolved when the handler finishes or reaches max time, rejected if it fails.
|
||||
*/
|
||||
protected executeHandler(name: string, force?: boolean, siteId?: string): Promise<any> {
|
||||
protected executeHandler(name: string, force?: boolean, siteId?: string): Promise<void> {
|
||||
return new Promise((resolve, reject): void => {
|
||||
let cancelTimeout;
|
||||
|
||||
this.logger.debug('Executing handler: ' + name);
|
||||
|
||||
// Wrap the call in Promise.resolve to make sure it's a promise.
|
||||
|
@ -232,7 +176,7 @@ export class CoreCronDelegate {
|
|||
clearTimeout(cancelTimeout);
|
||||
});
|
||||
|
||||
cancelTimeout = setTimeout(() => {
|
||||
const cancelTimeout = setTimeout(() => {
|
||||
// The handler took too long. Resolve because we don't want to retry soon.
|
||||
this.logger.debug(`Resolving execution of handler '${name}' because it took too long.`);
|
||||
resolve();
|
||||
|
@ -247,7 +191,7 @@ export class CoreCronDelegate {
|
|||
* @param siteId Site ID. If not defined, all sites.
|
||||
* @return Promise resolved if all handlers are executed successfully, rejected otherwise.
|
||||
*/
|
||||
forceSyncExecution(siteId?: string): Promise<any> {
|
||||
async forceSyncExecution(siteId?: string): Promise<void> {
|
||||
const promises = [];
|
||||
|
||||
for (const name in this.handlers) {
|
||||
|
@ -257,7 +201,7 @@ export class CoreCronDelegate {
|
|||
}
|
||||
}
|
||||
|
||||
return CoreUtils.instance.allPromises(promises);
|
||||
await CoreUtils.instance.allPromises(promises);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -268,7 +212,7 @@ export class CoreCronDelegate {
|
|||
* @param siteId Site ID. If not defined, all sites.
|
||||
* @return Promise resolved if handler has been executed successfully, rejected otherwise.
|
||||
*/
|
||||
forceCronHandlerExecution(name?: string, siteId?: string): Promise<any> {
|
||||
forceCronHandlerExecution(name?: string, siteId?: string): Promise<void> {
|
||||
const handler = this.handlers[name];
|
||||
|
||||
// Mark the handler as running (it might be running already).
|
||||
|
@ -327,8 +271,9 @@ export class CoreCronDelegate {
|
|||
const id = this.getHandlerLastExecutionId(name);
|
||||
|
||||
try {
|
||||
const entry = await this.appDB.getRecord(this.CRON_TABLE, { id });
|
||||
const time = parseInt(entry.value, 10);
|
||||
const entry = await this.appDB.getRecord<CronDBEntry>(CRON_TABLE, { id });
|
||||
|
||||
const time = Number(entry.value);
|
||||
|
||||
return isNaN(time) ? 0 : time;
|
||||
} catch (err) {
|
||||
|
@ -489,16 +434,16 @@ export class CoreCronDelegate {
|
|||
* @param time Time to set.
|
||||
* @return Promise resolved when the execution time is saved.
|
||||
*/
|
||||
protected async setHandlerLastExecutionTime(name: string, time: number): Promise<any> {
|
||||
protected async setHandlerLastExecutionTime(name: string, time: number): Promise<void> {
|
||||
await this.dbReady;
|
||||
|
||||
const id = this.getHandlerLastExecutionId(name);
|
||||
const entry = {
|
||||
id,
|
||||
value: time
|
||||
value: time,
|
||||
};
|
||||
|
||||
return this.appDB.insertRecord(this.CRON_TABLE, entry);
|
||||
await this.appDB.insertRecord(CRON_TABLE, entry);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -559,6 +504,78 @@ export class CoreCronDelegate {
|
|||
clearTimeout(this.handlers[name].timeout);
|
||||
delete this.handlers[name].timeout;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export class CoreCron extends makeSingleton(CoreCronDelegate) {}
|
||||
|
||||
|
||||
/**
|
||||
* Interface that all cron handlers must implement.
|
||||
*/
|
||||
export interface CoreCronHandler {
|
||||
/**
|
||||
* A name to identify the handler.
|
||||
*/
|
||||
name: string;
|
||||
|
||||
/**
|
||||
* Whether the handler is running. Used internally by the provider, there's no need to set it.
|
||||
*/
|
||||
running?: boolean;
|
||||
|
||||
/**
|
||||
* Timeout ID for the handler scheduling. Used internally by the provider, there's no need to set it.
|
||||
*/
|
||||
timeout?: number;
|
||||
|
||||
/**
|
||||
* Returns handler's interval in milliseconds. Defaults to CoreCronDelegate.DEFAULT_INTERVAL.
|
||||
*
|
||||
* @return Interval time (in milliseconds).
|
||||
*/
|
||||
getInterval?(): number;
|
||||
|
||||
/**
|
||||
* Check whether the process uses network or not. True if not defined.
|
||||
*
|
||||
* @return Whether the process uses network or not
|
||||
*/
|
||||
usesNetwork?(): boolean;
|
||||
|
||||
/**
|
||||
* Check whether it's a synchronization process or not. True if not defined.
|
||||
*
|
||||
* @return Whether it's a synchronization process or not.
|
||||
*/
|
||||
isSync?(): boolean;
|
||||
|
||||
/**
|
||||
* Check whether the sync can be executed manually. Call isSync if not defined.
|
||||
*
|
||||
* @return Whether the sync can be executed manually.
|
||||
*/
|
||||
canManualSync?(): boolean;
|
||||
|
||||
/**
|
||||
* Execute the process.
|
||||
*
|
||||
* @param siteId ID of the site affected. If not defined, all sites.
|
||||
* @param force Determines if it's a forced execution.
|
||||
* @return Promise resolved when done. If the promise is rejected, this function will be called again often,
|
||||
* it shouldn't be abused.
|
||||
*/
|
||||
execute?(siteId?: string, force?: boolean): Promise<void>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extended window type for automated tests.
|
||||
*/
|
||||
export type WindowForAutomatedTests = Window & {
|
||||
cronProvider?: CoreCronDelegate;
|
||||
};
|
||||
|
||||
type CronDBEntry = {
|
||||
id: string;
|
||||
value: number;
|
||||
};
|
||||
|
|
|
@ -24,9 +24,7 @@ import { makeSingleton, SQLite, Platform } from '@singletons/core.singletons';
|
|||
@Injectable()
|
||||
export class CoreDbProvider {
|
||||
|
||||
protected dbInstances = {};
|
||||
|
||||
constructor() { }
|
||||
protected dbInstances: {[name: string]: SQLiteDB} = {};
|
||||
|
||||
/**
|
||||
* Get or create a database object.
|
||||
|
@ -55,31 +53,31 @@ export class CoreDbProvider {
|
|||
* @param name DB name.
|
||||
* @return Promise resolved when the DB is deleted.
|
||||
*/
|
||||
deleteDB(name: string): Promise<any> {
|
||||
let promise;
|
||||
|
||||
async deleteDB(name: string): Promise<void> {
|
||||
if (typeof this.dbInstances[name] != 'undefined') {
|
||||
// Close the database first.
|
||||
promise = this.dbInstances[name].close();
|
||||
} else {
|
||||
promise = Promise.resolve();
|
||||
}
|
||||
await this.dbInstances[name].close();
|
||||
|
||||
return promise.then(() => {
|
||||
const db = this.dbInstances[name];
|
||||
delete this.dbInstances[name];
|
||||
|
||||
if (Platform.instance.is('cordova')) {
|
||||
return SQLite.instance.deleteDatabase({
|
||||
name,
|
||||
location: 'default'
|
||||
});
|
||||
} else {
|
||||
if (db instanceof SQLiteDBMock) {
|
||||
// In WebSQL we cannot delete the database, just empty it.
|
||||
return db.emptyDatabase();
|
||||
} else {
|
||||
return SQLite.instance.deleteDatabase({
|
||||
name,
|
||||
location: 'default',
|
||||
});
|
||||
}
|
||||
});
|
||||
} else if (Platform.instance.is('cordova')) {
|
||||
return SQLite.instance.deleteDatabase({
|
||||
name,
|
||||
location: 'default',
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export class CoreDB extends makeSingleton(CoreDbProvider) {}
|
||||
|
|
|
@ -33,46 +33,47 @@ export interface CoreEventObserver {
|
|||
*/
|
||||
@Injectable()
|
||||
export class CoreEventsProvider {
|
||||
static SESSION_EXPIRED = 'session_expired';
|
||||
static PASSWORD_CHANGE_FORCED = 'password_change_forced';
|
||||
static USER_NOT_FULLY_SETUP = 'user_not_fully_setup';
|
||||
static SITE_POLICY_NOT_AGREED = 'site_policy_not_agreed';
|
||||
static LOGIN = 'login';
|
||||
static LOGOUT = 'logout';
|
||||
static LANGUAGE_CHANGED = 'language_changed';
|
||||
static NOTIFICATION_SOUND_CHANGED = 'notification_sound_changed';
|
||||
static SITE_ADDED = 'site_added';
|
||||
static SITE_UPDATED = 'site_updated';
|
||||
static SITE_DELETED = 'site_deleted';
|
||||
static COMPLETION_MODULE_VIEWED = 'completion_module_viewed';
|
||||
static USER_DELETED = 'user_deleted';
|
||||
static PACKAGE_STATUS_CHANGED = 'package_status_changed';
|
||||
static COURSE_STATUS_CHANGED = 'course_status_changed';
|
||||
static SECTION_STATUS_CHANGED = 'section_status_changed';
|
||||
static COMPONENT_FILE_ACTION = 'component_file_action';
|
||||
static SITE_PLUGINS_LOADED = 'site_plugins_loaded';
|
||||
static SITE_PLUGINS_COURSE_RESTRICT_UPDATED = 'site_plugins_course_restrict_updated';
|
||||
static LOGIN_SITE_CHECKED = 'login_site_checked';
|
||||
static LOGIN_SITE_UNCHECKED = 'login_site_unchecked';
|
||||
static IAB_LOAD_START = 'inappbrowser_load_start';
|
||||
static IAB_EXIT = 'inappbrowser_exit';
|
||||
static APP_LAUNCHED_URL = 'app_launched_url'; // App opened with a certain URL (custom URL scheme).
|
||||
static FILE_SHARED = 'file_shared';
|
||||
static KEYBOARD_CHANGE = 'keyboard_change';
|
||||
static CORE_LOADING_CHANGED = 'core_loading_changed';
|
||||
static ORIENTATION_CHANGE = 'orientation_change';
|
||||
static LOAD_PAGE_MAIN_MENU = 'load_page_main_menu';
|
||||
static SEND_ON_ENTER_CHANGED = 'send_on_enter_changed';
|
||||
static MAIN_MENU_OPEN = 'main_menu_open';
|
||||
static SELECT_COURSE_TAB = 'select_course_tab';
|
||||
static WS_CACHE_INVALIDATED = 'ws_cache_invalidated';
|
||||
static SITE_STORAGE_DELETED = 'site_storage_deleted';
|
||||
static FORM_ACTION = 'form_action';
|
||||
static ACTIVITY_DATA_SENT = 'activity_data_sent';
|
||||
|
||||
static readonly SESSION_EXPIRED = 'session_expired';
|
||||
static readonly PASSWORD_CHANGE_FORCED = 'password_change_forced';
|
||||
static readonly USER_NOT_FULLY_SETUP = 'user_not_fully_setup';
|
||||
static readonly SITE_POLICY_NOT_AGREED = 'site_policy_not_agreed';
|
||||
static readonly LOGIN = 'login';
|
||||
static readonly LOGOUT = 'logout';
|
||||
static readonly LANGUAGE_CHANGED = 'language_changed';
|
||||
static readonly NOTIFICATION_SOUND_CHANGED = 'notification_sound_changed';
|
||||
static readonly SITE_ADDED = 'site_added';
|
||||
static readonly SITE_UPDATED = 'site_updated';
|
||||
static readonly SITE_DELETED = 'site_deleted';
|
||||
static readonly COMPLETION_MODULE_VIEWED = 'completion_module_viewed';
|
||||
static readonly USER_DELETED = 'user_deleted';
|
||||
static readonly PACKAGE_STATUS_CHANGED = 'package_status_changed';
|
||||
static readonly COURSE_STATUS_CHANGED = 'course_status_changed';
|
||||
static readonly SECTION_STATUS_CHANGED = 'section_status_changed';
|
||||
static readonly COMPONENT_FILE_ACTION = 'component_file_action';
|
||||
static readonly SITE_PLUGINS_LOADED = 'site_plugins_loaded';
|
||||
static readonly SITE_PLUGINS_COURSE_RESTRICT_UPDATED = 'site_plugins_course_restrict_updated';
|
||||
static readonly LOGIN_SITE_CHECKED = 'login_site_checked';
|
||||
static readonly LOGIN_SITE_UNCHECKED = 'login_site_unchecked';
|
||||
static readonly IAB_LOAD_START = 'inappbrowser_load_start';
|
||||
static readonly IAB_EXIT = 'inappbrowser_exit';
|
||||
static readonly APP_LAUNCHED_URL = 'app_launched_url'; // App opened with a certain URL (custom URL scheme).
|
||||
static readonly FILE_SHARED = 'file_shared';
|
||||
static readonly KEYBOARD_CHANGE = 'keyboard_change';
|
||||
static readonly CORE_LOADING_CHANGED = 'core_loading_changed';
|
||||
static readonly ORIENTATION_CHANGE = 'orientation_change';
|
||||
static readonly LOAD_PAGE_MAIN_MENU = 'load_page_main_menu';
|
||||
static readonly SEND_ON_ENTER_CHANGED = 'send_on_enter_changed';
|
||||
static readonly MAIN_MENU_OPEN = 'main_menu_open';
|
||||
static readonly SELECT_COURSE_TAB = 'select_course_tab';
|
||||
static readonly WS_CACHE_INVALIDATED = 'ws_cache_invalidated';
|
||||
static readonly SITE_STORAGE_DELETED = 'site_storage_deleted';
|
||||
static readonly FORM_ACTION = 'form_action';
|
||||
static readonly ACTIVITY_DATA_SENT = 'activity_data_sent';
|
||||
|
||||
protected logger: CoreLogger;
|
||||
protected observables: { [s: string]: Subject<any> } = {};
|
||||
protected uniqueEvents = {};
|
||||
protected observables: { [eventName: string]: Subject<unknown> } = {};
|
||||
protected uniqueEvents: { [eventName: string]: {data: unknown} } = {};
|
||||
|
||||
constructor() {
|
||||
this.logger = CoreLogger.getInstance('CoreEventsProvider');
|
||||
|
@ -89,7 +90,7 @@ export class CoreEventsProvider {
|
|||
* @param siteId Site where to trigger the event. Undefined won't check the site.
|
||||
* @return Observer to stop listening.
|
||||
*/
|
||||
on(eventName: string, callBack: (value: any) => void, siteId?: string): CoreEventObserver {
|
||||
on(eventName: string, callBack: (value: unknown) => void, siteId?: string): CoreEventObserver {
|
||||
// If it's a unique event and has been triggered already, call the callBack.
|
||||
// We don't need to create an observer because the event won't be triggered again.
|
||||
if (this.uniqueEvents[eventName]) {
|
||||
|
@ -99,7 +100,7 @@ export class CoreEventsProvider {
|
|||
return {
|
||||
off: (): void => {
|
||||
// Nothing to do.
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -107,10 +108,10 @@ export class CoreEventsProvider {
|
|||
|
||||
if (typeof this.observables[eventName] == 'undefined') {
|
||||
// No observable for this event, create a new one.
|
||||
this.observables[eventName] = new Subject<any>();
|
||||
this.observables[eventName] = new Subject<unknown>();
|
||||
}
|
||||
|
||||
const subscription = this.observables[eventName].subscribe((value: any) => {
|
||||
const subscription = this.observables[eventName].subscribe((value: {siteId?: string; [key: string]: unknown}) => {
|
||||
if (!siteId || value.siteId == siteId) {
|
||||
callBack(value);
|
||||
}
|
||||
|
@ -121,7 +122,7 @@ export class CoreEventsProvider {
|
|||
off: (): void => {
|
||||
this.logger.debug(`Stop listening to event '${eventName}'`);
|
||||
subscription.unsubscribe();
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -136,11 +137,8 @@ export class CoreEventsProvider {
|
|||
* @param siteId Site where to trigger the event. Undefined won't check the site.
|
||||
* @return Observer to stop listening.
|
||||
*/
|
||||
onMultiple(eventNames: string[], callBack: (value: any) => void, siteId?: string): CoreEventObserver {
|
||||
|
||||
const observers = eventNames.map((name) => {
|
||||
return this.on(name, callBack, siteId);
|
||||
});
|
||||
onMultiple(eventNames: string[], callBack: (value: unknown) => void, siteId?: string): CoreEventObserver {
|
||||
const observers = eventNames.map((name) => this.on(name, callBack, siteId));
|
||||
|
||||
// Create and return a CoreEventObserver.
|
||||
return {
|
||||
|
@ -148,7 +146,7 @@ export class CoreEventsProvider {
|
|||
observers.forEach((observer) => {
|
||||
observer.off();
|
||||
});
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -159,14 +157,11 @@ export class CoreEventsProvider {
|
|||
* @param data Data to pass to the observers.
|
||||
* @param siteId Site where to trigger the event. Undefined means no Site.
|
||||
*/
|
||||
trigger(eventName: string, data?: any, siteId?: string): void {
|
||||
trigger(eventName: string, data?: unknown, siteId?: string): void {
|
||||
this.logger.debug(`Event '${eventName}' triggered.`);
|
||||
if (this.observables[eventName]) {
|
||||
if (siteId) {
|
||||
if (!data) {
|
||||
data = {};
|
||||
}
|
||||
data.siteId = siteId;
|
||||
data = Object.assign(data || {}, { siteId });
|
||||
}
|
||||
this.observables[eventName].next(data);
|
||||
}
|
||||
|
@ -179,17 +174,14 @@ export class CoreEventsProvider {
|
|||
* @param data Data to pass to the observers.
|
||||
* @param siteId Site where to trigger the event. Undefined means no Site.
|
||||
*/
|
||||
triggerUnique(eventName: string, data: any, siteId?: string): void {
|
||||
triggerUnique(eventName: string, data: unknown, siteId?: string): void {
|
||||
if (this.uniqueEvents[eventName]) {
|
||||
this.logger.debug(`Unique event '${eventName}' ignored because it was already triggered.`);
|
||||
} else {
|
||||
this.logger.debug(`Unique event '${eventName}' triggered.`);
|
||||
|
||||
if (siteId) {
|
||||
if (!data) {
|
||||
data = {};
|
||||
}
|
||||
data.siteId = siteId;
|
||||
data = Object.assign(data || {}, { siteId });
|
||||
}
|
||||
|
||||
// Store the data so it can be passed to observers that register from now on.
|
||||
|
@ -203,6 +195,7 @@ export class CoreEventsProvider {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export class CoreEvents extends makeSingleton(CoreEventsProvider) {}
|
||||
|
|
|
@ -13,16 +13,18 @@
|
|||
// limitations under the License.
|
||||
|
||||
import { Injectable } from '@angular/core';
|
||||
import { FileEntry } from '@ionic-native/file';
|
||||
|
||||
import { CoreApp } from '@services/app';
|
||||
import { CoreFile } from '@services/file';
|
||||
import { CoreFilepool } from '@services/filepool';
|
||||
import { CoreSites } from '@services/sites';
|
||||
import { CoreWS } from '@services/ws';
|
||||
import { CoreWS, CoreWSExternalFile } from '@services/ws';
|
||||
import { CoreDomUtils } from '@services/utils/dom';
|
||||
import { CoreUrlUtils } from '@services/utils/url';
|
||||
import { CoreUtils } from '@services/utils/utils';
|
||||
import { CoreConstants } from '@core/constants';
|
||||
import { CoreError } from '@classes/errors/error';
|
||||
import { makeSingleton, Translate } from '@singletons/core.singletons';
|
||||
|
||||
/**
|
||||
|
@ -42,8 +44,8 @@ export class CoreFileHelperProvider {
|
|||
* @param siteId The site ID. If not defined, current site.
|
||||
* @return Resolved on success.
|
||||
*/
|
||||
async downloadAndOpenFile(file: any, component: string, componentId: string | number, state?: string,
|
||||
onProgress?: (event: any) => any, siteId?: string): Promise<void> {
|
||||
async downloadAndOpenFile(file: CoreWSExternalFile, component: string, componentId: string | number, state?: string,
|
||||
onProgress?: CoreFileHelperOnProgress, siteId?: string): Promise<void> {
|
||||
siteId = siteId || CoreSites.instance.getCurrentSiteId();
|
||||
|
||||
const fileUrl = this.getFileUrl(file);
|
||||
|
@ -80,7 +82,7 @@ export class CoreFileHelperProvider {
|
|||
}
|
||||
|
||||
if (state == CoreConstants.DOWNLOADING) {
|
||||
throw new Error(Translate.instance.instant('core.erroropenfiledownloading'));
|
||||
throw new CoreError(Translate.instance.instant('core.erroropenfiledownloading'));
|
||||
}
|
||||
|
||||
if (state === CoreConstants.NOT_DOWNLOADED) {
|
||||
|
@ -109,14 +111,11 @@ export class CoreFileHelperProvider {
|
|||
* @param siteId The site ID. If not defined, current site.
|
||||
* @return Resolved with the URL to use on success.
|
||||
*/
|
||||
protected downloadFileIfNeeded(file: any, fileUrl: string, component?: string, componentId?: string | number,
|
||||
timemodified?: number, state?: string, onProgress?: (event: any) => any, siteId?: string): Promise<string> {
|
||||
protected downloadFileIfNeeded(file: CoreWSExternalFile, fileUrl: string, component?: string, componentId?: string | number,
|
||||
timemodified?: number, state?: string, onProgress?: CoreFileHelperOnProgress, siteId?: string): Promise<string> {
|
||||
siteId = siteId || CoreSites.instance.getCurrentSiteId();
|
||||
|
||||
return CoreSites.instance.getSite(siteId).then((site) => {
|
||||
return site.checkAndFixPluginfileURL(fileUrl);
|
||||
}).then((fixedUrl) => {
|
||||
|
||||
return CoreSites.instance.getSite(siteId).then((site) => site.checkAndFixPluginfileURL(fileUrl)).then((fixedUrl) => {
|
||||
if (CoreFile.instance.isAvailable()) {
|
||||
let promise;
|
||||
if (state) {
|
||||
|
@ -134,16 +133,16 @@ export class CoreFileHelperProvider {
|
|||
if (state == CoreConstants.DOWNLOADED) {
|
||||
// File is downloaded, get the local file URL.
|
||||
return CoreFilepool.instance.getUrlByUrl(
|
||||
siteId, fileUrl, component, componentId, timemodified, false, false, file);
|
||||
siteId, fileUrl, component, componentId, timemodified, false, false, file);
|
||||
} else {
|
||||
if (!isOnline && !this.isStateDownloaded(state)) {
|
||||
// Not downloaded and user is offline, reject.
|
||||
return Promise.reject(Translate.instance.instant('core.networkerrormsg'));
|
||||
return Promise.reject(new CoreError(Translate.instance.instant('core.networkerrormsg')));
|
||||
}
|
||||
|
||||
if (onProgress) {
|
||||
// This call can take a while. Send a fake event to notify that we're doing some calculations.
|
||||
onProgress({calculating: true});
|
||||
onProgress({ calculating: true });
|
||||
}
|
||||
|
||||
return CoreFilepool.instance.shouldDownloadBeforeOpen(fixedUrl, file.filesize).then(() => {
|
||||
|
@ -166,7 +165,7 @@ export class CoreFileHelperProvider {
|
|||
} else {
|
||||
// Outdated but offline, so we return the local URL.
|
||||
return CoreFilepool.instance.getUrlByUrl(
|
||||
siteId, fileUrl, component, componentId, timemodified, false, false, file);
|
||||
siteId, fileUrl, component, componentId, timemodified, false, false, file);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -191,27 +190,27 @@ export class CoreFileHelperProvider {
|
|||
* @return Resolved with internal URL on success, rejected otherwise.
|
||||
*/
|
||||
downloadFile(fileUrl: string, component?: string, componentId?: string | number, timemodified?: number,
|
||||
onProgress?: (event: any) => any, file?: any, siteId?: string): Promise<string> {
|
||||
onProgress?: (event: ProgressEvent) => void, file?: CoreWSExternalFile, siteId?: string): Promise<string> {
|
||||
siteId = siteId || CoreSites.instance.getCurrentSiteId();
|
||||
|
||||
// Get the site and check if it can download files.
|
||||
return CoreSites.instance.getSite(siteId).then((site) => {
|
||||
if (!site.canDownloadFiles()) {
|
||||
return Promise.reject(Translate.instance.instant('core.cannotdownloadfiles'));
|
||||
return Promise.reject(new CoreError(Translate.instance.instant('core.cannotdownloadfiles')));
|
||||
}
|
||||
|
||||
return CoreFilepool.instance.downloadUrl(siteId, fileUrl, false, component, componentId,
|
||||
timemodified, onProgress, undefined, file).catch((error) => {
|
||||
timemodified, onProgress, undefined, file).catch((error) =>
|
||||
|
||||
// Download failed, check the state again to see if the file was downloaded before.
|
||||
return CoreFilepool.instance.getFileStateByUrl(siteId, fileUrl, timemodified).then((state) => {
|
||||
CoreFilepool.instance.getFileStateByUrl(siteId, fileUrl, timemodified).then((state) => {
|
||||
if (this.isStateDownloaded(state)) {
|
||||
return CoreFilepool.instance.getInternalUrlByUrl(siteId, fileUrl);
|
||||
} else {
|
||||
return Promise.reject(error);
|
||||
}
|
||||
});
|
||||
});
|
||||
}),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -219,9 +218,10 @@ export class CoreFileHelperProvider {
|
|||
* Get the file's URL.
|
||||
*
|
||||
* @param file The file.
|
||||
* @deprecated since 3.9.5. Get directly the fileurl instead.
|
||||
*/
|
||||
getFileUrl(file: any): string {
|
||||
return file.fileurl || file.url;
|
||||
getFileUrl(file: CoreWSExternalFile): string {
|
||||
return file.fileurl;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -229,7 +229,7 @@ export class CoreFileHelperProvider {
|
|||
*
|
||||
* @param file The file.
|
||||
*/
|
||||
getFileTimemodified(file: any): number {
|
||||
getFileTimemodified(file: CoreWSExternalFile): number {
|
||||
return file.timemodified || 0;
|
||||
}
|
||||
|
||||
|
@ -249,7 +249,7 @@ export class CoreFileHelperProvider {
|
|||
* @param file The file to check.
|
||||
* @return Whether the file should be opened in browser.
|
||||
*/
|
||||
shouldOpenInBrowser(file: any): boolean {
|
||||
shouldOpenInBrowser(file: CoreWSExternalFile): boolean {
|
||||
if (!file || !file.isexternalfile || !file.mimetype) {
|
||||
return false;
|
||||
}
|
||||
|
@ -275,7 +275,7 @@ export class CoreFileHelperProvider {
|
|||
* @param files The files to check.
|
||||
* @return Total files size.
|
||||
*/
|
||||
async getTotalFilesSize(files: any[]): Promise<number> {
|
||||
async getTotalFilesSize(files: (CoreWSExternalFile | FileEntry)[]): Promise<number> {
|
||||
let totalSize = 0;
|
||||
|
||||
for (const file of files) {
|
||||
|
@ -291,27 +291,29 @@ export class CoreFileHelperProvider {
|
|||
* @param file The file to check.
|
||||
* @return File size.
|
||||
*/
|
||||
async getFileSize(file: any): Promise<number> {
|
||||
if (file.filesize) {
|
||||
async getFileSize(file: CoreWSExternalFile | FileEntry): Promise<number> {
|
||||
if ('filesize' in file && (file.filesize || file.filesize === 0)) {
|
||||
return file.filesize;
|
||||
}
|
||||
|
||||
// If it's a remote file. First check if we have the file downloaded since it's more reliable.
|
||||
if (file.filename && !file.name) {
|
||||
if ('filename' in file) {
|
||||
const fileUrl = file.fileurl;
|
||||
|
||||
try {
|
||||
const siteId = CoreSites.instance.getCurrentSiteId();
|
||||
|
||||
const path = await CoreFilepool.instance.getFilePathByUrl(siteId, file.fileurl);
|
||||
const path = await CoreFilepool.instance.getFilePathByUrl(siteId, fileUrl);
|
||||
const fileEntry = await CoreFile.instance.getFile(path);
|
||||
const fileObject = await CoreFile.instance.getFileObjectFromFileEntry(fileEntry);
|
||||
|
||||
return fileObject.size;
|
||||
} catch (error) {
|
||||
// Error getting the file, maybe it's not downloaded. Get remote size.
|
||||
const size = await CoreWS.instance.getRemoteFileSize(file.fileurl);
|
||||
const size = await CoreWS.instance.getRemoteFileSize(fileUrl);
|
||||
|
||||
if (size === -1) {
|
||||
throw new Error('Couldn\'t determine file size: ' + file.fileurl);
|
||||
throw new CoreError(`Couldn't determine file size: ${fileUrl}`);
|
||||
}
|
||||
|
||||
return size;
|
||||
|
@ -319,13 +321,13 @@ export class CoreFileHelperProvider {
|
|||
}
|
||||
|
||||
// If it's a local file, get its size.
|
||||
if (file.name) {
|
||||
if ('name' in file) {
|
||||
const fileObject = await CoreFile.instance.getFileObjectFromFileEntry(file);
|
||||
|
||||
return fileObject.size;
|
||||
}
|
||||
|
||||
throw new Error('Couldn\'t determine file size: ' + file.fileurl);
|
||||
throw new CoreError('Couldn\'t determine file size');
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -334,7 +336,7 @@ export class CoreFileHelperProvider {
|
|||
* @param file The file to check.
|
||||
* @return bool.
|
||||
*/
|
||||
isOpenableInApp(file: {filename?: string, name?: string}): boolean {
|
||||
isOpenableInApp(file: {filename?: string; name?: string}): boolean {
|
||||
const re = /(?:\.([^.]+))?$/;
|
||||
|
||||
const ext = re.exec(file.filename || file.name)[1];
|
||||
|
@ -363,7 +365,7 @@ export class CoreFileHelperProvider {
|
|||
*/
|
||||
isFileTypeExcludedInApp(fileType: string): boolean {
|
||||
const currentSite = CoreSites.instance.getCurrentSite();
|
||||
const fileTypeExcludeList = currentSite && currentSite.getStoredConfig('tool_mobile_filetypeexclusionlist');
|
||||
const fileTypeExcludeList = currentSite && <string> currentSite.getStoredConfig('tool_mobile_filetypeexclusionlist');
|
||||
|
||||
if (!fileTypeExcludeList) {
|
||||
return false;
|
||||
|
@ -373,6 +375,10 @@ export class CoreFileHelperProvider {
|
|||
|
||||
return !!fileTypeExcludeList.match(regEx);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export class CoreFileHelper extends makeSingleton(CoreFileHelperProvider) {}
|
||||
|
||||
export type CoreFileHelperOnProgress = (event?: ProgressEvent | { calculating: true }) => void;
|
||||
|
||||
|
|
|
@ -13,8 +13,10 @@
|
|||
// limitations under the License.
|
||||
|
||||
import { Injectable } from '@angular/core';
|
||||
import { FileEntry } from '@ionic-native/file';
|
||||
|
||||
import { CoreSites } from '@services/sites';
|
||||
import { CoreWSExternalFile } from '@services/ws';
|
||||
import { makeSingleton } from '@singletons/core.singletons';
|
||||
|
||||
/**
|
||||
|
@ -26,9 +28,8 @@ import { makeSingleton } from '@singletons/core.singletons';
|
|||
*/
|
||||
@Injectable()
|
||||
export class CoreFileSessionProvider {
|
||||
protected files = {};
|
||||
|
||||
constructor() { }
|
||||
protected files: {[siteId: string]: {[component: string]: {[id: string]: (CoreWSExternalFile | FileEntry)[]}}} = {};
|
||||
|
||||
/**
|
||||
* Add a file to the session.
|
||||
|
@ -38,7 +39,7 @@ export class CoreFileSessionProvider {
|
|||
* @param file File to add.
|
||||
* @param siteId Site ID. If not defined, current site.
|
||||
*/
|
||||
addFile(component: string, id: string | number, file: any, siteId?: string): void {
|
||||
addFile(component: string, id: string | number, file: CoreWSExternalFile | FileEntry, siteId?: string): void {
|
||||
siteId = siteId || CoreSites.instance.getCurrentSiteId();
|
||||
|
||||
this.initFileArea(component, id, siteId);
|
||||
|
@ -68,7 +69,7 @@ export class CoreFileSessionProvider {
|
|||
* @param siteId Site ID. If not defined, current site.
|
||||
* @return Array of files in session.
|
||||
*/
|
||||
getFiles(component: string, id: string | number, siteId?: string): any[] {
|
||||
getFiles(component: string, id: string | number, siteId?: string): (CoreWSExternalFile | FileEntry)[] {
|
||||
siteId = siteId || CoreSites.instance.getCurrentSiteId();
|
||||
if (this.files[siteId] && this.files[siteId][component] && this.files[siteId][component][id]) {
|
||||
return this.files[siteId][component][id];
|
||||
|
@ -106,7 +107,7 @@ export class CoreFileSessionProvider {
|
|||
* @param file File to remove. The instance should be exactly the same as the one stored in session.
|
||||
* @param siteId Site ID. If not defined, current site.
|
||||
*/
|
||||
removeFile(component: string, id: string | number, file: any, siteId?: string): void {
|
||||
removeFile(component: string, id: string | number, file: CoreWSExternalFile | FileEntry, siteId?: string): void {
|
||||
siteId = siteId || CoreSites.instance.getCurrentSiteId();
|
||||
if (this.files[siteId] && this.files[siteId][component] && this.files[siteId][component][id]) {
|
||||
const position = this.files[siteId][component][id].indexOf(file);
|
||||
|
@ -140,13 +141,14 @@ export class CoreFileSessionProvider {
|
|||
* @param newFiles Files to set.
|
||||
* @param siteId Site ID. If not defined, current site.
|
||||
*/
|
||||
setFiles(component: string, id: string | number, newFiles: any[], siteId?: string): void {
|
||||
setFiles(component: string, id: string | number, newFiles: (CoreWSExternalFile | FileEntry)[], siteId?: string): void {
|
||||
siteId = siteId || CoreSites.instance.getCurrentSiteId();
|
||||
|
||||
this.initFileArea(component, id, siteId);
|
||||
|
||||
this.files[siteId][component][id] = newFiles;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export class CoreFileSession extends makeSingleton(CoreFileSessionProvider) {}
|
||||
|
|
|
@ -14,15 +14,18 @@
|
|||
|
||||
import { Injectable } from '@angular/core';
|
||||
|
||||
import { FileEntry, DirectoryEntry, Entry, Metadata } from '@ionic-native/file';
|
||||
import { FileEntry, DirectoryEntry, Entry, Metadata, IFile } from '@ionic-native/file';
|
||||
|
||||
import { CoreApp } from '@services/app';
|
||||
import { CoreWSExternalFile } from '@services/ws';
|
||||
import { CoreMimetypeUtils } from '@services/utils/mimetype';
|
||||
import { CoreTextUtils } from '@services/utils/text';
|
||||
import { CoreUtils } from '@services/utils/utils';
|
||||
import CoreConfigConstants from '@app/config.json';
|
||||
import { CoreError } from '@classes/errors/error';
|
||||
|
||||
import { CoreLogger } from '@singletons/logger';
|
||||
import { makeSingleton, File, Zip, Platform } from '@singletons/core.singletons';
|
||||
import { makeSingleton, File, Zip, Platform, WebView } from '@singletons/core.singletons';
|
||||
|
||||
/**
|
||||
* Progress event used when writing a file data into a file.
|
||||
|
@ -49,23 +52,35 @@ export type CoreFileProgressEvent = {
|
|||
*/
|
||||
export type CoreFileProgressFunction = (event: CoreFileProgressEvent) => void;
|
||||
|
||||
/**
|
||||
* Constants to define the format to read a file.
|
||||
*/
|
||||
export const enum CoreFileFormat {
|
||||
FORMATTEXT = 0,
|
||||
FORMATDATAURL = 1,
|
||||
FORMATBINARYSTRING = 2,
|
||||
FORMATARRAYBUFFER = 3,
|
||||
FORMATJSON = 4,
|
||||
}
|
||||
|
||||
/**
|
||||
* Factory to interact with the file system.
|
||||
*/
|
||||
@Injectable()
|
||||
export class CoreFileProvider {
|
||||
|
||||
// Formats to read a file.
|
||||
static FORMATTEXT = 0;
|
||||
static FORMATDATAURL = 1;
|
||||
static FORMATBINARYSTRING = 2;
|
||||
static FORMATARRAYBUFFER = 3;
|
||||
static FORMATJSON = 4;
|
||||
static readonly FORMATTEXT = CoreFileFormat.FORMATTEXT;
|
||||
static readonly FORMATDATAURL = CoreFileFormat.FORMATDATAURL;
|
||||
static readonly FORMATBINARYSTRING = CoreFileFormat.FORMATBINARYSTRING;
|
||||
static readonly FORMATARRAYBUFFER = CoreFileFormat.FORMATARRAYBUFFER;
|
||||
static readonly FORMATJSON = CoreFileFormat.FORMATJSON;
|
||||
|
||||
// Folders.
|
||||
static SITESFOLDER = 'sites';
|
||||
static TMPFOLDER = 'tmp';
|
||||
static readonly SITESFOLDER = 'sites';
|
||||
static readonly TMPFOLDER = 'tmp';
|
||||
|
||||
static CHUNK_SIZE = 1048576; // 1 MB. Same chunk size as Ionic Native.
|
||||
static readonly CHUNK_SIZE = 1048576; // 1 MB. Same chunk size as Ionic Native.
|
||||
|
||||
protected logger: CoreLogger;
|
||||
protected initialized = false;
|
||||
|
@ -73,73 +88,9 @@ export class CoreFileProvider {
|
|||
protected isHTMLAPI = false;
|
||||
|
||||
constructor() {
|
||||
|
||||
this.logger = CoreLogger.getInstance('CoreFileProvider');
|
||||
|
||||
if (CoreApp.instance.isAndroid() && !Object.getOwnPropertyDescriptor(FileReader.prototype, 'onloadend')) {
|
||||
// Cordova File plugin creates some getters and setter for FileReader, but Ionic's polyfills override them in Android.
|
||||
// Create the getters and setters again. This code comes from FileReader.js in cordova-plugin-file.
|
||||
// @todo: Check if this is still needed.
|
||||
this.defineGetterSetter(FileReader.prototype, 'readyState', function(): any {
|
||||
return this._localURL ? this._readyState : this._realReader.readyState;
|
||||
});
|
||||
|
||||
this.defineGetterSetter(FileReader.prototype, 'error', function(): any {
|
||||
return this._localURL ? this._error : this._realReader.error;
|
||||
});
|
||||
|
||||
this.defineGetterSetter(FileReader.prototype, 'result', function(): any {
|
||||
return this._localURL ? this._result : this._realReader.result;
|
||||
});
|
||||
|
||||
this.defineEvent('onloadstart');
|
||||
this.defineEvent('onprogress');
|
||||
this.defineEvent('onload');
|
||||
this.defineEvent('onerror');
|
||||
this.defineEvent('onloadend');
|
||||
this.defineEvent('onabort');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Define an event for FileReader.
|
||||
*
|
||||
* @param eventName Name of the event.
|
||||
*/
|
||||
protected defineEvent(eventName: string): void {
|
||||
this.defineGetterSetter(FileReader.prototype, eventName, function(): any {
|
||||
return this._realReader[eventName] || null;
|
||||
}, function(value: any): void {
|
||||
this._realReader[eventName] = value;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Define a getter and, optionally, a setter for a certain property in an object.
|
||||
*
|
||||
* @param obj Object to set the getter/setter for.
|
||||
* @param key Name of the property where to set them.
|
||||
* @param getFunc The getter function.
|
||||
* @param setFunc The setter function.
|
||||
*/
|
||||
protected defineGetterSetter(obj: any, key: string, getFunc: () => any, setFunc?: (value?: any) => any): void {
|
||||
if (Object.defineProperty) {
|
||||
const desc: any = {
|
||||
get: getFunc,
|
||||
configurable: true
|
||||
};
|
||||
|
||||
if (setFunc) {
|
||||
desc.set = setFunc;
|
||||
}
|
||||
|
||||
Object.defineProperty(obj, key, desc);
|
||||
} else {
|
||||
obj.__defineGetter__(key, getFunc);
|
||||
if (setFunc) {
|
||||
obj.__defineSetter__(key, setFunc);
|
||||
}
|
||||
}
|
||||
// @todo: Check if redefining FileReader getters and setters is still needed in Android.
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -172,7 +123,6 @@ export class CoreFileProvider {
|
|||
}
|
||||
|
||||
return Platform.instance.ready().then(() => {
|
||||
|
||||
if (CoreApp.instance.isAndroid()) {
|
||||
this.basePath = File.instance.externalApplicationStorageDirectory || this.basePath;
|
||||
} else if (CoreApp.instance.isIOS()) {
|
||||
|
@ -180,7 +130,7 @@ export class CoreFileProvider {
|
|||
} else if (!this.isAvailable() || this.basePath === '') {
|
||||
this.logger.error('Error getting device OS.');
|
||||
|
||||
return Promise.reject(null);
|
||||
return Promise.reject(new CoreError('Error getting device OS to initialize file system.'));
|
||||
}
|
||||
|
||||
this.initialized = true;
|
||||
|
@ -208,9 +158,7 @@ export class CoreFileProvider {
|
|||
this.logger.debug('Get file: ' + path);
|
||||
|
||||
return File.instance.resolveLocalFilesystemUrl(this.addBasePathIfNeeded(path));
|
||||
}).then((entry) => {
|
||||
return <FileEntry> entry;
|
||||
});
|
||||
}).then((entry) => <FileEntry> entry);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -246,39 +194,36 @@ export class CoreFileProvider {
|
|||
* @param base Base path to create the dir/file in. If not set, use basePath.
|
||||
* @return Promise to be resolved when the dir/file is created.
|
||||
*/
|
||||
protected create(isDirectory: boolean, path: string, failIfExists?: boolean, base?: string): Promise<any> {
|
||||
return this.init().then(() => {
|
||||
// Remove basePath if it's in the path.
|
||||
path = this.removeStartingSlash(path.replace(this.basePath, ''));
|
||||
base = base || this.basePath;
|
||||
protected async create(isDirectory: boolean, path: string, failIfExists?: boolean, base?: string):
|
||||
Promise<FileEntry | DirectoryEntry> {
|
||||
await this.init();
|
||||
|
||||
if (path.indexOf('/') == -1) {
|
||||
if (isDirectory) {
|
||||
this.logger.debug('Create dir ' + path + ' in ' + base);
|
||||
// Remove basePath if it's in the path.
|
||||
path = this.removeStartingSlash(path.replace(this.basePath, ''));
|
||||
base = base || this.basePath;
|
||||
|
||||
return File.instance.createDir(base, path, !failIfExists);
|
||||
} else {
|
||||
this.logger.debug('Create file ' + path + ' in ' + base);
|
||||
if (path.indexOf('/') == -1) {
|
||||
if (isDirectory) {
|
||||
this.logger.debug('Create dir ' + path + ' in ' + base);
|
||||
|
||||
return File.instance.createFile(base, path, !failIfExists);
|
||||
}
|
||||
return File.instance.createDir(base, path, !failIfExists);
|
||||
} else {
|
||||
// The file plugin doesn't allow creating more than 1 level at a time (e.g. tmp/folder).
|
||||
// We need to create them 1 by 1.
|
||||
const firstDir = path.substr(0, path.indexOf('/'));
|
||||
const restOfPath = path.substr(path.indexOf('/') + 1);
|
||||
this.logger.debug('Create file ' + path + ' in ' + base);
|
||||
|
||||
this.logger.debug('Create dir ' + firstDir + ' in ' + base);
|
||||
|
||||
return File.instance.createDir(base, firstDir, true).then((newDirEntry) => {
|
||||
return this.create(isDirectory, restOfPath, failIfExists, newDirEntry.toURL());
|
||||
}).catch((error) => {
|
||||
this.logger.error('Error creating directory ' + firstDir + ' in ' + base);
|
||||
|
||||
return Promise.reject(error);
|
||||
});
|
||||
return File.instance.createFile(base, path, !failIfExists);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
// The file plugin doesn't allow creating more than 1 level at a time (e.g. tmp/folder).
|
||||
// We need to create them 1 by 1.
|
||||
const firstDir = path.substr(0, path.indexOf('/'));
|
||||
const restOfPath = path.substr(path.indexOf('/') + 1);
|
||||
|
||||
this.logger.debug('Create dir ' + firstDir + ' in ' + base);
|
||||
|
||||
const newDirEntry = await File.instance.createDir(base, firstDir, true);
|
||||
|
||||
return this.create(isDirectory, restOfPath, failIfExists, newDirEntry.toURL());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -288,8 +233,10 @@ export class CoreFileProvider {
|
|||
* @param failIfExists True if it should fail if the directory exists, false otherwise.
|
||||
* @return Promise to be resolved when the directory is created.
|
||||
*/
|
||||
createDir(path: string, failIfExists?: boolean): Promise<DirectoryEntry> {
|
||||
return this.create(true, path, failIfExists);
|
||||
async createDir(path: string, failIfExists?: boolean): Promise<DirectoryEntry> {
|
||||
const entry = <DirectoryEntry> await this.create(true, path, failIfExists);
|
||||
|
||||
return entry;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -299,8 +246,10 @@ export class CoreFileProvider {
|
|||
* @param failIfExists True if it should fail if the file exists, false otherwise..
|
||||
* @return Promise to be resolved when the file is created.
|
||||
*/
|
||||
createFile(path: string, failIfExists?: boolean): Promise<FileEntry> {
|
||||
return this.create(false, path, failIfExists);
|
||||
async createFile(path: string, failIfExists?: boolean): Promise<FileEntry> {
|
||||
const entry = <FileEntry> await this.create(true, path, failIfExists);
|
||||
|
||||
return entry;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -309,14 +258,14 @@ export class CoreFileProvider {
|
|||
* @param path Relative path to the directory.
|
||||
* @return Promise to be resolved when the directory is deleted.
|
||||
*/
|
||||
removeDir(path: string): Promise<any> {
|
||||
return this.init().then(() => {
|
||||
// Remove basePath if it's in the path.
|
||||
path = this.removeStartingSlash(path.replace(this.basePath, ''));
|
||||
this.logger.debug('Remove directory: ' + path);
|
||||
async removeDir(path: string): Promise<void> {
|
||||
await this.init();
|
||||
|
||||
return File.instance.removeRecursively(this.basePath, path);
|
||||
});
|
||||
// Remove basePath if it's in the path.
|
||||
path = this.removeStartingSlash(path.replace(this.basePath, ''));
|
||||
this.logger.debug('Remove directory: ' + path);
|
||||
|
||||
await File.instance.removeRecursively(this.basePath, path);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -325,23 +274,25 @@ export class CoreFileProvider {
|
|||
* @param path Relative path to the file.
|
||||
* @return Promise to be resolved when the file is deleted.
|
||||
*/
|
||||
removeFile(path: string): Promise<any> {
|
||||
return this.init().then(() => {
|
||||
// Remove basePath if it's in the path.
|
||||
path = this.removeStartingSlash(path.replace(this.basePath, ''));
|
||||
this.logger.debug('Remove file: ' + path);
|
||||
async removeFile(path: string): Promise<void> {
|
||||
await this.init();
|
||||
|
||||
return File.instance.removeFile(this.basePath, path).catch((error) => {
|
||||
// The delete can fail if the path has encoded characters. Try again if that's the case.
|
||||
const decodedPath = decodeURI(path);
|
||||
// Remove basePath if it's in the path.
|
||||
path = this.removeStartingSlash(path.replace(this.basePath, ''));
|
||||
this.logger.debug('Remove file: ' + path);
|
||||
|
||||
if (decodedPath != path) {
|
||||
return File.instance.removeFile(this.basePath, decodedPath);
|
||||
} else {
|
||||
return Promise.reject(error);
|
||||
}
|
||||
});
|
||||
});
|
||||
try {
|
||||
await File.instance.removeFile(this.basePath, path);
|
||||
} catch (error) {
|
||||
// The delete can fail if the path has encoded characters. Try again if that's the case.
|
||||
const decodedPath = decodeURI(path);
|
||||
|
||||
if (decodedPath != path) {
|
||||
await File.instance.removeFile(this.basePath, decodedPath);
|
||||
} else {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -350,10 +301,8 @@ export class CoreFileProvider {
|
|||
* @param fileEntry File Entry.
|
||||
* @return Promise resolved when the file is deleted.
|
||||
*/
|
||||
removeFileByFileEntry(fileEntry: any): Promise<any> {
|
||||
return new Promise((resolve, reject): void => {
|
||||
fileEntry.remove(resolve, reject);
|
||||
});
|
||||
removeFileByFileEntry(entry: Entry): Promise<void> {
|
||||
return new Promise((resolve, reject) => entry.remove(resolve, reject));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -362,14 +311,26 @@ export class CoreFileProvider {
|
|||
* @param path Relative path to the directory.
|
||||
* @return Promise to be resolved when the contents are retrieved.
|
||||
*/
|
||||
getDirectoryContents(path: string): Promise<any> {
|
||||
return this.init().then(() => {
|
||||
// Remove basePath if it's in the path.
|
||||
path = this.removeStartingSlash(path.replace(this.basePath, ''));
|
||||
this.logger.debug('Get contents of dir: ' + path);
|
||||
async getDirectoryContents(path: string): Promise<(FileEntry | DirectoryEntry)[]> {
|
||||
await this.init();
|
||||
|
||||
return File.instance.listDir(this.basePath, path);
|
||||
});
|
||||
// Remove basePath if it's in the path.
|
||||
path = this.removeStartingSlash(path.replace(this.basePath, ''));
|
||||
this.logger.debug('Get contents of dir: ' + path);
|
||||
|
||||
const result = await File.instance.listDir(this.basePath, path);
|
||||
|
||||
return <(FileEntry | DirectoryEntry)[]> result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Type guard to check if the param is a DirectoryEntry.
|
||||
*
|
||||
* @param entry Param to check.
|
||||
* @return Whether the param is a DirectoryEntry.
|
||||
*/
|
||||
protected isDirectoryEntry(entry: FileEntry | DirectoryEntry): entry is DirectoryEntry {
|
||||
return entry.isDirectory === true;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -378,19 +339,18 @@ export class CoreFileProvider {
|
|||
* @param entry Directory or file.
|
||||
* @return Promise to be resolved when the size is calculated.
|
||||
*/
|
||||
protected getSize(entry: any): Promise<number> {
|
||||
return new Promise((resolve, reject): void => {
|
||||
if (entry.isDirectory) {
|
||||
protected getSize(entry: DirectoryEntry | FileEntry): Promise<number> {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (this.isDirectoryEntry(entry)) {
|
||||
const directoryReader = entry.createReader();
|
||||
directoryReader.readEntries((entries) => {
|
||||
|
||||
directoryReader.readEntries((entries: (DirectoryEntry | FileEntry)[]) => {
|
||||
const promises = [];
|
||||
for (let i = 0; i < entries.length; i++) {
|
||||
promises.push(this.getSize(entries[i]));
|
||||
}
|
||||
|
||||
Promise.all(promises).then((sizes) => {
|
||||
|
||||
let directorySize = 0;
|
||||
for (let i = 0; i < sizes.length; i++) {
|
||||
const fileSize = Number(sizes[i]);
|
||||
|
@ -402,12 +362,9 @@ export class CoreFileProvider {
|
|||
directorySize += fileSize;
|
||||
}
|
||||
resolve(directorySize);
|
||||
|
||||
}, reject);
|
||||
|
||||
}, reject);
|
||||
|
||||
} else if (entry.isFile) {
|
||||
} else {
|
||||
entry.file((file) => {
|
||||
resolve(file.size);
|
||||
}, reject);
|
||||
|
@ -427,9 +384,7 @@ export class CoreFileProvider {
|
|||
|
||||
this.logger.debug('Get size of dir: ' + path);
|
||||
|
||||
return this.getDir(path).then((dirEntry) => {
|
||||
return this.getSize(dirEntry);
|
||||
});
|
||||
return this.getDir(path).then((dirEntry) => this.getSize(dirEntry));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -444,9 +399,7 @@ export class CoreFileProvider {
|
|||
|
||||
this.logger.debug('Get size of file: ' + path);
|
||||
|
||||
return this.getFile(path).then((fileEntry) => {
|
||||
return this.getSize(fileEntry);
|
||||
});
|
||||
return this.getFile(path).then((fileEntry) => this.getSize(fileEntry));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -455,7 +408,7 @@ export class CoreFileProvider {
|
|||
* @param path Relative path to the file.
|
||||
* @return Promise to be resolved when the file is retrieved.
|
||||
*/
|
||||
getFileObjectFromFileEntry(entry: FileEntry): Promise<any> {
|
||||
getFileObjectFromFileEntry(entry: FileEntry): Promise<IFile> {
|
||||
return new Promise((resolve, reject): void => {
|
||||
this.logger.debug('Get file object of: ' + entry.fullPath);
|
||||
entry.file(resolve, reject);
|
||||
|
@ -496,15 +449,10 @@ export class CoreFileProvider {
|
|||
* Read a file from local file system.
|
||||
*
|
||||
* @param path Relative path to the file.
|
||||
* @param format Format to read the file. Must be one of:
|
||||
* FORMATTEXT
|
||||
* FORMATDATAURL
|
||||
* FORMATBINARYSTRING
|
||||
* FORMATARRAYBUFFER
|
||||
* FORMATJSON
|
||||
* @param format Format to read the file.
|
||||
* @return Promise to be resolved when the file is read.
|
||||
*/
|
||||
readFile(path: string, format: number = CoreFileProvider.FORMATTEXT): Promise<any> {
|
||||
readFile(path: string, format: CoreFileFormat = CoreFileProvider.FORMATTEXT): Promise<string | ArrayBuffer | unknown> {
|
||||
// Remove basePath if it's in the path.
|
||||
path = this.removeStartingSlash(path.replace(this.basePath, ''));
|
||||
this.logger.debug('Read file ' + path + ' with format ' + format);
|
||||
|
@ -521,7 +469,7 @@ export class CoreFileProvider {
|
|||
const parsed = CoreTextUtils.instance.parseJSON(text, null);
|
||||
|
||||
if (parsed == null && text != null) {
|
||||
return Promise.reject('Error parsing JSON file: ' + path);
|
||||
return Promise.reject(new CoreError('Error parsing JSON file: ' + path));
|
||||
}
|
||||
|
||||
return parsed;
|
||||
|
@ -535,27 +483,21 @@ export class CoreFileProvider {
|
|||
* Read file contents from a file data object.
|
||||
*
|
||||
* @param fileData File's data.
|
||||
* @param format Format to read the file. Must be one of:
|
||||
* FORMATTEXT
|
||||
* FORMATDATAURL
|
||||
* FORMATBINARYSTRING
|
||||
* FORMATARRAYBUFFER
|
||||
* FORMATJSON
|
||||
* @param format Format to read the file.
|
||||
* @return Promise to be resolved when the file is read.
|
||||
*/
|
||||
readFileData(fileData: any, format: number = CoreFileProvider.FORMATTEXT): Promise<any> {
|
||||
readFileData(fileData: IFile, format: CoreFileFormat = CoreFileProvider.FORMATTEXT): Promise<string | ArrayBuffer | unknown> {
|
||||
format = format || CoreFileProvider.FORMATTEXT;
|
||||
this.logger.debug('Read file from file data with format ' + format);
|
||||
|
||||
return new Promise((resolve, reject): void => {
|
||||
const reader = new FileReader();
|
||||
|
||||
reader.onloadend = (evt): void => {
|
||||
const target = <any> evt.target; // Convert to <any> to be able to use non-standard properties.
|
||||
if (target.result !== undefined && target.result !== null) {
|
||||
reader.onloadend = (event): void => {
|
||||
if (event.target.result !== undefined && event.target.result !== null) {
|
||||
if (format == CoreFileProvider.FORMATJSON) {
|
||||
// Convert to object.
|
||||
const parsed = CoreTextUtils.instance.parseJSON(target.result, null);
|
||||
const parsed = CoreTextUtils.instance.parseJSON(<string> event.target.result, null);
|
||||
|
||||
if (parsed == null) {
|
||||
reject('Error parsing JSON file.');
|
||||
|
@ -563,10 +505,10 @@ export class CoreFileProvider {
|
|||
|
||||
resolve(parsed);
|
||||
} else {
|
||||
resolve(target.result);
|
||||
resolve(event.target.result);
|
||||
}
|
||||
} else if (target.error !== undefined && target.error !== null) {
|
||||
reject(target.error);
|
||||
} else if (event.target.error !== undefined && event.target.error !== null) {
|
||||
reject(event.target.error);
|
||||
} else {
|
||||
reject({ code: null, message: 'READER_ONLOADEND_ERR' });
|
||||
}
|
||||
|
@ -575,7 +517,7 @@ export class CoreFileProvider {
|
|||
// Check if the load starts. If it doesn't start in 3 seconds, reject.
|
||||
// Sometimes in Android the read doesn't start for some reason, so the promise never finishes.
|
||||
let hasStarted = false;
|
||||
reader.onloadstart = (evt): void => {
|
||||
reader.onloadstart = () => {
|
||||
hasStarted = true;
|
||||
};
|
||||
setTimeout(() => {
|
||||
|
@ -597,7 +539,6 @@ export class CoreFileProvider {
|
|||
default:
|
||||
reader.readAsText(fileData);
|
||||
}
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -609,7 +550,7 @@ export class CoreFileProvider {
|
|||
* @param append Whether to append the data to the end of the file.
|
||||
* @return Promise to be resolved when the file is written.
|
||||
*/
|
||||
writeFile(path: string, data: any, append?: boolean): Promise<FileEntry> {
|
||||
writeFile(path: string, data: string | Blob, append?: boolean): Promise<FileEntry> {
|
||||
return this.init().then(() => {
|
||||
// Remove basePath if it's in the path.
|
||||
path = this.removeStartingSlash(path.replace(this.basePath, ''));
|
||||
|
@ -624,9 +565,8 @@ export class CoreFileProvider {
|
|||
data = new Blob([data], { type: type || 'text/plain' });
|
||||
}
|
||||
|
||||
return File.instance.writeFile(this.basePath, path, data, { replace: !append, append: !!append }).then(() => {
|
||||
return fileEntry;
|
||||
});
|
||||
return File.instance.writeFile(this.basePath, path, data, { replace: !append, append: !!append })
|
||||
.then(() => fileEntry);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
@ -644,8 +584,7 @@ export class CoreFileProvider {
|
|||
* @return Promise resolved when done.
|
||||
*/
|
||||
async writeFileDataInFile(file: Blob, path: string, onProgress?: CoreFileProgressFunction, offset: number = 0,
|
||||
append?: boolean): Promise<FileEntry> {
|
||||
|
||||
append?: boolean): Promise<FileEntry> {
|
||||
offset = offset || 0;
|
||||
|
||||
try {
|
||||
|
@ -659,7 +598,7 @@ export class CoreFileProvider {
|
|||
onProgress && onProgress({
|
||||
lengthComputable: true,
|
||||
loaded: offset,
|
||||
total: file.size
|
||||
total: file.size,
|
||||
});
|
||||
|
||||
if (offset >= file.size) {
|
||||
|
@ -671,8 +610,8 @@ export class CoreFileProvider {
|
|||
return this.writeFileDataInFile(file, path, onProgress, offset, true);
|
||||
} catch (error) {
|
||||
if (error && error.target && error.target.error) {
|
||||
// Error returned by the writer, get the "real" error.
|
||||
error = error.target.error;
|
||||
// Error returned by the writer, throw the "real" error.
|
||||
throw error.target.error;
|
||||
}
|
||||
|
||||
throw error;
|
||||
|
@ -686,9 +625,7 @@ export class CoreFileProvider {
|
|||
* @return Promise to be resolved when the file is retrieved.
|
||||
*/
|
||||
getExternalFile(fullPath: string): Promise<FileEntry> {
|
||||
return File.instance.resolveLocalFilesystemUrl(fullPath).then((entry) => {
|
||||
return <FileEntry> entry;
|
||||
});
|
||||
return File.instance.resolveLocalFilesystemUrl(fullPath).then((entry) => <FileEntry> entry);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -709,11 +646,11 @@ export class CoreFileProvider {
|
|||
* @param fullPath Absolute path to the file.
|
||||
* @return Promise to be resolved when the file is removed.
|
||||
*/
|
||||
removeExternalFile(fullPath: string): Promise<any> {
|
||||
async removeExternalFile(fullPath: string): Promise<void> {
|
||||
const directory = fullPath.substring(0, fullPath.lastIndexOf('/'));
|
||||
const filename = fullPath.substr(fullPath.lastIndexOf('/') + 1);
|
||||
|
||||
return File.instance.removeFile(directory, filename);
|
||||
await File.instance.removeFile(directory, filename);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -742,9 +679,7 @@ export class CoreFileProvider {
|
|||
return this.init().then(() => {
|
||||
if (CoreApp.instance.isIOS()) {
|
||||
// In iOS we want the internal URL (cdvfile://localhost/persistent/...).
|
||||
return File.instance.resolveDirectoryUrl(this.basePath).then((dirEntry) => {
|
||||
return dirEntry.toInternalURL();
|
||||
});
|
||||
return File.instance.resolveDirectoryUrl(this.basePath).then((dirEntry) => dirEntry.toInternalURL());
|
||||
} else {
|
||||
// In the other platforms we use the basePath as it is (file://...).
|
||||
return this.basePath;
|
||||
|
@ -776,8 +711,10 @@ export class CoreFileProvider {
|
|||
* try to create it (slower).
|
||||
* @return Promise resolved when the entry is moved.
|
||||
*/
|
||||
moveDir(originalPath: string, newPath: string, destDirExists?: boolean): Promise<any> {
|
||||
return this.copyOrMoveFileOrDir(originalPath, newPath, true, false, destDirExists);
|
||||
async moveDir(originalPath: string, newPath: string, destDirExists?: boolean): Promise<DirectoryEntry> {
|
||||
const entry = await this.copyOrMoveFileOrDir(originalPath, newPath, true, false, destDirExists);
|
||||
|
||||
return <DirectoryEntry> entry;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -789,8 +726,10 @@ export class CoreFileProvider {
|
|||
* try to create it (slower).
|
||||
* @return Promise resolved when the entry is moved.
|
||||
*/
|
||||
moveFile(originalPath: string, newPath: string, destDirExists?: boolean): Promise<any> {
|
||||
return this.copyOrMoveFileOrDir(originalPath, newPath, false, false, destDirExists);
|
||||
async moveFile(originalPath: string, newPath: string, destDirExists?: boolean): Promise<FileEntry> {
|
||||
const entry = await this.copyOrMoveFileOrDir(originalPath, newPath, false, false, destDirExists);
|
||||
|
||||
return <FileEntry> entry;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -802,8 +741,10 @@ export class CoreFileProvider {
|
|||
* try to create it (slower).
|
||||
* @return Promise resolved when the entry is copied.
|
||||
*/
|
||||
copyDir(from: string, to: string, destDirExists?: boolean): Promise<any> {
|
||||
return this.copyOrMoveFileOrDir(from, to, true, true, destDirExists);
|
||||
async copyDir(from: string, to: string, destDirExists?: boolean): Promise<DirectoryEntry> {
|
||||
const entry = await this.copyOrMoveFileOrDir(from, to, true, true, destDirExists);
|
||||
|
||||
return <DirectoryEntry> entry;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -815,8 +756,10 @@ export class CoreFileProvider {
|
|||
* try to create it (slower).
|
||||
* @return Promise resolved when the entry is copied.
|
||||
*/
|
||||
copyFile(from: string, to: string, destDirExists?: boolean): Promise<any> {
|
||||
return this.copyOrMoveFileOrDir(from, to, false, true, destDirExists);
|
||||
async copyFile(from: string, to: string, destDirExists?: boolean): Promise<FileEntry> {
|
||||
const entry = await this.copyOrMoveFileOrDir(from, to, false, true, destDirExists);
|
||||
|
||||
return <FileEntry> entry;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -830,16 +773,16 @@ export class CoreFileProvider {
|
|||
* try to create it (slower).
|
||||
* @return Promise resolved when the entry is copied.
|
||||
*/
|
||||
protected async copyOrMoveFileOrDir(from: string, to: string, isDir?: boolean, copy?: boolean, destDirExists?: boolean)
|
||||
: Promise<Entry> {
|
||||
|
||||
protected async copyOrMoveFileOrDir(from: string, to: string, isDir?: boolean, copy?: boolean, destDirExists?: boolean):
|
||||
Promise<FileEntry | DirectoryEntry> {
|
||||
const fileIsInAppFolder = this.isPathInAppFolder(from);
|
||||
|
||||
if (!fileIsInAppFolder) {
|
||||
return this.copyOrMoveExternalFile(from, to, copy);
|
||||
}
|
||||
|
||||
const moveCopyFn = copy ?
|
||||
const moveCopyFn: (path: string, dirName: string, newPath: string, newDirName: string) =>
|
||||
Promise<FileEntry | DirectoryEntry> = copy ?
|
||||
(isDir ? File.instance.copyDir.bind(File.instance) : File.instance.copyFile.bind(File.instance)) :
|
||||
(isDir ? File.instance.moveDir.bind(File.instance) : File.instance.moveFile.bind(File.instance));
|
||||
|
||||
|
@ -885,10 +828,10 @@ export class CoreFileProvider {
|
|||
* path/ -> directory: 'path', name: ''
|
||||
* path -> directory: '', name: 'path'
|
||||
*/
|
||||
getFileAndDirectoryFromPath(path: string): {directory: string, name: string} {
|
||||
getFileAndDirectoryFromPath(path: string): {directory: string; name: string} {
|
||||
const file = {
|
||||
directory: '',
|
||||
name: ''
|
||||
name: '',
|
||||
};
|
||||
|
||||
file.directory = path.substring(0, path.lastIndexOf('/'));
|
||||
|
@ -949,7 +892,8 @@ export class CoreFileProvider {
|
|||
* @param recreateDir Delete the dest directory before unzipping. Defaults to true.
|
||||
* @return Promise resolved when the file is unzipped.
|
||||
*/
|
||||
unzipFile(path: string, destFolder?: string, onProgress?: (progress: any) => void, recreateDir: boolean = true): Promise<any> {
|
||||
unzipFile(path: string, destFolder?: string, onProgress?: (progress: ProgressEvent) => void, recreateDir: boolean = true):
|
||||
Promise<void> {
|
||||
// Get the source file.
|
||||
let fileEntry: FileEntry;
|
||||
|
||||
|
@ -960,10 +904,10 @@ export class CoreFileProvider {
|
|||
// Make sure the dest dir doesn't exist already.
|
||||
return this.removeDir(destFolder).catch(() => {
|
||||
// Ignore errors.
|
||||
}).then(() => {
|
||||
}).then(() =>
|
||||
// Now create the dir, otherwise if any of the ancestor dirs doesn't exist the unzip would fail.
|
||||
return this.createDir(destFolder);
|
||||
});
|
||||
this.createDir(destFolder),
|
||||
);
|
||||
}
|
||||
}).then(() => {
|
||||
// If destFolder is not set, use same location as ZIP file. We need to use absolute paths (including basePath).
|
||||
|
@ -972,7 +916,7 @@ export class CoreFileProvider {
|
|||
return Zip.instance.unzip(fileEntry.toURL(), destFolder, onProgress);
|
||||
}).then((result) => {
|
||||
if (result == -1) {
|
||||
return Promise.reject('Unzip failed.');
|
||||
return Promise.reject(new CoreError('Unzip failed.'));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -985,18 +929,18 @@ export class CoreFileProvider {
|
|||
* @param newValue New value.
|
||||
* @return Promise resolved in success.
|
||||
*/
|
||||
replaceInFile(path: string, search: string | RegExp, newValue: string): Promise<any> {
|
||||
return this.readFile(path).then((content) => {
|
||||
if (typeof content == 'undefined' || content === null || !content.replace) {
|
||||
return Promise.reject(null);
|
||||
}
|
||||
async replaceInFile(path: string, search: string | RegExp, newValue: string): Promise<void> {
|
||||
let content = <string> await this.readFile(path);
|
||||
|
||||
if (content.match(search)) {
|
||||
content = content.replace(search, newValue);
|
||||
if (typeof content == 'undefined' || content === null || !content.replace) {
|
||||
throw new CoreError(`Error reading file ${path}`);
|
||||
}
|
||||
|
||||
return this.writeFile(path, content);
|
||||
}
|
||||
});
|
||||
if (content.match(search)) {
|
||||
content = content.replace(search, newValue);
|
||||
|
||||
await this.writeFile(path, content);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1007,7 +951,7 @@ export class CoreFileProvider {
|
|||
*/
|
||||
getMetadata(fileEntry: Entry): Promise<Metadata> {
|
||||
if (!fileEntry || !fileEntry.getMetadata) {
|
||||
return Promise.reject(null);
|
||||
return Promise.reject(new CoreError('Cannot get metadata from file entry.'));
|
||||
}
|
||||
|
||||
return new Promise((resolve, reject): void => {
|
||||
|
@ -1022,7 +966,7 @@ export class CoreFileProvider {
|
|||
* @param isDir True if directory, false if file.
|
||||
* @return Promise resolved with metadata.
|
||||
*/
|
||||
getMetadataFromPath(path: string, isDir?: boolean): Promise<any> {
|
||||
getMetadataFromPath(path: string, isDir?: boolean): Promise<Metadata> {
|
||||
let promise;
|
||||
if (isDir) {
|
||||
promise = this.getDir(path);
|
||||
|
@ -1030,9 +974,7 @@ export class CoreFileProvider {
|
|||
promise = this.getFile(path);
|
||||
}
|
||||
|
||||
return promise.then((entry) => {
|
||||
return this.getMetadata(entry);
|
||||
});
|
||||
return promise.then((entry) => this.getMetadata(entry));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1057,22 +999,22 @@ export class CoreFileProvider {
|
|||
* @param copy True to copy, false to move.
|
||||
* @return Promise resolved when the entry is copied/moved.
|
||||
*/
|
||||
protected copyOrMoveExternalFile(from: string, to: string, copy?: boolean): Promise<any> {
|
||||
protected copyOrMoveExternalFile(from: string, to: string, copy?: boolean): Promise<FileEntry> {
|
||||
// Get the file to copy/move.
|
||||
return this.getExternalFile(from).then((fileEntry) => {
|
||||
// Create the destination dir if it doesn't exist.
|
||||
const dirAndFile = this.getFileAndDirectoryFromPath(to);
|
||||
|
||||
return this.createDir(dirAndFile.directory).then((dirEntry) => {
|
||||
return this.createDir(dirAndFile.directory).then((dirEntry) =>
|
||||
// Now copy/move the file.
|
||||
return new Promise((resolve, reject): void => {
|
||||
new Promise((resolve, reject): void => {
|
||||
if (copy) {
|
||||
fileEntry.copyTo(dirEntry, dirAndFile.name, resolve, reject);
|
||||
fileEntry.copyTo(dirEntry, dirAndFile.name, (entry: FileEntry) => resolve(entry), reject);
|
||||
} else {
|
||||
fileEntry.moveTo(dirEntry, dirAndFile.name, resolve, reject);
|
||||
fileEntry.moveTo(dirEntry, dirAndFile.name, (entry: FileEntry) => resolve(entry), reject);
|
||||
}
|
||||
});
|
||||
});
|
||||
}),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -1083,7 +1025,7 @@ export class CoreFileProvider {
|
|||
* @param to Relative new path of the file (inside the app folder).
|
||||
* @return Promise resolved when the entry is copied.
|
||||
*/
|
||||
copyExternalFile(from: string, to: string): Promise<any> {
|
||||
copyExternalFile(from: string, to: string): Promise<FileEntry> {
|
||||
return this.copyOrMoveExternalFile(from, to, true);
|
||||
}
|
||||
|
||||
|
@ -1094,7 +1036,7 @@ export class CoreFileProvider {
|
|||
* @param to Relative new path of the file (inside the app folder).
|
||||
* @return Promise resolved when the entry is moved.
|
||||
*/
|
||||
moveExternalFile(from: string, to: string): Promise<any> {
|
||||
moveExternalFile(from: string, to: string): Promise<FileEntry> {
|
||||
return this.copyOrMoveExternalFile(from, to, false);
|
||||
}
|
||||
|
||||
|
@ -1144,10 +1086,10 @@ export class CoreFileProvider {
|
|||
// Ask the user what he wants to do.
|
||||
return newName;
|
||||
}
|
||||
}).catch(() => {
|
||||
}).catch(() =>
|
||||
// Folder doesn't exist, name is unique. Clean it and return it.
|
||||
return CoreTextUtils.instance.removeSpecialCharactersForFiles(CoreTextUtils.instance.decodeURIComponent(fileName));
|
||||
});
|
||||
CoreTextUtils.instance.removeSpecialCharactersForFiles(CoreTextUtils.instance.decodeURIComponent(fileName)),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1155,10 +1097,9 @@ export class CoreFileProvider {
|
|||
*
|
||||
* @return Promise resolved when done.
|
||||
*/
|
||||
clearTmpFolder(): Promise<any> {
|
||||
return this.removeDir(CoreFileProvider.TMPFOLDER).catch(() => {
|
||||
// Ignore errors because the folder might not exist.
|
||||
});
|
||||
async clearTmpFolder(): Promise<void> {
|
||||
// Ignore errors because the folder might not exist.
|
||||
await CoreUtils.instance.ignoreErrors(this.removeDir(CoreFileProvider.TMPFOLDER));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1168,19 +1109,21 @@ export class CoreFileProvider {
|
|||
* @param files List of used files.
|
||||
* @return Promise resolved when done, rejected if failure.
|
||||
*/
|
||||
removeUnusedFiles(dirPath: string, files: any[]): Promise<any> {
|
||||
async removeUnusedFiles(dirPath: string, files: (CoreWSExternalFile | FileEntry)[]): Promise<void> {
|
||||
// Get the directory contents.
|
||||
return this.getDirectoryContents(dirPath).then((contents) => {
|
||||
try {
|
||||
const contents = await this.getDirectoryContents(dirPath);
|
||||
|
||||
if (!contents.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
const filesMap = {};
|
||||
const filesMap: {[fullPath: string]: FileEntry} = {};
|
||||
const promises = [];
|
||||
|
||||
// Index the received files by fullPath and ignore the invalid ones.
|
||||
files.forEach((file) => {
|
||||
if (file.fullPath) {
|
||||
if ('fullPath' in file) {
|
||||
filesMap[file.fullPath] = file;
|
||||
}
|
||||
});
|
||||
|
@ -1193,10 +1136,10 @@ export class CoreFileProvider {
|
|||
}
|
||||
});
|
||||
|
||||
return Promise.all(promises);
|
||||
}).catch(() => {
|
||||
await Promise.all(promises);
|
||||
} catch (error) {
|
||||
// Ignore errors, maybe it doesn't exist.
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1246,7 +1189,7 @@ export class CoreFileProvider {
|
|||
* @return Converted src.
|
||||
*/
|
||||
convertFileSrc(src: string): string {
|
||||
return CoreApp.instance.isIOS() ? (<any> window).Ionic.WebView.convertFileSrc(src) : src;
|
||||
return CoreApp.instance.isIOS() ? WebView.instance.convertFileSrc(src) : src;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1272,6 +1215,7 @@ export class CoreFileProvider {
|
|||
protected isPathInAppFolder(path: string): boolean {
|
||||
return !path || !path.match(/^[a-z0-9]+:\/\//i) || path.indexOf(this.basePath) != -1;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export class CoreFile extends makeSingleton(CoreFileProvider) {}
|
||||
|
|
|
@ -29,8 +29,9 @@ import { CoreTimeUtils } from '@services/utils/time';
|
|||
import { CoreUrlUtils } from '@services/utils/url';
|
||||
import { CoreUtils, PromiseDefer } from '@services/utils/utils';
|
||||
import { SQLiteDB } from '@classes/sqlitedb';
|
||||
import { CoreError } from '@classes/errors/error';
|
||||
import { CoreConstants } from '@core/constants';
|
||||
import { makeSingleton, Network, NgZone } from '@singletons/core.singletons';
|
||||
import { makeSingleton, Network, NgZone, Translate } from '@singletons/core.singletons';
|
||||
import { CoreLogger } from '@singletons/logger';
|
||||
|
||||
/*
|
||||
|
@ -56,7 +57,7 @@ export class CoreFilepoolProvider {
|
|||
protected static readonly ERR_FS_OR_NETWORK_UNAVAILABLE = 'CoreFilepoolError:ERR_FS_OR_NETWORK_UNAVAILABLE';
|
||||
protected static readonly ERR_QUEUE_ON_PAUSE = 'CoreFilepoolError:ERR_QUEUE_ON_PAUSE';
|
||||
|
||||
protected static readonly FILE_UPDATE_ANY_WHERE_CLAUSE =
|
||||
protected static readonly FILE_UPDATE_UNKNOWN_WHERE_CLAUSE =
|
||||
'isexternalfile = 1 OR ((revision IS NULL OR revision = 0) AND (timemodified IS NULL OR timemodified = 0))';
|
||||
|
||||
// Variables for database.
|
||||
|
@ -239,7 +240,7 @@ export class CoreFilepoolProvider {
|
|||
protected appDB: SQLiteDB;
|
||||
protected dbReady: Promise<void>; // Promise resolved when the app DB is initialized.
|
||||
protected queueState: string;
|
||||
protected urlAttributes = [
|
||||
protected urlAttributes: RegExp[] = [
|
||||
new RegExp('(\\?|&)token=([A-Za-z0-9]*)'),
|
||||
new RegExp('(\\?|&)forcedownload=[0-1]'),
|
||||
new RegExp('(\\?|&)preview=[A-Za-z0-9]+'),
|
||||
|
@ -248,7 +249,7 @@ export class CoreFilepoolProvider {
|
|||
|
||||
// To handle file downloads using the queue.
|
||||
protected queueDeferreds: { [s: string]: { [s: string]: CoreFilepoolPromiseDefer } } = {};
|
||||
protected sizeCache = {}; // A "cache" to store file sizes to prevent performing too many HEAD requests.
|
||||
protected sizeCache: {[fileUrl: string]: number} = {}; // A "cache" to store file sizes.
|
||||
// Variables to prevent downloading packages/files twice at the same time.
|
||||
protected packagesPromises: { [s: string]: { [s: string]: Promise<void> } } = {};
|
||||
protected filePromises: { [s: string]: { [s: string]: Promise<string> } } = {};
|
||||
|
@ -288,7 +289,7 @@ export class CoreFilepoolProvider {
|
|||
*/
|
||||
protected async addFileLink(siteId: string, fileId: string, component: string, componentId?: string | number): Promise<void> {
|
||||
if (!component) {
|
||||
throw null;
|
||||
throw new CoreError('Cannot add link because component is invalid.');
|
||||
}
|
||||
|
||||
componentId = this.fixComponentId(componentId);
|
||||
|
@ -358,8 +359,10 @@ export class CoreFilepoolProvider {
|
|||
* @return Promise resolved on success.
|
||||
*/
|
||||
protected async addFileToPool(siteId: string, fileId: string, data: CoreFilepoolFileEntry): Promise<void> {
|
||||
const record = Object.assign({}, data);
|
||||
record.fileId = fileId;
|
||||
const record = {
|
||||
fileId,
|
||||
...data,
|
||||
};
|
||||
|
||||
const db = await CoreSites.instance.getSiteDb(siteId);
|
||||
|
||||
|
@ -457,12 +460,12 @@ export class CoreFilepoolProvider {
|
|||
await this.dbReady;
|
||||
|
||||
if (!CoreFile.instance.isAvailable()) {
|
||||
throw null;
|
||||
throw new CoreError('File system cannot be used.');
|
||||
}
|
||||
|
||||
const site = await CoreSites.instance.getSite(siteId);
|
||||
if (!site.canDownloadFiles()) {
|
||||
throw null;
|
||||
throw new CoreError('Site doesn\'t allow downloading files.');
|
||||
}
|
||||
|
||||
let file: CoreWSExternalFile;
|
||||
|
@ -488,7 +491,7 @@ export class CoreFilepoolProvider {
|
|||
const queueDeferred = this.getQueueDeferred(siteId, fileId, false, onProgress);
|
||||
|
||||
return this.hasFileInQueue(siteId, fileId).then((entry: CoreFilepoolQueueEntry) => {
|
||||
const newData: CoreFilepoolQueueEntry = {};
|
||||
const newData: CoreFilepoolQueueDBEntry = {};
|
||||
let foundLink = false;
|
||||
|
||||
if (entry) {
|
||||
|
@ -562,14 +565,14 @@ export class CoreFilepoolProvider {
|
|||
* @param componentId An ID to use in conjunction with the component.
|
||||
* @param timemodified The time this file was modified.
|
||||
* @param checkSize True if we shouldn't download files if their size is big, false otherwise.
|
||||
* @param downloadAny True to download file in WiFi if their size is any, false otherwise.
|
||||
* @param downloadUnknown True to download file in WiFi if their size is unknown, false otherwise.
|
||||
* Ignored if checkSize=false.
|
||||
* @param options Extra options (isexternalfile, repositorytype).
|
||||
* @param revision File revision. If not defined, it will be calculated using the URL.
|
||||
* @return Promise resolved when the file is downloaded.
|
||||
*/
|
||||
protected async addToQueueIfNeeded(siteId: string, fileUrl: string, component: string, componentId?: string | number,
|
||||
timemodified: number = 0, checkSize: boolean = true, downloadAny?: boolean, options: CoreFilepoolFileOptions = {},
|
||||
timemodified: number = 0, checkSize: boolean = true, downloadUnknown?: boolean, options: CoreFilepoolFileOptions = {},
|
||||
revision?: number): Promise<void> {
|
||||
if (!checkSize) {
|
||||
// No need to check size, just add it to the queue.
|
||||
|
@ -584,7 +587,7 @@ export class CoreFilepoolProvider {
|
|||
} else {
|
||||
if (!CoreApp.instance.isOnline()) {
|
||||
// Cannot check size in offline, stop.
|
||||
throw null;
|
||||
throw new CoreError(Translate.instance.instant('core.cannotconnect'));
|
||||
}
|
||||
|
||||
size = await CoreWS.instance.getRemoteFileSize(fileUrl);
|
||||
|
@ -592,16 +595,16 @@ export class CoreFilepoolProvider {
|
|||
|
||||
// Calculate the size of the file.
|
||||
const isWifi = CoreApp.instance.isWifi();
|
||||
const sizeAny = size <= 0;
|
||||
const sizeUnknown = size <= 0;
|
||||
|
||||
if (!sizeAny) {
|
||||
if (!sizeUnknown) {
|
||||
// Store the size in the cache.
|
||||
this.sizeCache[fileUrl] = size;
|
||||
}
|
||||
|
||||
// Check if the file should be downloaded.
|
||||
if (sizeAny) {
|
||||
if (downloadAny && isWifi) {
|
||||
if (sizeUnknown) {
|
||||
if (downloadUnknown && isWifi) {
|
||||
await this.addToQueueByUrl(siteId, fileUrl, component, componentId, timemodified, undefined, undefined,
|
||||
0, options, revision, true);
|
||||
}
|
||||
|
@ -685,7 +688,7 @@ export class CoreFilepoolProvider {
|
|||
|
||||
const count = await db.countRecords(CoreFilepoolProvider.LINKS_TABLE, conditions);
|
||||
if (count <= 0) {
|
||||
return null;
|
||||
throw new CoreError('Component doesn\'t have files');
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -696,7 +699,7 @@ export class CoreFilepoolProvider {
|
|||
* @param componentId An ID to use in conjunction with the component.
|
||||
* @return Link, null if nothing to link.
|
||||
*/
|
||||
protected createComponentLink(component: string, componentId?: string | number): CoreFilepoolComponentLink {
|
||||
protected createComponentLink(component: string, componentId?: string | number): CoreFilepoolComponentLink | null {
|
||||
if (typeof component != 'undefined' && component != null) {
|
||||
return { component, componentId: this.fixComponentId(componentId) };
|
||||
}
|
||||
|
@ -779,7 +782,7 @@ export class CoreFilepoolProvider {
|
|||
if (poolFileObject && poolFileObject.fileId !== fileId) {
|
||||
this.logger.error('Invalid object to update passed');
|
||||
|
||||
throw null;
|
||||
throw new CoreError('Invalid object to update passed.');
|
||||
}
|
||||
|
||||
const downloadId = this.getFileDownloadId(fileUrl, filePath);
|
||||
|
@ -793,7 +796,7 @@ export class CoreFilepoolProvider {
|
|||
|
||||
this.filePromises[siteId][downloadId] = CoreSites.instance.getSite(siteId).then(async (site) => {
|
||||
if (!site.canDownloadFiles()) {
|
||||
return Promise.reject(null);
|
||||
throw new CoreError('Site doesn\'t allow downloading files.');
|
||||
}
|
||||
|
||||
const entry = await CoreWS.instance.downloadFile(fileUrl, filePath, addExtension, onProgress);
|
||||
|
@ -952,7 +955,7 @@ export class CoreFilepoolProvider {
|
|||
try {
|
||||
await Promise.all(promises);
|
||||
// Success prefetching, store package as downloaded.
|
||||
this.storePackageStatus(siteId, CoreConstants.DOWNLOADED, component, componentId, extra);
|
||||
await this.storePackageStatus(siteId, CoreConstants.DOWNLOADED, component, componentId, extra);
|
||||
} catch (error) {
|
||||
// Error downloading, go back to previous status and reject the promise.
|
||||
await this.setPackagePreviousStatus(siteId, component, componentId);
|
||||
|
@ -1014,7 +1017,7 @@ export class CoreFilepoolProvider {
|
|||
let alreadyDownloaded = true;
|
||||
|
||||
if (!CoreFile.instance.isAvailable()) {
|
||||
throw null;
|
||||
throw new CoreError('File system cannot be used.');
|
||||
}
|
||||
|
||||
const file = await this.fixPluginfileURL(siteId, fileUrl);
|
||||
|
@ -1093,14 +1096,12 @@ export class CoreFilepoolProvider {
|
|||
let urls = [];
|
||||
|
||||
const element = CoreDomUtils.instance.convertToElement(html);
|
||||
const elements = element.querySelectorAll('a, img, audio, video, source, track');
|
||||
const elements: (HTMLAnchorElement | HTMLImageElement | HTMLAudioElement | HTMLVideoElement | HTMLSourceElement |
|
||||
HTMLTrackElement)[] = Array.from(element.querySelectorAll('a, img, audio, video, source, track'));
|
||||
|
||||
for (let i = 0; i < elements.length; i++) {
|
||||
const element = elements[i];
|
||||
let url = element.tagName === 'A'
|
||||
? (element as HTMLAnchorElement).href
|
||||
: (element as HTMLImageElement | HTMLVideoElement | HTMLAudioElement |
|
||||
HTMLAudioElement | HTMLTrackElement | HTMLSourceElement).src;
|
||||
let url = 'href' in element ? element.href : element.src;
|
||||
|
||||
if (url && CoreUrlUtils.instance.isDownloadableUrl(url) && urls.indexOf(url) == -1) {
|
||||
urls.push(url);
|
||||
|
@ -1236,7 +1237,7 @@ export class CoreFilepoolProvider {
|
|||
componentId: this.fixComponentId(componentId),
|
||||
};
|
||||
|
||||
const items = await db.getRecords(CoreFilepoolProvider.LINKS_TABLE, conditions);
|
||||
const items = await db.getRecords<CoreFilepoolLinksRecord>(CoreFilepoolProvider.LINKS_TABLE, conditions);
|
||||
items.forEach((item) => {
|
||||
item.componentId = this.fixComponentId(item.componentId);
|
||||
});
|
||||
|
@ -1252,16 +1253,16 @@ export class CoreFilepoolProvider {
|
|||
* @return Resolved with the URL. Rejected otherwise.
|
||||
*/
|
||||
async getDirectoryUrlByUrl(siteId: string, fileUrl: string): Promise<string> {
|
||||
if (CoreFile.instance.isAvailable()) {
|
||||
const file = await this.fixPluginfileURL(siteId, fileUrl);
|
||||
const fileId = this.getFileIdByUrl(file.fileurl);
|
||||
const filePath = await this.getFilePath(siteId, fileId, '');
|
||||
const dirEntry = await CoreFile.instance.getDir(filePath);
|
||||
|
||||
return dirEntry.toURL();
|
||||
if (!CoreFile.instance.isAvailable()) {
|
||||
throw new CoreError('File system cannot be used.');
|
||||
}
|
||||
|
||||
throw null;
|
||||
const file = await this.fixPluginfileURL(siteId, fileUrl);
|
||||
const fileId = this.getFileIdByUrl(file.fileurl);
|
||||
const filePath = await this.getFilePath(siteId, fileId, '');
|
||||
const dirEntry = await CoreFile.instance.getDir(filePath);
|
||||
|
||||
return dirEntry.toURL();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1346,7 +1347,8 @@ export class CoreFilepoolProvider {
|
|||
*/
|
||||
protected async getFileLinks(siteId: string, fileId: string): Promise<CoreFilepoolLinksRecord[]> {
|
||||
const db = await CoreSites.instance.getSiteDb(siteId);
|
||||
const items = await db.getRecords(CoreFilepoolProvider.LINKS_TABLE, { fileId });
|
||||
const items = await db.getRecords<CoreFilepoolLinksRecord>(CoreFilepoolProvider.LINKS_TABLE, { fileId });
|
||||
|
||||
items.forEach((item) => {
|
||||
item.componentId = this.fixComponentId(item.componentId);
|
||||
});
|
||||
|
@ -1421,7 +1423,7 @@ export class CoreFilepoolProvider {
|
|||
const files = [];
|
||||
|
||||
const promises = items.map((item) =>
|
||||
db.getRecord(CoreFilepoolProvider.FILES_TABLE, { fileId: item.fileId }).then((fileEntry) => {
|
||||
db.getRecord<CoreFilepoolFileEntry>(CoreFilepoolProvider.FILES_TABLE, { fileId: item.fileId }).then((fileEntry) => {
|
||||
if (!fileEntry) {
|
||||
return;
|
||||
}
|
||||
|
@ -1532,7 +1534,7 @@ export class CoreFilepoolProvider {
|
|||
* @param componentId An ID to use in conjunction with the component.
|
||||
* @param timemodified The time this file was modified.
|
||||
* @param checkSize True if we shouldn't download files if their size is big, false otherwise.
|
||||
* @param downloadAny True to download file in WiFi if their size is any, false otherwise.
|
||||
* @param downloadUnknown True to download file in WiFi if their size is unknown, false otherwise.
|
||||
* Ignored if checkSize=false.
|
||||
* @param options Extra options (isexternalfile, repositorytype).
|
||||
* @param revision File revision. If not defined, it will be calculated using the URL.
|
||||
|
@ -1544,12 +1546,12 @@ export class CoreFilepoolProvider {
|
|||
* If the file isn't downloaded or it's outdated, return the online URL and add it to the queue to be downloaded later.
|
||||
*/
|
||||
protected async getFileUrlByUrl(siteId: string, fileUrl: string, component: string, componentId?: string | number,
|
||||
mode: string = 'url', timemodified: number = 0, checkSize: boolean = true, downloadAny?: boolean,
|
||||
mode: string = 'url', timemodified: number = 0, checkSize: boolean = true, downloadUnknown?: boolean,
|
||||
options: CoreFilepoolFileOptions = {}, revision?: number): Promise<string> {
|
||||
const addToQueue = (fileUrl: string): void => {
|
||||
// Add the file to queue if needed and ignore errors.
|
||||
this.addToQueueIfNeeded(siteId, fileUrl, component, componentId, timemodified, checkSize,
|
||||
downloadAny, options, revision).catch(() => {
|
||||
downloadUnknown, options, revision).catch(() => {
|
||||
// Ignore errors.
|
||||
});
|
||||
};
|
||||
|
@ -1594,7 +1596,7 @@ export class CoreFilepoolProvider {
|
|||
return fileUrl;
|
||||
}
|
||||
|
||||
throw null;
|
||||
throw new CoreError('File not found.');
|
||||
}
|
||||
}, () => {
|
||||
// We do not have the file in store yet. Add to queue and return the fixed URL.
|
||||
|
@ -1614,14 +1616,14 @@ export class CoreFilepoolProvider {
|
|||
* @return Resolved with the internal URL. Rejected otherwise.
|
||||
*/
|
||||
protected async getInternalSrcById(siteId: string, fileId: string): Promise<string> {
|
||||
if (CoreFile.instance.isAvailable()) {
|
||||
const path = await this.getFilePath(siteId, fileId);
|
||||
const fileEntry = await CoreFile.instance.getFile(path);
|
||||
|
||||
return CoreFile.instance.convertFileSrc(fileEntry.toURL());
|
||||
if (!CoreFile.instance.isAvailable()) {
|
||||
throw new CoreError('File system cannot be used.');
|
||||
}
|
||||
|
||||
throw null;
|
||||
const path = await this.getFilePath(siteId, fileId);
|
||||
const fileEntry = await CoreFile.instance.getFile(path);
|
||||
|
||||
return CoreFile.instance.convertFileSrc(fileEntry.toURL());
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1632,19 +1634,19 @@ export class CoreFilepoolProvider {
|
|||
* @return Resolved with the URL. Rejected otherwise.
|
||||
*/
|
||||
protected async getInternalUrlById(siteId: string, fileId: string): Promise<string> {
|
||||
if (CoreFile.instance.isAvailable()) {
|
||||
const path = await this.getFilePath(siteId, fileId);
|
||||
const fileEntry = await CoreFile.instance.getFile(path);
|
||||
|
||||
// This URL is usually used to launch files or put them in HTML. In desktop we need the internal URL.
|
||||
if (CoreApp.instance.isDesktop()) {
|
||||
return fileEntry.toInternalURL();
|
||||
} else {
|
||||
return fileEntry.toURL();
|
||||
}
|
||||
if (!CoreFile.instance.isAvailable()) {
|
||||
throw new CoreError('File system cannot be used.');
|
||||
}
|
||||
|
||||
throw null;
|
||||
const path = await this.getFilePath(siteId, fileId);
|
||||
const fileEntry = await CoreFile.instance.getFile(path);
|
||||
|
||||
// This URL is usually used to launch files or put them in HTML. In desktop we need the internal URL.
|
||||
if (CoreApp.instance.isDesktop()) {
|
||||
return fileEntry.toInternalURL();
|
||||
} else {
|
||||
return fileEntry.toURL();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1654,13 +1656,13 @@ export class CoreFilepoolProvider {
|
|||
* @return Resolved with the URL.
|
||||
*/
|
||||
protected async getInternalUrlByPath(filePath: string): Promise<string> {
|
||||
if (CoreFile.instance.isAvailable()) {
|
||||
const fileEntry = await CoreFile.instance.getFile(filePath);
|
||||
|
||||
return fileEntry.toURL();
|
||||
if (!CoreFile.instance.isAvailable()) {
|
||||
throw new CoreError('File system cannot be used.');
|
||||
}
|
||||
|
||||
throw null;
|
||||
const fileEntry = await CoreFile.instance.getFile(filePath);
|
||||
|
||||
return fileEntry.toURL();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1671,14 +1673,14 @@ export class CoreFilepoolProvider {
|
|||
* @return Resolved with the URL. Rejected otherwise.
|
||||
*/
|
||||
async getInternalUrlByUrl(siteId: string, fileUrl: string): Promise<string> {
|
||||
if (CoreFile.instance.isAvailable()) {
|
||||
const file = await this.fixPluginfileURL(siteId, fileUrl);
|
||||
const fileId = this.getFileIdByUrl(file.fileurl);
|
||||
|
||||
return this.getInternalUrlById(siteId, fileId);
|
||||
if (!CoreFile.instance.isAvailable()) {
|
||||
throw new CoreError('File system cannot be used.');
|
||||
}
|
||||
|
||||
throw null;
|
||||
const file = await this.fixPluginfileURL(siteId, fileUrl);
|
||||
const fileId = this.getFileIdByUrl(file.fileurl);
|
||||
|
||||
return this.getInternalUrlById(siteId, fileId);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1748,16 +1750,16 @@ export class CoreFilepoolProvider {
|
|||
* @return Resolved with the URL.
|
||||
*/
|
||||
async getPackageDirUrlByUrl(siteId: string, url: string): Promise<string> {
|
||||
if (CoreFile.instance.isAvailable()) {
|
||||
const file = await this.fixPluginfileURL(siteId, url);
|
||||
const dirName = this.getPackageDirNameByUrl(file.fileurl);
|
||||
const dirPath = await this.getFilePath(siteId, dirName, '');
|
||||
const dirEntry = await CoreFile.instance.getDir(dirPath);
|
||||
|
||||
return dirEntry.toURL();
|
||||
if (!CoreFile.instance.isAvailable()) {
|
||||
throw new CoreError('File system cannot be used.');
|
||||
}
|
||||
|
||||
throw null;
|
||||
const file = await this.fixPluginfileURL(siteId, url);
|
||||
const dirName = this.getPackageDirNameByUrl(file.fileurl);
|
||||
const dirPath = await this.getFilePath(siteId, dirName, '');
|
||||
const dirEntry = await CoreFile.instance.getDir(dirPath);
|
||||
|
||||
return dirEntry.toURL();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1973,7 +1975,7 @@ export class CoreFilepoolProvider {
|
|||
* @param componentId An ID to use in conjunction with the component.
|
||||
* @param timemodified The time this file was modified.
|
||||
* @param checkSize True if we shouldn't download files if their size is big, false otherwise.
|
||||
* @param downloadAny True to download file in WiFi if their size is any, false otherwise.
|
||||
* @param downloadUnknown True to download file in WiFi if their size is unknown, false otherwise.
|
||||
* Ignored if checkSize=false.
|
||||
* @param options Extra options (isexternalfile, repositorytype).
|
||||
* @param revision File revision. If not defined, it will be calculated using the URL.
|
||||
|
@ -1983,10 +1985,10 @@ export class CoreFilepoolProvider {
|
|||
* The URL returned is compatible to use with IMG tags.
|
||||
*/
|
||||
getSrcByUrl(siteId: string, fileUrl: string, component: string, componentId?: string | number, timemodified: number = 0,
|
||||
checkSize: boolean = true, downloadAny?: boolean, options: CoreFilepoolFileOptions = {}, revision?: number):
|
||||
checkSize: boolean = true, downloadUnknown?: boolean, options: CoreFilepoolFileOptions = {}, revision?: number):
|
||||
Promise<string> {
|
||||
return this.getFileUrlByUrl(siteId, fileUrl, component, componentId, 'src',
|
||||
timemodified, checkSize, downloadAny, options, revision);
|
||||
timemodified, checkSize, downloadUnknown, options, revision);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -2017,7 +2019,7 @@ export class CoreFilepoolProvider {
|
|||
* @param componentId An ID to use in conjunction with the component.
|
||||
* @param timemodified The time this file was modified.
|
||||
* @param checkSize True if we shouldn't download files if their size is big, false otherwise.
|
||||
* @param downloadAny True to download file in WiFi if their size is any, false otherwise.
|
||||
* @param downloadUnknown True to download file in WiFi if their size is unknown, false otherwise.
|
||||
* Ignored if checkSize=false.
|
||||
* @param options Extra options (isexternalfile, repositorytype).
|
||||
* @param revision File revision. If not defined, it will be calculated using the URL.
|
||||
|
@ -2027,10 +2029,10 @@ export class CoreFilepoolProvider {
|
|||
* The URL returned is compatible to use with a local browser.
|
||||
*/
|
||||
getUrlByUrl(siteId: string, fileUrl: string, component: string, componentId?: string | number, timemodified: number = 0,
|
||||
checkSize: boolean = true, downloadAny?: boolean, options: CoreFilepoolFileOptions = {}, revision?: number):
|
||||
checkSize: boolean = true, downloadUnknown?: boolean, options: CoreFilepoolFileOptions = {}, revision?: number):
|
||||
Promise<string> {
|
||||
return this.getFileUrlByUrl(siteId, fileUrl, component, componentId, 'url',
|
||||
timemodified, checkSize, downloadAny, options, revision);
|
||||
timemodified, checkSize, downloadUnknown, options, revision);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -2100,9 +2102,10 @@ export class CoreFilepoolProvider {
|
|||
*/
|
||||
protected async hasFileInPool(siteId: string, fileId: string): Promise<CoreFilepoolFileEntry> {
|
||||
const db = await CoreSites.instance.getSiteDb(siteId);
|
||||
const entry = await db.getRecord(CoreFilepoolProvider.FILES_TABLE, { fileId });
|
||||
const entry = await db.getRecord<CoreFilepoolFileEntry>(CoreFilepoolProvider.FILES_TABLE, { fileId });
|
||||
|
||||
if (typeof entry === 'undefined') {
|
||||
throw null;
|
||||
throw new CoreError('File not found in filepool.');
|
||||
}
|
||||
|
||||
return entry;
|
||||
|
@ -2118,12 +2121,13 @@ export class CoreFilepoolProvider {
|
|||
protected async hasFileInQueue(siteId: string, fileId: string): Promise<CoreFilepoolQueueEntry> {
|
||||
await this.dbReady;
|
||||
|
||||
const entry = await this.appDB.getRecord(CoreFilepoolProvider.QUEUE_TABLE, { siteId, fileId });
|
||||
const entry = await this.appDB.getRecord<CoreFilepoolQueueEntry>(CoreFilepoolProvider.QUEUE_TABLE, { siteId, fileId });
|
||||
|
||||
if (typeof entry === 'undefined') {
|
||||
throw null;
|
||||
throw new CoreError('File not found in queue.');
|
||||
}
|
||||
// Convert the links to an object.
|
||||
entry.linksUnserialized = CoreTextUtils.instance.parseJSON(entry.links, []);
|
||||
entry.linksUnserialized = <CoreFilepoolComponentLink[]> CoreTextUtils.instance.parseJSON(entry.links, []);
|
||||
|
||||
return entry;
|
||||
}
|
||||
|
@ -2132,14 +2136,14 @@ export class CoreFilepoolProvider {
|
|||
* Invalidate all the files in a site.
|
||||
*
|
||||
* @param siteId The site ID.
|
||||
* @param onlyAny True to only invalidate files from external repos or without revision/timemodified.
|
||||
* @param onlyUnknown True to only invalidate files from external repos or without revision/timemodified.
|
||||
* It is advised to set it to true to reduce the performance and data usage of the app.
|
||||
* @return Resolved on success.
|
||||
*/
|
||||
async invalidateAllFiles(siteId: string, onlyAny: boolean = true): Promise<void> {
|
||||
async invalidateAllFiles(siteId: string, onlyUnknown: boolean = true): Promise<void> {
|
||||
const db = await CoreSites.instance.getSiteDb(siteId);
|
||||
|
||||
const where = onlyAny ? CoreFilepoolProvider.FILE_UPDATE_ANY_WHERE_CLAUSE : null;
|
||||
const where = onlyUnknown ? CoreFilepoolProvider.FILE_UPDATE_UNKNOWN_WHERE_CLAUSE : null;
|
||||
|
||||
await db.updateRecordsWhere(CoreFilepoolProvider.FILES_TABLE, { stale: 1 }, where);
|
||||
}
|
||||
|
@ -2171,11 +2175,11 @@ export class CoreFilepoolProvider {
|
|||
* @param siteId The site ID.
|
||||
* @param component The component to invalidate.
|
||||
* @param componentId An ID to use in conjunction with the component.
|
||||
* @param onlyAny True to only invalidate files from external repos or without revision/timemodified.
|
||||
* It is advised to set it to true to reduce the performance and data usage of the app.
|
||||
* @param onlyUnknown True to only invalidate files from external repos or without revision/timemodified.
|
||||
* It is advised to set it to true to reduce the performance and data usage of the app.
|
||||
* @return Resolved when done.
|
||||
*/
|
||||
async invalidateFilesByComponent(siteId: string, component: string, componentId?: string | number, onlyAny: boolean = true):
|
||||
async invalidateFilesByComponent(siteId: string, component: string, componentId?: string | number, onlyUnknown: boolean = true):
|
||||
Promise<void> {
|
||||
const db = await CoreSites.instance.getSiteDb(siteId);
|
||||
|
||||
|
@ -2191,8 +2195,8 @@ export class CoreFilepoolProvider {
|
|||
|
||||
whereAndParams[0] = 'fileId ' + whereAndParams[0];
|
||||
|
||||
if (onlyAny) {
|
||||
whereAndParams[0] += ' AND (' + CoreFilepoolProvider.FILE_UPDATE_ANY_WHERE_CLAUSE + ')';
|
||||
if (onlyUnknown) {
|
||||
whereAndParams[0] += ' AND (' + CoreFilepoolProvider.FILE_UPDATE_UNKNOWN_WHERE_CLAUSE + ')';
|
||||
}
|
||||
|
||||
await db.updateRecordsWhere(CoreFilepoolProvider.FILES_TABLE, { stale: 1 }, whereAndParams[0], whereAndParams[1]);
|
||||
|
@ -2258,7 +2262,7 @@ export class CoreFilepoolProvider {
|
|||
* @param entry Filepool entry.
|
||||
* @return Whether it cannot determine updates.
|
||||
*/
|
||||
protected isFileUpdateAny(entry: CoreFilepoolFileEntry): boolean {
|
||||
protected isFileUpdateUnknown(entry: CoreFilepoolFileEntry): boolean {
|
||||
return !!entry.isexternalfile || (!entry.revision && !entry.timemodified);
|
||||
}
|
||||
|
||||
|
@ -2433,7 +2437,7 @@ export class CoreFilepoolProvider {
|
|||
let items: CoreFilepoolQueueEntry[];
|
||||
|
||||
try {
|
||||
items = await this.appDB.getRecords(CoreFilepoolProvider.QUEUE_TABLE, undefined,
|
||||
items = await this.appDB.getRecords<CoreFilepoolQueueEntry>(CoreFilepoolProvider.QUEUE_TABLE, undefined,
|
||||
'priority DESC, added ASC', undefined, 0, 1);
|
||||
} catch (err) {
|
||||
throw CoreFilepoolProvider.ERR_QUEUE_IS_EMPTY;
|
||||
|
@ -2444,7 +2448,7 @@ export class CoreFilepoolProvider {
|
|||
throw CoreFilepoolProvider.ERR_QUEUE_IS_EMPTY;
|
||||
}
|
||||
// Convert the links to an object.
|
||||
item.linksUnserialized = CoreTextUtils.instance.parseJSON(item.links, []);
|
||||
item.linksUnserialized = <CoreFilepoolComponentLink[]> CoreTextUtils.instance.parseJSON(item.links, []);
|
||||
|
||||
return this.processQueueItem(item);
|
||||
}
|
||||
|
@ -2760,7 +2764,7 @@ export class CoreFilepoolProvider {
|
|||
const mimetype = await CoreUtils.instance.getMimeTypeFromUrl(url);
|
||||
// If the file is streaming (audio or video) we reject.
|
||||
if (mimetype.indexOf('video') != -1 || mimetype.indexOf('audio') != -1) {
|
||||
throw null;
|
||||
throw new CoreError('File is audio or video.');
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2988,9 +2992,9 @@ export type CoreFilepoolFileEntry = CoreFilepoolFileOptions & {
|
|||
};
|
||||
|
||||
/**
|
||||
* Entry from the file's queue.
|
||||
* DB data for entry from file's queue.
|
||||
*/
|
||||
export type CoreFilepoolQueueEntry = CoreFilepoolFileOptions & {
|
||||
export type CoreFilepoolQueueDBEntry = CoreFilepoolFileOptions & {
|
||||
/**
|
||||
* The site the file belongs to.
|
||||
*/
|
||||
|
@ -3025,7 +3029,12 @@ export type CoreFilepoolQueueEntry = CoreFilepoolFileOptions & {
|
|||
* File links (to link the file to components and componentIds). Serialized to store on DB.
|
||||
*/
|
||||
links?: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* Entry from the file's queue.
|
||||
*/
|
||||
export type CoreFilepoolQueueEntry = CoreFilepoolQueueDBEntry & {
|
||||
/**
|
||||
* File links (to link the file to components and componentIds).
|
||||
*/
|
||||
|
|
|
@ -16,7 +16,7 @@ import { Injectable } from '@angular/core';
|
|||
import { Coordinates } from '@ionic-native/geolocation';
|
||||
|
||||
import { CoreApp } from '@services/app';
|
||||
import { CoreError } from '@classes/error';
|
||||
import { CoreError } from '@classes/errors/error';
|
||||
import { Geolocation, Diagnostic, makeSingleton } from '@singletons/core.singletons';
|
||||
|
||||
@Injectable()
|
||||
|
@ -116,6 +116,7 @@ export class CoreGeolocationProvider {
|
|||
*
|
||||
* @param error Error.
|
||||
*/
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any
|
||||
protected isCordovaPermissionDeniedError(error?: any): boolean {
|
||||
return error && 'code' in error && 'PERMISSION_DENIED' in error && error.code === error.PERMISSION_DENIED;
|
||||
}
|
||||
|
|
|
@ -16,6 +16,7 @@ import { Injectable } from '@angular/core';
|
|||
|
||||
import { CoreSites } from '@services/sites';
|
||||
import { CoreSite, CoreSiteWSPreSets } from '@classes/site';
|
||||
import { CoreError } from '@classes/errors/error';
|
||||
import { makeSingleton, Translate } from '@singletons/core.singletons';
|
||||
import { CoreWSExternalWarning } from '@services/ws';
|
||||
import { CoreCourseBase } from '@/types/global';
|
||||
|
@ -79,9 +80,11 @@ export class CoreGroupsProvider {
|
|||
preSets.emergencyCache = false;
|
||||
}
|
||||
|
||||
const response = await site.read('core_group_get_activity_allowed_groups', params, preSets);
|
||||
const response: CoreGroupGetActivityAllowedGroupsResponse =
|
||||
await site.read('core_group_get_activity_allowed_groups', params, preSets);
|
||||
|
||||
if (!response || !response.groups) {
|
||||
throw null;
|
||||
throw new CoreError('Activity allowed groups not found.');
|
||||
}
|
||||
|
||||
return response;
|
||||
|
@ -195,9 +198,11 @@ export class CoreGroupsProvider {
|
|||
preSets.emergencyCache = false;
|
||||
}
|
||||
|
||||
const response = await site.read('core_group_get_activity_groupmode', params, preSets);
|
||||
const response: CoreGroupGetActivityGroupModeResponse =
|
||||
await site.read('core_group_get_activity_groupmode', params, preSets);
|
||||
|
||||
if (!response || typeof response.groupmode == 'undefined') {
|
||||
throw null;
|
||||
throw new CoreError('Activity group mode not found.');
|
||||
}
|
||||
|
||||
return response.groupmode;
|
||||
|
@ -267,9 +272,11 @@ export class CoreGroupsProvider {
|
|||
updateFrequency: CoreSite.FREQUENCY_RARELY,
|
||||
};
|
||||
|
||||
const response = await site.read('core_group_get_course_user_groups', data, preSets);
|
||||
const response: CoreGroupGetCourseUserGroupsResponse =
|
||||
await site.read('core_group_get_course_user_groups', data, preSets);
|
||||
|
||||
if (!response || !response.groups) {
|
||||
throw null;
|
||||
throw new CoreError('User groups in course not found.');
|
||||
}
|
||||
|
||||
return response.groups;
|
||||
|
@ -461,3 +468,26 @@ export type CoreGroupGetActivityAllowedGroupsResponse = {
|
|||
canaccessallgroups?: boolean; // Whether the user will be able to access all the activity groups.
|
||||
warnings?: CoreWSExternalWarning[];
|
||||
};
|
||||
|
||||
/**
|
||||
* Result of WS core_group_get_activity_groupmode.
|
||||
*/
|
||||
export type CoreGroupGetActivityGroupModeResponse = {
|
||||
groupmode: number; // Group mode: 0 for no groups, 1 for separate groups, 2 for visible groups.
|
||||
warnings?: CoreWSExternalWarning[];
|
||||
};
|
||||
|
||||
/**
|
||||
* Result of WS core_group_get_course_user_groups.
|
||||
*/
|
||||
export type CoreGroupGetCourseUserGroupsResponse = {
|
||||
groups: {
|
||||
id: number; // Group record id.
|
||||
name: string; // Multilang compatible name, course unique.
|
||||
description: string; // Group description text.
|
||||
descriptionformat: number; // Description format (1 = HTML, 0 = MOODLE, 2 = PLAIN or 4 = MARKDOWN).
|
||||
idnumber: string; // Id number.
|
||||
courseid?: number; // Course id.
|
||||
}[];
|
||||
warnings?: CoreWSExternalWarning[];
|
||||
};
|
||||
|
|
|
@ -30,9 +30,9 @@ export class CoreLangProvider {
|
|||
protected fallbackLanguage = 'en'; // Always use English as fallback language since it contains all strings.
|
||||
protected defaultLanguage = CoreConfigConstants.default_lang || 'en'; // Lang to use if device lang not valid or is forced.
|
||||
protected currentLanguage: string; // Save current language in a variable to speed up the get function.
|
||||
protected customStrings = {}; // Strings defined using the admin tool.
|
||||
protected customStrings: CoreLanguageObject = {}; // Strings defined using the admin tool.
|
||||
protected customStringsRaw: string;
|
||||
protected sitePluginsStrings = {}; // Strings defined by site plugins.
|
||||
protected sitePluginsStrings: CoreLanguageObject = {}; // Strings defined by site plugins.
|
||||
|
||||
constructor() {
|
||||
// Set fallback language and language to use until the app determines the right language to use.
|
||||
|
@ -110,11 +110,11 @@ export class CoreLangProvider {
|
|||
* @param language New language to use.
|
||||
* @return Promise resolved when the change is finished.
|
||||
*/
|
||||
changeCurrentLanguage(language: string): Promise<unknown> {
|
||||
async changeCurrentLanguage(language: string): Promise<void> {
|
||||
const promises = [];
|
||||
|
||||
// Change the language, resolving the promise when we receive the first value.
|
||||
promises.push(new Promise((resolve, reject): void => {
|
||||
promises.push(new Promise((resolve, reject) => {
|
||||
const subscription = Translate.instance.use(language).subscribe((data) => {
|
||||
// It's a language override, load the original one first.
|
||||
const fallbackLang = Translate.instance.instant('core.parentlanguage');
|
||||
|
@ -165,13 +165,15 @@ export class CoreLangProvider {
|
|||
|
||||
this.currentLanguage = language;
|
||||
|
||||
return Promise.all(promises).finally(() => {
|
||||
try {
|
||||
await Promise.all(promises);
|
||||
} finally {
|
||||
// Load the custom and site plugins strings for the language.
|
||||
if (this.loadLangStrings(this.customStrings, language) || this.loadLangStrings(this.sitePluginsStrings, language)) {
|
||||
// Some lang strings have changed, emit an event to update the pipes.
|
||||
Translate.instance.onLangChange.emit({ lang: language, translations: Translate.instance.translations[language] });
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -196,7 +198,7 @@ export class CoreLangProvider {
|
|||
*
|
||||
* @return Custom strings.
|
||||
*/
|
||||
getAllCustomStrings(): unknown {
|
||||
getAllCustomStrings(): CoreLanguageObject {
|
||||
return this.customStrings;
|
||||
}
|
||||
|
||||
|
@ -205,7 +207,7 @@ export class CoreLangProvider {
|
|||
*
|
||||
* @return Site plugins strings.
|
||||
*/
|
||||
getAllSitePluginsStrings(): unknown {
|
||||
getAllSitePluginsStrings(): CoreLanguageObject {
|
||||
return this.sitePluginsStrings;
|
||||
}
|
||||
|
||||
|
@ -220,7 +222,7 @@ export class CoreLangProvider {
|
|||
}
|
||||
|
||||
// Get current language from config (user might have changed it).
|
||||
return CoreConfig.instance.get('current_language').then((language) => language).catch(() => {
|
||||
return CoreConfig.instance.get<string>('current_language').then((language) => language).catch(() => {
|
||||
// User hasn't defined a language. If default language is forced, use it.
|
||||
if (CoreConfigConstants.default_lang && CoreConfigConstants.forcedefaultlanguage) {
|
||||
return CoreConfigConstants.default_lang;
|
||||
|
@ -283,7 +285,7 @@ export class CoreLangProvider {
|
|||
* @param lang The language to check.
|
||||
* @return Promise resolved when done.
|
||||
*/
|
||||
getTranslationTable(lang: string): Promise<unknown> {
|
||||
getTranslationTable(lang: string): Promise<Record<string, unknown>> {
|
||||
// Create a promise to convert the observable into a promise.
|
||||
return new Promise((resolve, reject): void => {
|
||||
const observer = Translate.instance.getTranslation(lang).subscribe((table) => {
|
||||
|
|
|
@ -20,9 +20,11 @@ import { CoreApp, CoreAppSchema } from '@services/app';
|
|||
import { CoreConfig } from '@services/config';
|
||||
import { CoreEventObserver, CoreEvents, CoreEventsProvider } from '@services/events';
|
||||
import { CoreTextUtils } from '@services/utils/text';
|
||||
import { CoreUtils } from '@services/utils/utils';
|
||||
import { CoreUtils, PromiseDefer } from '@services/utils/utils';
|
||||
import { SQLiteDB } from '@classes/sqlitedb';
|
||||
import { CoreSite } from '@classes/site';
|
||||
import { CoreQueueRunner } from '@classes/queue-runner';
|
||||
import { CoreError } from '@classes/errors/error';
|
||||
import { CoreConstants } from '@core/constants';
|
||||
import CoreConfigConstants from '@app/config.json';
|
||||
import { makeSingleton, NgZone, Platform, Translate, LocalNotifications, Push, Device } from '@singletons/core.singletons';
|
||||
|
@ -94,14 +96,9 @@ export class CoreLocalNotificationsProvider {
|
|||
protected appDB: SQLiteDB;
|
||||
protected dbReady: Promise<void>; // Promise resolved when the app DB is initialized.
|
||||
protected codes: { [s: string]: number } = {};
|
||||
protected codeRequestsQueue = {};
|
||||
protected observables = {};
|
||||
protected currentNotification = {
|
||||
title: '',
|
||||
texts: [],
|
||||
ids: [],
|
||||
timeouts: [],
|
||||
};
|
||||
protected codeRequestsQueue: {[key: string]: CodeRequestsQueueItem} = {};
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
protected observables: {[eventName: string]: {[component: string]: Subject<any>}} = {};
|
||||
|
||||
protected triggerSubscription: Subscription;
|
||||
protected clickSubscription: Subscription;
|
||||
|
@ -156,7 +153,7 @@ export class CoreLocalNotificationsProvider {
|
|||
});
|
||||
});
|
||||
|
||||
CoreEvents.instance.on(CoreEventsProvider.SITE_DELETED, (site) => {
|
||||
CoreEvents.instance.on(CoreEventsProvider.SITE_DELETED, (site: CoreSite) => {
|
||||
if (site) {
|
||||
this.cancelSiteNotifications(site.id);
|
||||
}
|
||||
|
@ -270,13 +267,15 @@ export class CoreLocalNotificationsProvider {
|
|||
|
||||
try {
|
||||
// Check if we already have a code stored for that ID.
|
||||
const entry = await this.appDB.getRecord(table, { id: id });
|
||||
const entry = await this.appDB.getRecord<{id: string; code: number}>(table, { id: id });
|
||||
|
||||
this.codes[key] = entry.code;
|
||||
|
||||
return entry.code;
|
||||
} catch (err) {
|
||||
// No code stored for that ID. Create a new code for it.
|
||||
const entries = await this.appDB.getRecords(table, undefined, 'code DESC');
|
||||
const entries = await this.appDB.getRecords<{id: string; code: number}>(table, undefined, 'code DESC');
|
||||
|
||||
let newCode = 0;
|
||||
if (entries.length > 0) {
|
||||
newCode = entries[0].code + 1;
|
||||
|
@ -326,7 +325,7 @@ export class CoreLocalNotificationsProvider {
|
|||
*/
|
||||
protected getUniqueNotificationId(notificationId: number, component: string, siteId: string): Promise<number> {
|
||||
if (!siteId || !component) {
|
||||
return Promise.reject(null);
|
||||
return Promise.reject(new CoreError('Site ID or component not supplied.'));
|
||||
}
|
||||
|
||||
return this.getSiteCode(siteId).then((siteCode) => this.getComponentCode(component).then((componentCode) =>
|
||||
|
@ -372,7 +371,9 @@ export class CoreLocalNotificationsProvider {
|
|||
await this.dbReady;
|
||||
|
||||
try {
|
||||
const stored = await this.appDB.getRecord(CoreLocalNotificationsProvider.TRIGGERED_TABLE, { id: notification.id });
|
||||
const stored = await this.appDB.getRecord<{id: number; at: number}>(CoreLocalNotificationsProvider.TRIGGERED_TABLE,
|
||||
{ id: notification.id });
|
||||
|
||||
let triggered = (notification.trigger && notification.trigger.at) || 0;
|
||||
|
||||
if (typeof triggered != 'number') {
|
||||
|
@ -398,6 +399,7 @@ export class CoreLocalNotificationsProvider {
|
|||
*
|
||||
* @param data Data received by the notification.
|
||||
*/
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any
|
||||
notifyClick(data: any): void {
|
||||
this.notifyEvent('click', data);
|
||||
}
|
||||
|
@ -408,6 +410,7 @@ export class CoreLocalNotificationsProvider {
|
|||
* @param eventName Name of the event to notify.
|
||||
* @param data Data received by the notification.
|
||||
*/
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any
|
||||
notifyEvent(eventName: string, data: any): void {
|
||||
// Execute the code in the Angular zone, so change detection doesn't stop working.
|
||||
NgZone.instance.run(() => {
|
||||
|
@ -426,6 +429,7 @@ export class CoreLocalNotificationsProvider {
|
|||
* @param data Notification data.
|
||||
* @return Parsed data.
|
||||
*/
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any
|
||||
protected parseNotificationData(data: any): any {
|
||||
if (!data) {
|
||||
return {};
|
||||
|
@ -454,11 +458,11 @@ export class CoreLocalNotificationsProvider {
|
|||
if (typeof request == 'object' && typeof request.table != 'undefined' && typeof request.id != 'undefined') {
|
||||
// Get the code and resolve/reject all the promises of this request.
|
||||
promise = this.getCode(request.table, request.id).then((code) => {
|
||||
request.promises.forEach((p) => {
|
||||
request.deferreds.forEach((p) => {
|
||||
p.resolve(code);
|
||||
});
|
||||
}).catch((error) => {
|
||||
request.promises.forEach((p) => {
|
||||
request.deferreds.forEach((p) => {
|
||||
p.reject(error);
|
||||
});
|
||||
});
|
||||
|
@ -508,7 +512,7 @@ export class CoreLocalNotificationsProvider {
|
|||
|
||||
return {
|
||||
off: (): void => {
|
||||
this.observables[eventName][component].unsubscribe(callback);
|
||||
this.observables[eventName][component].unsubscribe();
|
||||
},
|
||||
};
|
||||
}
|
||||
|
@ -539,13 +543,13 @@ export class CoreLocalNotificationsProvider {
|
|||
|
||||
if (typeof this.codeRequestsQueue[key] != 'undefined') {
|
||||
// There's already a pending request for this store and ID, add the promise to it.
|
||||
this.codeRequestsQueue[key].promises.push(deferred);
|
||||
this.codeRequestsQueue[key].deferreds.push(deferred);
|
||||
} else {
|
||||
// Add a pending request to the queue.
|
||||
this.codeRequestsQueue[key] = {
|
||||
table: table,
|
||||
id: id,
|
||||
promises: [deferred],
|
||||
deferreds: [deferred],
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -682,7 +686,7 @@ export class CoreLocalNotificationsProvider {
|
|||
|
||||
const entry = {
|
||||
id: notification.id,
|
||||
at: notification.trigger && notification.trigger.at ? notification.trigger.at : Date.now(),
|
||||
at: notification.trigger && notification.trigger.at ? notification.trigger.at.getTime() : Date.now(),
|
||||
};
|
||||
|
||||
return this.appDB.insertRecord(CoreLocalNotificationsProvider.TRIGGERED_TABLE, entry);
|
||||
|
@ -709,3 +713,9 @@ export class CoreLocalNotificationsProvider {
|
|||
export class CoreLocalNotifications extends makeSingleton(CoreLocalNotificationsProvider) {}
|
||||
|
||||
export type CoreLocalNotificationsClickCallback<T = unknown> = (value: T) => void;
|
||||
|
||||
type CodeRequestsQueueItem = {
|
||||
table: string;
|
||||
id: string;
|
||||
deferreds: PromiseDefer<number>[];
|
||||
};
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -17,6 +17,8 @@ import { CoreEvents, CoreEventsProvider } from '@services/events';
|
|||
import { CoreSites, CoreSiteSchema } from '@services/sites';
|
||||
import { makeSingleton } from '@singletons/core.singletons';
|
||||
|
||||
const SYNC_TABLE = 'sync';
|
||||
|
||||
/*
|
||||
* Service that provides some features regarding synchronization.
|
||||
*/
|
||||
|
@ -24,36 +26,35 @@ import { makeSingleton } from '@singletons/core.singletons';
|
|||
export class CoreSyncProvider {
|
||||
|
||||
// Variables for the database.
|
||||
protected SYNC_TABLE = 'sync';
|
||||
protected siteSchema: CoreSiteSchema = {
|
||||
name: 'CoreSyncProvider',
|
||||
version: 1,
|
||||
tables: [
|
||||
{
|
||||
name: this.SYNC_TABLE,
|
||||
name: SYNC_TABLE,
|
||||
columns: [
|
||||
{
|
||||
name: 'component',
|
||||
type: 'TEXT',
|
||||
notNull: true
|
||||
notNull: true,
|
||||
},
|
||||
{
|
||||
name: 'id',
|
||||
type: 'TEXT',
|
||||
notNull: true
|
||||
notNull: true,
|
||||
},
|
||||
{
|
||||
name: 'time',
|
||||
type: 'INTEGER'
|
||||
type: 'INTEGER',
|
||||
},
|
||||
{
|
||||
name: 'warnings',
|
||||
type: 'TEXT'
|
||||
}
|
||||
type: 'TEXT',
|
||||
},
|
||||
],
|
||||
primaryKeys: ['component', 'id']
|
||||
}
|
||||
]
|
||||
primaryKeys: ['component', 'id'],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
// Store blocked sync objects.
|
||||
|
@ -63,7 +64,7 @@ export class CoreSyncProvider {
|
|||
CoreSites.instance.registerSiteSchema(this.siteSchema);
|
||||
|
||||
// Unblock all blocks on logout.
|
||||
CoreEvents.instance.on(CoreEventsProvider.LOGOUT, (data) => {
|
||||
CoreEvents.instance.on(CoreEventsProvider.LOGOUT, (data: {siteId: string}) => {
|
||||
this.clearAllBlocks(data.siteId);
|
||||
});
|
||||
}
|
||||
|
@ -125,32 +126,32 @@ export class CoreSyncProvider {
|
|||
|
||||
/**
|
||||
* Returns a sync record.
|
||||
*
|
||||
* @param component Component name.
|
||||
* @param id Unique ID per component.
|
||||
* @param siteId Site ID. If not defined, current site.
|
||||
* @return Record if found or reject.
|
||||
*/
|
||||
getSyncRecord(component: string, id: string | number, siteId?: string): Promise<any> {
|
||||
return CoreSites.instance.getSiteDb(siteId).then((db) => {
|
||||
return db.getRecord(this.SYNC_TABLE, { component: component, id: id });
|
||||
});
|
||||
getSyncRecord(component: string, id: string | number, siteId?: string): Promise<CoreSyncRecord> {
|
||||
return CoreSites.instance.getSiteDb(siteId).then((db) => db.getRecord(SYNC_TABLE, { component: component, id: id }));
|
||||
}
|
||||
|
||||
/**
|
||||
* Inserts or Updates info of a sync record.
|
||||
*
|
||||
* @param component Component name.
|
||||
* @param id Unique ID per component.
|
||||
* @param data Data that updates the record.
|
||||
* @param siteId Site ID. If not defined, current site.
|
||||
* @return Promise resolved with done.
|
||||
*/
|
||||
insertOrUpdateSyncRecord(component: string, id: string | number, data: any, siteId?: string): Promise<any> {
|
||||
return CoreSites.instance.getSiteDb(siteId).then((db) => {
|
||||
data.component = component;
|
||||
data.id = id;
|
||||
async insertOrUpdateSyncRecord(component: string, id: string, data: CoreSyncRecord, siteId?: string): Promise<void> {
|
||||
const db = await CoreSites.instance.getSiteDb(siteId);
|
||||
|
||||
return db.insertRecord(this.SYNC_TABLE, data);
|
||||
});
|
||||
data.component = component;
|
||||
data.id = id;
|
||||
|
||||
await db.insertRecord(SYNC_TABLE, data);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -206,6 +207,14 @@ export class CoreSyncProvider {
|
|||
delete this.blockedItems[siteId][uniqueId][operation];
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export class CoreSync extends makeSingleton(CoreSyncProvider) {}
|
||||
|
||||
export type CoreSyncRecord = {
|
||||
component: string;
|
||||
id: string;
|
||||
time: number;
|
||||
warnings: string;
|
||||
};
|
||||
|
|
|
@ -20,6 +20,8 @@ import CoreConfigConstants from '@app/config.json';
|
|||
import { makeSingleton } from '@singletons/core.singletons';
|
||||
import { CoreLogger } from '@singletons/logger';
|
||||
|
||||
const VERSION_APPLIED = 'version_applied';
|
||||
|
||||
/**
|
||||
* Factory to handle app updates. This factory shouldn't be used outside of core.
|
||||
*
|
||||
|
@ -27,12 +29,12 @@ import { CoreLogger } from '@singletons/logger';
|
|||
*/
|
||||
@Injectable()
|
||||
export class CoreUpdateManagerProvider implements CoreInitHandler {
|
||||
|
||||
// Data for init delegate.
|
||||
name = 'CoreUpdateManager';
|
||||
priority = CoreInitDelegate.MAX_RECOMMENDED_PRIORITY + 300;
|
||||
blocking = true;
|
||||
|
||||
protected VERSION_APPLIED = 'version_applied';
|
||||
protected logger: CoreLogger;
|
||||
|
||||
constructor() {
|
||||
|
@ -45,11 +47,11 @@ export class CoreUpdateManagerProvider implements CoreInitHandler {
|
|||
*
|
||||
* @return Promise resolved when the update process finishes.
|
||||
*/
|
||||
async load(): Promise<any> {
|
||||
async load(): Promise<void> {
|
||||
const promises = [];
|
||||
const versionCode = CoreConfigConstants.versioncode;
|
||||
|
||||
const versionApplied: number = await CoreConfig.instance.get(this.VERSION_APPLIED, 0);
|
||||
const versionApplied = await CoreConfig.instance.get<number>(VERSION_APPLIED, 0);
|
||||
|
||||
if (versionCode >= 3900 && versionApplied < 3900 && versionApplied > 0) {
|
||||
// @todo: H5P update.
|
||||
|
@ -58,11 +60,12 @@ export class CoreUpdateManagerProvider implements CoreInitHandler {
|
|||
try {
|
||||
await Promise.all(promises);
|
||||
|
||||
await CoreConfig.instance.set(this.VERSION_APPLIED, versionCode);
|
||||
await CoreConfig.instance.set(VERSION_APPLIED, versionCode);
|
||||
} catch (error) {
|
||||
this.logger.error(`Error applying update from ${versionApplied} to ${versionCode}`, error);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export class CoreUpdateManager extends makeSingleton(CoreUpdateManagerProvider) {}
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -391,7 +391,7 @@ export class CoreIframeUtilsProvider {
|
|||
return;
|
||||
}
|
||||
|
||||
if (!CoreUrlUtils.instance.isLocalFileUrlScheme(urlParts.protocol, urlParts.domain)) {
|
||||
if (!CoreUrlUtils.instance.isLocalFileUrlScheme(urlParts.protocol)) {
|
||||
// Scheme suggests it's an external resource.
|
||||
event && event.preventDefault();
|
||||
|
||||
|
|
|
@ -17,6 +17,7 @@ import { DomSanitizer, SafeUrl } from '@angular/platform-browser';
|
|||
|
||||
import { CoreApp } from '@services/app';
|
||||
import { CoreLang } from '@services/lang';
|
||||
import { CoreError } from '@classes/errors/error';
|
||||
import { makeSingleton, Translate } from '@singletons/core.singletons';
|
||||
import { CoreWSExternalFile } from '@services/ws';
|
||||
import { Locutus } from '@singletons/locutus';
|
||||
|
@ -29,6 +30,8 @@ export type CoreTextErrorObject = {
|
|||
error?: string;
|
||||
content?: string;
|
||||
body?: string;
|
||||
debuginfo?: string;
|
||||
backtrace?: string;
|
||||
};
|
||||
|
||||
/*
|
||||
|
@ -526,10 +529,13 @@ export class CoreTextUtilsProvider {
|
|||
* @param error Error object.
|
||||
* @return Error message, undefined if not found.
|
||||
*/
|
||||
getErrorMessageFromError(error: string | CoreTextErrorObject): string {
|
||||
getErrorMessageFromError(error: string | CoreError | CoreTextErrorObject): string {
|
||||
if (typeof error == 'string') {
|
||||
return error;
|
||||
}
|
||||
if (error instanceof CoreError) {
|
||||
return error.message;
|
||||
}
|
||||
|
||||
return error && (error.message || error.error || error.content || error.body);
|
||||
}
|
||||
|
|
|
@ -424,18 +424,16 @@ export class CoreUrlUtilsProvider {
|
|||
isLocalFileUrl(url: string): boolean {
|
||||
const urlParts = CoreUrl.parse(url);
|
||||
|
||||
return this.isLocalFileUrlScheme(urlParts.protocol, urlParts.domain);
|
||||
return this.isLocalFileUrlScheme(urlParts.protocol);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether a URL scheme belongs to a local file.
|
||||
*
|
||||
* @param scheme Scheme to check.
|
||||
* @param notUsed Unused parameter.
|
||||
* @return Whether the scheme belongs to a local file.
|
||||
*/
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
isLocalFileUrlScheme(scheme: string, notUsed?: string): boolean {
|
||||
isLocalFileUrlScheme(scheme: string): boolean {
|
||||
if (scheme) {
|
||||
scheme = scheme.toLowerCase();
|
||||
}
|
||||
|
|
|
@ -21,10 +21,11 @@ import { CoreApp } from '@services/app';
|
|||
import { CoreEvents, CoreEventsProvider } from '@services/events';
|
||||
import { CoreFile } from '@services/file';
|
||||
import { CoreLang } from '@services/lang';
|
||||
import { CoreWS, CoreWSError, CoreWSExternalFile } from '@services/ws';
|
||||
import { CoreWS, CoreWSExternalFile } from '@services/ws';
|
||||
import { CoreDomUtils } from '@services/utils/dom';
|
||||
import { CoreMimetypeUtils } from '@services/utils/mimetype';
|
||||
import { CoreTextUtils } from '@services/utils/text';
|
||||
import { CoreWSError } from '@classes/errors/wserror';
|
||||
import {
|
||||
makeSingleton, Clipboard, InAppBrowser, Platform, FileOpener, WebIntent, QRScanner, Translate,
|
||||
} from '@singletons/core.singletons';
|
||||
|
@ -325,6 +326,7 @@ export class CoreUtilsProvider {
|
|||
* @param message The message to include in the error.
|
||||
* @param needsTranslate If the message needs to be translated.
|
||||
* @return Fake WS error.
|
||||
* @deprecated since 3.9.5. Just create the error directly.
|
||||
*/
|
||||
createFakeWSError(message: string, needsTranslate?: boolean): CoreWSError {
|
||||
return CoreWS.instance.createFakeWSError(message, needsTranslate);
|
||||
|
|
|
@ -13,8 +13,9 @@
|
|||
// limitations under the License.
|
||||
|
||||
import { Injectable } from '@angular/core';
|
||||
import { HttpResponse } from '@angular/common/http';
|
||||
import { HttpResponse, HttpParams } from '@angular/common/http';
|
||||
|
||||
import { FileEntry } from '@ionic-native/file';
|
||||
import { FileUploadOptions } from '@ionic-native/file-transfer/ngx';
|
||||
import { Md5 } from 'ts-md5/dist/md5';
|
||||
import { Observable } from 'rxjs';
|
||||
|
@ -25,20 +26,28 @@ import { CoreApp } from '@services/app';
|
|||
import { CoreFile, CoreFileProvider } from '@services/file';
|
||||
import { CoreMimetypeUtils } from '@services/utils/mimetype';
|
||||
import { CoreTextUtils } from '@services/utils/text';
|
||||
import { CoreUtils, PromiseDefer } from '@services/utils/utils';
|
||||
import { CoreConstants } from '@core/constants';
|
||||
import { CoreError } from '@classes/errors/error';
|
||||
import { CoreInterceptor } from '@classes/interceptor';
|
||||
import { makeSingleton, Translate, FileTransfer, Http, Platform } from '@singletons/core.singletons';
|
||||
import { makeSingleton, Translate, FileTransfer, Http, Platform, NativeHttp } from '@singletons/core.singletons';
|
||||
import { CoreArray } from '@singletons/array';
|
||||
import { CoreLogger } from '@singletons/logger';
|
||||
import { CoreWSError } from '@classes/errors/wserror';
|
||||
import { CoreAjaxError } from '@classes/errors/ajaxerror';
|
||||
import { CoreAjaxWSError } from '@classes/errors/ajaxwserror';
|
||||
|
||||
/**
|
||||
* This service allows performing WS calls and download/upload files.
|
||||
*/
|
||||
@Injectable()
|
||||
export class CoreWSProvider {
|
||||
|
||||
protected logger: CoreLogger;
|
||||
protected mimeTypeCache = {}; // A "cache" to store file mimetypes to prevent performing too many HEAD requests.
|
||||
protected ongoingCalls = {};
|
||||
protected retryCalls = [];
|
||||
protected mimeTypeCache: {[url: string]: string} = {}; // A "cache" to store file mimetypes to decrease HEAD requests.
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
protected ongoingCalls: {[queueItemId: string]: Promise<any>} = {};
|
||||
protected retryCalls: RetryCall[] = [];
|
||||
protected retryTimeout = 0;
|
||||
|
||||
constructor() {
|
||||
|
@ -46,7 +55,7 @@ export class CoreWSProvider {
|
|||
|
||||
Platform.instance.ready().then(() => {
|
||||
if (CoreApp.instance.isIOS()) {
|
||||
(<any> cordova).plugin.http.setHeader('User-Agent', navigator.userAgent);
|
||||
NativeHttp.instance.setHeader('*', 'User-Agent', navigator.userAgent);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -61,20 +70,15 @@ export class CoreWSProvider {
|
|||
* @return Deferred promise resolved with the response data in success and rejected with the error message
|
||||
* if it fails.
|
||||
*/
|
||||
protected addToRetryQueue(method: string, siteUrl: string, ajaxData: any, preSets: CoreWSPreSets): Promise<any> {
|
||||
const call: any = {
|
||||
protected addToRetryQueue<T = unknown>(method: string, siteUrl: string, data: unknown, preSets: CoreWSPreSets): Promise<T> {
|
||||
const call = {
|
||||
method,
|
||||
siteUrl,
|
||||
ajaxData,
|
||||
data,
|
||||
preSets,
|
||||
deferred: {}
|
||||
deferred: CoreUtils.instance.promiseDefer<T>(),
|
||||
};
|
||||
|
||||
call.deferred.promise = new Promise((resolve, reject): void => {
|
||||
call.deferred.resolve = resolve;
|
||||
call.deferred.reject = reject;
|
||||
});
|
||||
|
||||
this.retryCalls.push(call);
|
||||
|
||||
return call.deferred.promise;
|
||||
|
@ -88,14 +92,11 @@ export class CoreWSProvider {
|
|||
* @param preSets Extra settings and information.
|
||||
* @return Promise resolved with the response data in success and rejected if it fails.
|
||||
*/
|
||||
call(method: string, data: any, preSets: CoreWSPreSets): Promise<any> {
|
||||
|
||||
let siteUrl;
|
||||
|
||||
call<T = unknown>(method: string, data: unknown, preSets: CoreWSPreSets): Promise<T> {
|
||||
if (!preSets) {
|
||||
return Promise.reject(this.createFakeWSError('core.unexpectederror', true));
|
||||
return Promise.reject(new CoreError(Translate.instance.instant('core.unexpectederror')));
|
||||
} else if (!CoreApp.instance.isOnline()) {
|
||||
return Promise.reject(this.createFakeWSError('core.networkerrormsg', true));
|
||||
return Promise.reject(new CoreError(Translate.instance.instant('core.networkerrormsg')));
|
||||
}
|
||||
|
||||
preSets.typeExpected = preSets.typeExpected || 'object';
|
||||
|
@ -103,18 +104,18 @@ export class CoreWSProvider {
|
|||
preSets.responseExpected = true;
|
||||
}
|
||||
|
||||
data = Object.assign({}, data); // Create a new object so the changes don't affect the original data.
|
||||
data.wsfunction = method;
|
||||
data.wstoken = preSets.wsToken;
|
||||
siteUrl = preSets.siteUrl + '/webservice/rest/server.php?moodlewsrestformat=json';
|
||||
const dataToSend = Object.assign({}, data); // Create a new object so the changes don't affect the original data.
|
||||
dataToSend['wsfunction'] = method;
|
||||
dataToSend['wstoken'] = preSets.wsToken;
|
||||
const siteUrl = preSets.siteUrl + '/webservice/rest/server.php?moodlewsrestformat=json';
|
||||
|
||||
// There are some ongoing retry calls, wait for timeout.
|
||||
if (this.retryCalls.length > 0) {
|
||||
this.logger.warn('Calls locked, trying later...');
|
||||
|
||||
return this.addToRetryQueue(method, siteUrl, data, preSets);
|
||||
return this.addToRetryQueue<T>(method, siteUrl, data, preSets);
|
||||
} else {
|
||||
return this.performPost(method, siteUrl, data, preSets);
|
||||
return this.performPost<T>(method, siteUrl, data, preSets);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -130,17 +131,17 @@ export class CoreWSProvider {
|
|||
* - errorcode: Error code returned by the site (if any).
|
||||
* - available: 0 if unknown, 1 if available, -1 if not available.
|
||||
*/
|
||||
callAjax(method: string, data: any, preSets: CoreWSAjaxPreSets): Promise<any> {
|
||||
callAjax<T = unknown>(method: string, data: Record<string, unknown>, preSets: CoreWSAjaxPreSets): Promise<T> {
|
||||
const cacheParams = {
|
||||
methodname: method,
|
||||
args: data,
|
||||
};
|
||||
|
||||
let promise = this.getPromiseHttp('ajax', preSets.siteUrl, cacheParams);
|
||||
let promise = this.getPromiseHttp<T>('ajax', preSets.siteUrl, cacheParams);
|
||||
|
||||
if (!promise) {
|
||||
promise = this.performAjax(method, data, preSets);
|
||||
promise = this.setPromiseHttp(promise, 'ajax', preSets.siteUrl, cacheParams);
|
||||
promise = this.performAjax<T>(method, data, preSets);
|
||||
promise = this.setPromiseHttp<T>(promise, 'ajax', preSets.siteUrl, cacheParams);
|
||||
}
|
||||
|
||||
return promise;
|
||||
|
@ -154,7 +155,9 @@ export class CoreWSProvider {
|
|||
* @param stripUnicode If Unicode long chars need to be stripped.
|
||||
* @return The cleaned object or null if some strings becomes empty after stripping Unicode.
|
||||
*/
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any
|
||||
convertValuesToString(data: any, stripUnicode?: boolean): any {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const result: any = Array.isArray(data) ? [] : {};
|
||||
|
||||
for (const key in data) {
|
||||
|
@ -210,15 +213,14 @@ export class CoreWSProvider {
|
|||
* @param needsTranslate If the message needs to be translated.
|
||||
* @param translateParams Translation params, if needed.
|
||||
* @return Fake WS error.
|
||||
* @deprecated since 3.9.5. Just create the error directly.
|
||||
*/
|
||||
createFakeWSError(message: string, needsTranslate?: boolean, translateParams?: {}): CoreWSError {
|
||||
createFakeWSError(message: string, needsTranslate?: boolean, translateParams?: {[name: string]: string}): CoreError {
|
||||
if (needsTranslate) {
|
||||
message = Translate.instance.instant(message, translateParams);
|
||||
}
|
||||
|
||||
return {
|
||||
message,
|
||||
};
|
||||
return new CoreError(message);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -230,71 +232,68 @@ export class CoreWSProvider {
|
|||
* @param onProgress Function to call on progress.
|
||||
* @return Promise resolved with the downloaded file.
|
||||
*/
|
||||
downloadFile(url: string, path: string, addExtension?: boolean, onProgress?: (event: ProgressEvent) => any): Promise<any> {
|
||||
async downloadFile(url: string, path: string, addExtension?: boolean, onProgress?: (event: ProgressEvent) => void):
|
||||
Promise<CoreWSDownloadedFileEntry> {
|
||||
this.logger.debug('Downloading file', url, path, addExtension);
|
||||
|
||||
if (!CoreApp.instance.isOnline()) {
|
||||
return Promise.reject(Translate.instance.instant('core.networkerrormsg'));
|
||||
throw new CoreError(Translate.instance.instant('core.networkerrormsg'));
|
||||
}
|
||||
|
||||
// Use a tmp path to download the file and then move it to final location.
|
||||
// This is because if the download fails, the local file is deleted.
|
||||
const tmpPath = path + '.tmp';
|
||||
|
||||
// Create the tmp file as an empty file.
|
||||
return CoreFile.instance.createFile(tmpPath).then((fileEntry) => {
|
||||
try {
|
||||
// Create the tmp file as an empty file.
|
||||
const fileEntry = await CoreFile.instance.createFile(tmpPath);
|
||||
|
||||
const transfer = FileTransfer.instance.create();
|
||||
transfer.onProgress(onProgress);
|
||||
|
||||
return transfer.download(url, fileEntry.toURL(), true).then(() => {
|
||||
let promise;
|
||||
// Download the file in the tmp file.
|
||||
await transfer.download(url, fileEntry.toURL(), true);
|
||||
|
||||
if (addExtension) {
|
||||
const ext = CoreMimetypeUtils.instance.getFileExtension(path);
|
||||
let extension = '';
|
||||
|
||||
// Google Drive extensions will be considered invalid since Moodle usually converts them.
|
||||
if (!ext || ext == 'gdoc' || ext == 'gsheet' || ext == 'gslides' || ext == 'gdraw' || ext == 'php') {
|
||||
// Not valid, get the file's mimetype.
|
||||
promise = this.getRemoteFileMimeType(url).then((mime) => {
|
||||
if (mime) {
|
||||
const remoteExt = CoreMimetypeUtils.instance.getExtension(mime, url);
|
||||
// If the file is from Google Drive, ignore mimetype application/json.
|
||||
if (remoteExt && (!ext || mime != 'application/json')) {
|
||||
if (ext) {
|
||||
// Remove existing extension since we will use another one.
|
||||
path = CoreMimetypeUtils.instance.removeExtension(path);
|
||||
}
|
||||
path += '.' + remoteExt;
|
||||
if (addExtension) {
|
||||
extension = CoreMimetypeUtils.instance.getFileExtension(path);
|
||||
|
||||
return remoteExt;
|
||||
}
|
||||
// Google Drive extensions will be considered invalid since Moodle usually converts them.
|
||||
if (!extension || CoreArray.contains(['gdoc', 'gsheet', 'gslides', 'gdraw', 'php'], extension)) {
|
||||
// Not valid, get the file's mimetype.
|
||||
const mimetype = await this.getRemoteFileMimeType(url);
|
||||
|
||||
if (mimetype) {
|
||||
const remoteExtension = CoreMimetypeUtils.instance.getExtension(mimetype, url);
|
||||
// If the file is from Google Drive, ignore mimetype application/json.
|
||||
if (remoteExtension && (!extension || mimetype != 'application/json')) {
|
||||
if (extension) {
|
||||
// Remove existing extension since we will use another one.
|
||||
path = CoreMimetypeUtils.instance.removeExtension(path);
|
||||
}
|
||||
path += '.' + remoteExtension;
|
||||
|
||||
return ext;
|
||||
});
|
||||
} else {
|
||||
promise = Promise.resolve(ext);
|
||||
extension = remoteExtension;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
promise = Promise.resolve('');
|
||||
}
|
||||
}
|
||||
|
||||
return promise.then((extension) => {
|
||||
return CoreFile.instance.moveFile(tmpPath, path).then((movedEntry) => {
|
||||
// Save the extension.
|
||||
movedEntry.extension = extension;
|
||||
movedEntry.path = path;
|
||||
this.logger.debug(`Success downloading file ${url} to ${path} with extension ${extension}`);
|
||||
// Move the file to the final location.
|
||||
const movedEntry: CoreWSDownloadedFileEntry = await CoreFile.instance.moveFile(tmpPath, path);
|
||||
|
||||
return movedEntry;
|
||||
});
|
||||
});
|
||||
});
|
||||
}).catch((err) => {
|
||||
this.logger.error(`Error downloading ${url} to ${path}`, err);
|
||||
// Save the extension.
|
||||
movedEntry.extension = extension;
|
||||
movedEntry.path = path;
|
||||
this.logger.debug(`Success downloading file ${url} to ${path} with extension ${extension}`);
|
||||
|
||||
return Promise.reject(err);
|
||||
});
|
||||
return movedEntry;
|
||||
} catch (error) {
|
||||
this.logger.error(`Error downloading ${url} to ${path}`, error);
|
||||
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -304,13 +303,11 @@ export class CoreWSProvider {
|
|||
* @param url Base URL of the HTTP request.
|
||||
* @param params Params of the HTTP request.
|
||||
*/
|
||||
protected getPromiseHttp(method: string, url: string, params?: any): any {
|
||||
protected getPromiseHttp<T = unknown>(method: string, url: string, params?: Record<string, unknown>): Promise<T> {
|
||||
const queueItemId = this.getQueueItemId(method, url, params);
|
||||
if (typeof this.ongoingCalls[queueItemId] != 'undefined') {
|
||||
return this.ongoingCalls[queueItemId];
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -334,10 +331,10 @@ export class CoreWSProvider {
|
|||
this.mimeTypeCache[url] = mimeType;
|
||||
|
||||
return mimeType || '';
|
||||
}).catch(() => {
|
||||
}).catch(() =>
|
||||
// Error, resolve with empty mimetype.
|
||||
return '';
|
||||
});
|
||||
'',
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -355,10 +352,10 @@ export class CoreWSProvider {
|
|||
}
|
||||
|
||||
return -1;
|
||||
}).catch(() => {
|
||||
}).catch(() =>
|
||||
// Error, return -1.
|
||||
return -1;
|
||||
});
|
||||
-1,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -378,7 +375,7 @@ export class CoreWSProvider {
|
|||
* @param params Params of the HTTP request.
|
||||
* @return Queue item ID.
|
||||
*/
|
||||
protected getQueueItemId(method: string, url: string, params?: any): string {
|
||||
protected getQueueItemId(method: string, url: string, params?: Record<string, unknown>): string {
|
||||
if (params) {
|
||||
url += '###' + CoreInterceptor.serialize(params);
|
||||
}
|
||||
|
@ -397,14 +394,14 @@ export class CoreWSProvider {
|
|||
* - errorcode: Error code returned by the site (if any).
|
||||
* - available: 0 if unknown, 1 if available, -1 if not available.
|
||||
*/
|
||||
protected performAjax(method: string, data: any, preSets: CoreWSAjaxPreSets): Promise<any> {
|
||||
|
||||
let promise;
|
||||
protected performAjax<T = unknown>(method: string, data: Record<string, unknown>, preSets: CoreWSAjaxPreSets): Promise<T> {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
let promise: Promise<HttpResponse<any>>;
|
||||
|
||||
if (typeof preSets.siteUrl == 'undefined') {
|
||||
return rejectWithError(this.createFakeWSError('core.unexpectederror', true));
|
||||
return Promise.reject(new CoreAjaxError(Translate.instance.instant('core.unexpectederror')));
|
||||
} else if (!CoreApp.instance.isOnline()) {
|
||||
return rejectWithError(this.createFakeWSError('core.networkerrormsg', true));
|
||||
return Promise.reject(new CoreAjaxError(Translate.instance.instant('core.networkerrormsg')));
|
||||
}
|
||||
|
||||
if (typeof preSets.responseExpected == 'undefined') {
|
||||
|
@ -415,7 +412,7 @@ export class CoreWSProvider {
|
|||
const ajaxData = [{
|
||||
index: 0,
|
||||
methodname: method,
|
||||
args: this.convertValuesToString(data)
|
||||
args: this.convertValuesToString(data),
|
||||
}];
|
||||
|
||||
// The info= parameter has no function. It is just to help with debugging.
|
||||
|
@ -426,18 +423,19 @@ export class CoreWSProvider {
|
|||
// Send params using GET.
|
||||
siteUrl += '&args=' + encodeURIComponent(JSON.stringify(ajaxData));
|
||||
|
||||
promise = this.sendHTTPRequest(siteUrl, {
|
||||
promise = this.sendHTTPRequest<T>(siteUrl, {
|
||||
method: 'get',
|
||||
});
|
||||
} else {
|
||||
promise = this.sendHTTPRequest(siteUrl, {
|
||||
promise = this.sendHTTPRequest<T>(siteUrl, {
|
||||
method: 'post',
|
||||
data: ajaxData,
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
data: <any> ajaxData,
|
||||
serializer: 'json',
|
||||
});
|
||||
}
|
||||
|
||||
return promise.then((response: HttpResponse<any>) => {
|
||||
return promise.then((response) => {
|
||||
let data = response.body;
|
||||
|
||||
// Some moodle web services return null.
|
||||
|
@ -448,39 +446,24 @@ export class CoreWSProvider {
|
|||
|
||||
// Check if error. Ajax layer should always return an object (if error) or an array (if success).
|
||||
if (!data || typeof data != 'object') {
|
||||
return rejectWithError(this.createFakeWSError('core.serverconnection', true));
|
||||
return Promise.reject(new CoreAjaxError(Translate.instance.instant('core.serverconnection')));
|
||||
} else if (data.error) {
|
||||
return rejectWithError(data);
|
||||
return Promise.reject(new CoreAjaxWSError(data));
|
||||
}
|
||||
|
||||
// Get the first response since only one request was done.
|
||||
data = data[0];
|
||||
|
||||
if (data.error) {
|
||||
return rejectWithError(data.exception);
|
||||
return Promise.reject(new CoreAjaxWSError(data.exception));
|
||||
}
|
||||
|
||||
return data.data;
|
||||
}, (data) => {
|
||||
const available = data.status == 404 ? -1 : 0;
|
||||
|
||||
return rejectWithError(this.createFakeWSError('core.serverconnection', true), available);
|
||||
return Promise.reject(new CoreAjaxError(Translate.instance.instant('core.serverconnection'), available));
|
||||
});
|
||||
|
||||
// Convenience function to return an error.
|
||||
function rejectWithError(exception: any, available?: number): Promise<never> {
|
||||
if (typeof available == 'undefined') {
|
||||
if (exception.errorcode) {
|
||||
available = exception.errorcode == 'invalidrecord' ? -1 : 1;
|
||||
} else {
|
||||
available = 0;
|
||||
}
|
||||
}
|
||||
|
||||
exception.available = available;
|
||||
|
||||
return Promise.reject(exception);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -489,16 +472,16 @@ export class CoreWSProvider {
|
|||
* @param url URL to perform the request.
|
||||
* @return Promise resolved with the response.
|
||||
*/
|
||||
performHead(url: string): Promise<HttpResponse<any>> {
|
||||
let promise = this.getPromiseHttp('head', url);
|
||||
performHead<T = unknown>(url: string): Promise<HttpResponse<T>> {
|
||||
let promise = this.getPromiseHttp<HttpResponse<T>>('head', url);
|
||||
|
||||
if (!promise) {
|
||||
promise = this.sendHTTPRequest(url, {
|
||||
promise = this.sendHTTPRequest<T>(url, {
|
||||
method: 'head',
|
||||
responseType: 'text',
|
||||
});
|
||||
|
||||
promise = this.setPromiseHttp(promise, 'head', url);
|
||||
promise = this.setPromiseHttp<HttpResponse<T>>(promise, 'head', url);
|
||||
}
|
||||
|
||||
return promise;
|
||||
|
@ -513,12 +496,12 @@ export class CoreWSProvider {
|
|||
* @param preSets Extra settings and information.
|
||||
* @return Promise resolved with the response data in success and rejected with CoreWSError if it fails.
|
||||
*/
|
||||
performPost(method: string, siteUrl: string, ajaxData: any, preSets: CoreWSPreSets): Promise<any> {
|
||||
performPost<T = unknown>(method: string, siteUrl: string, ajaxData: unknown, preSets: CoreWSPreSets): Promise<T> {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const options: any = {};
|
||||
|
||||
// This is done because some returned values like 0 are treated as null if responseType is json.
|
||||
if (preSets.typeExpected == 'number' || preSets.typeExpected == 'boolean' || preSets.typeExpected == 'string') {
|
||||
// Avalaible values are: https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/responseType
|
||||
options.responseType = 'text';
|
||||
}
|
||||
|
||||
|
@ -530,8 +513,8 @@ export class CoreWSProvider {
|
|||
// Perform the post request.
|
||||
const promise = Http.instance.post(requestUrl, ajaxData, options).pipe(timeout(this.getRequestTimeout())).toPromise();
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
return promise.then((data: any) => {
|
||||
|
||||
// Some moodle web services return null.
|
||||
// If the responseExpected value is set to false, we create a blank object if the response is null.
|
||||
if (!data && !preSets.responseExpected) {
|
||||
|
@ -539,7 +522,7 @@ export class CoreWSProvider {
|
|||
}
|
||||
|
||||
if (!data) {
|
||||
return Promise.reject(this.createFakeWSError('core.serverconnection', true));
|
||||
return Promise.reject(new CoreError(Translate.instance.instant('core.serverconnection')));
|
||||
} else if (typeof data != preSets.typeExpected) {
|
||||
// If responseType is text an string will be returned, parse before returning.
|
||||
if (typeof data == 'string') {
|
||||
|
@ -548,7 +531,7 @@ export class CoreWSProvider {
|
|||
if (isNaN(data)) {
|
||||
this.logger.warn(`Response expected type "${preSets.typeExpected}" cannot be parsed to number`);
|
||||
|
||||
return Promise.reject(this.createFakeWSError('core.errorinvalidresponse', true));
|
||||
return Promise.reject(new CoreError(Translate.instance.instant('core.errorinvalidresponse')));
|
||||
}
|
||||
} else if (preSets.typeExpected == 'boolean') {
|
||||
if (data === 'true') {
|
||||
|
@ -558,17 +541,17 @@ export class CoreWSProvider {
|
|||
} else {
|
||||
this.logger.warn(`Response expected type "${preSets.typeExpected}" is not true or false`);
|
||||
|
||||
return Promise.reject(this.createFakeWSError('core.errorinvalidresponse', true));
|
||||
return Promise.reject(new CoreError(Translate.instance.instant('core.errorinvalidresponse')));
|
||||
}
|
||||
} else {
|
||||
this.logger.warn('Response of type "' + typeof data + `" received, expecting "${preSets.typeExpected}"`);
|
||||
|
||||
return Promise.reject(this.createFakeWSError('core.errorinvalidresponse', true));
|
||||
return Promise.reject(new CoreError(Translate.instance.instant('core.errorinvalidresponse')));
|
||||
}
|
||||
} else {
|
||||
this.logger.warn('Response of type "' + typeof data + `" received, expecting "${preSets.typeExpected}"`);
|
||||
|
||||
return Promise.reject(this.createFakeWSError('core.errorinvalidresponse', true));
|
||||
return Promise.reject(new CoreError(Translate.instance.instant('core.errorinvalidresponse')));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -578,18 +561,18 @@ export class CoreWSProvider {
|
|||
this.logger.error('Error calling WS', method, data);
|
||||
}
|
||||
|
||||
return Promise.reject(data);
|
||||
return Promise.reject(new CoreWSError(data));
|
||||
}
|
||||
|
||||
if (typeof data.debuginfo != 'undefined') {
|
||||
return Promise.reject(this.createFakeWSError('Error. ' + data.message));
|
||||
return Promise.reject(new CoreError('Error. ' + data.message));
|
||||
}
|
||||
|
||||
return data;
|
||||
}, (error) => {
|
||||
// If server has heavy load, retry after some seconds.
|
||||
if (error.status == 429) {
|
||||
const retryPromise = this.addToRetryQueue(method, siteUrl, ajaxData, preSets);
|
||||
const retryPromise = this.addToRetryQueue<T>(method, siteUrl, ajaxData, preSets);
|
||||
|
||||
// Only process the queue one time.
|
||||
if (this.retryTimeout == 0) {
|
||||
|
@ -610,7 +593,7 @@ export class CoreWSProvider {
|
|||
return retryPromise;
|
||||
}
|
||||
|
||||
return Promise.reject(this.createFakeWSError('core.serverconnection', true));
|
||||
return Promise.reject(new CoreError(Translate.instance.instant('core.serverconnection')));
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -623,7 +606,7 @@ export class CoreWSProvider {
|
|||
const call = this.retryCalls.shift();
|
||||
// Add a delay between calls.
|
||||
setTimeout(() => {
|
||||
call.deferred.resolve(this.performPost(call.method, call.siteUrl, call.ajaxData, call.preSets));
|
||||
call.deferred.resolve(this.performPost(call.method, call.siteUrl, call.data, call.preSets));
|
||||
this.processRetryQueue();
|
||||
}, 200);
|
||||
} else {
|
||||
|
@ -640,14 +623,14 @@ export class CoreWSProvider {
|
|||
* @param params Params of the HTTP request.
|
||||
* @return The promise saved.
|
||||
*/
|
||||
protected setPromiseHttp(promise: Promise<any>, method: string, url: string, params?: any): Promise<any> {
|
||||
protected setPromiseHttp<T = unknown>(promise: Promise<T>, method: string, url: string, params?: Record<string, unknown>):
|
||||
Promise<T> {
|
||||
const queueItemId = this.getQueueItemId(method, url, params);
|
||||
let timeout;
|
||||
|
||||
this.ongoingCalls[queueItemId] = promise;
|
||||
|
||||
// HTTP not finished, but we should delete the promise after timeout.
|
||||
timeout = setTimeout(() => {
|
||||
const timeout = setTimeout(() => {
|
||||
delete this.ongoingCalls[queueItemId];
|
||||
}, this.getRequestTimeout());
|
||||
|
||||
|
@ -667,22 +650,14 @@ export class CoreWSProvider {
|
|||
* @param data Arguments to pass to the method.
|
||||
* @param preSets Extra settings and information.
|
||||
* @return Promise resolved with the response data in success and rejected with the error message if it fails.
|
||||
* @return Request response. If the request fails, returns an object with 'error'=true and 'message' properties.
|
||||
* @return Request response.
|
||||
*/
|
||||
syncCall(method: string, data: any, preSets: CoreWSPreSets): any {
|
||||
const errorResponse = {
|
||||
error: true,
|
||||
message: '',
|
||||
};
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/explicit-module-boundary-types
|
||||
syncCall<T = unknown>(method: string, data: any, preSets: CoreWSPreSets): T {
|
||||
if (!preSets) {
|
||||
errorResponse.message = Translate.instance.instant('core.unexpectederror');
|
||||
|
||||
return errorResponse;
|
||||
throw new CoreError(Translate.instance.instant('core.unexpectederror'));
|
||||
} else if (!CoreApp.instance.isOnline()) {
|
||||
errorResponse.message = Translate.instance.instant('core.networkerrormsg');
|
||||
|
||||
return errorResponse;
|
||||
throw new CoreError(Translate.instance.instant('core.networkerrormsg'));
|
||||
}
|
||||
|
||||
preSets.typeExpected = preSets.typeExpected || 'object';
|
||||
|
@ -693,9 +668,7 @@ export class CoreWSProvider {
|
|||
data = this.convertValuesToString(data || {}, preSets.cleanUnicode);
|
||||
if (data == null) {
|
||||
// Empty cleaned text found.
|
||||
errorResponse.message = Translate.instance.instant('core.unicodenotsupportedcleanerror');
|
||||
|
||||
return errorResponse;
|
||||
throw new CoreError(Translate.instance.instant('core.unicodenotsupportedcleanerror'));
|
||||
}
|
||||
|
||||
data.wsfunction = method;
|
||||
|
@ -706,22 +679,21 @@ export class CoreWSProvider {
|
|||
data = CoreInterceptor.serialize(data);
|
||||
|
||||
// Perform sync request using XMLHttpRequest.
|
||||
const xhr = new (<any> window).XMLHttpRequest();
|
||||
const xhr = new XMLHttpRequest();
|
||||
xhr.open('post', siteUrl, false);
|
||||
xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded;charset=utf-8');
|
||||
|
||||
xhr.send(data);
|
||||
|
||||
// Get response.
|
||||
data = ('response' in xhr) ? xhr.response : xhr.responseText;
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
data = ('response' in xhr) ? xhr.response : (<any> xhr).responseText;
|
||||
|
||||
// Check status.
|
||||
const status = Math.max(xhr.status === 1223 ? 204 : xhr.status, 0);
|
||||
if (status < 200 || status >= 300) {
|
||||
// Request failed.
|
||||
errorResponse.message = data;
|
||||
|
||||
return errorResponse;
|
||||
throw new CoreError(data);
|
||||
}
|
||||
|
||||
// Treat response.
|
||||
|
@ -734,18 +706,14 @@ export class CoreWSProvider {
|
|||
}
|
||||
|
||||
if (!data) {
|
||||
errorResponse.message = Translate.instance.instant('core.serverconnection');
|
||||
throw new CoreError(Translate.instance.instant('core.serverconnection'));
|
||||
} else if (typeof data != preSets.typeExpected) {
|
||||
this.logger.warn('Response of type "' + typeof data + '" received, expecting "' + preSets.typeExpected + '"');
|
||||
errorResponse.message = Translate.instance.instant('core.errorinvalidresponse');
|
||||
throw new CoreError(Translate.instance.instant('core.errorinvalidresponse'));
|
||||
}
|
||||
|
||||
if (typeof data.exception != 'undefined' || typeof data.debuginfo != 'undefined') {
|
||||
errorResponse.message = data.message;
|
||||
}
|
||||
|
||||
if (errorResponse.message !== '') {
|
||||
return errorResponse;
|
||||
throw new CoreWSError(data);
|
||||
}
|
||||
|
||||
return data;
|
||||
|
@ -760,16 +728,16 @@ export class CoreWSProvider {
|
|||
* @param onProgress Function to call on progress.
|
||||
* @return Promise resolved when uploaded.
|
||||
*/
|
||||
uploadFile(filePath: string, options: CoreWSFileUploadOptions, preSets: CoreWSPreSets,
|
||||
onProgress?: (event: ProgressEvent) => any): Promise<any> {
|
||||
uploadFile<T = unknown>(filePath: string, options: CoreWSFileUploadOptions, preSets: CoreWSPreSets,
|
||||
onProgress?: (event: ProgressEvent) => void): Promise<T> {
|
||||
this.logger.debug(`Trying to upload file: ${filePath}`);
|
||||
|
||||
if (!filePath || !options || !preSets) {
|
||||
return Promise.reject(null);
|
||||
return Promise.reject(new CoreError('Invalid options passed to upload file.'));
|
||||
}
|
||||
|
||||
if (!CoreApp.instance.isOnline()) {
|
||||
return Promise.reject(Translate.instance.instant('core.networkerrormsg'));
|
||||
return Promise.reject(new CoreError(Translate.instance.instant('core.networkerrormsg')));
|
||||
}
|
||||
|
||||
const uploadUrl = preSets.siteUrl + '/webservice/upload.php';
|
||||
|
@ -781,34 +749,40 @@ export class CoreWSProvider {
|
|||
options.params = {
|
||||
token: preSets.wsToken,
|
||||
filearea: options.fileArea || 'draft',
|
||||
itemid: options.itemId || 0
|
||||
itemid: options.itemId || 0,
|
||||
};
|
||||
options.chunkedMode = false;
|
||||
options.headers = {
|
||||
Connection: 'close'
|
||||
};
|
||||
options.headers = {};
|
||||
options['Connection'] = 'close';
|
||||
|
||||
return transfer.upload(filePath, uploadUrl, options, true).then((success) => {
|
||||
const data = CoreTextUtils.instance.parseJSON(success.response, null,
|
||||
this.logger.error.bind(this.logger, 'Error parsing response from upload', success.response));
|
||||
this.logger.error.bind(this.logger, 'Error parsing response from upload', success.response));
|
||||
|
||||
if (data === null) {
|
||||
return Promise.reject(Translate.instance.instant('core.errorinvalidresponse'));
|
||||
return Promise.reject(new CoreError(Translate.instance.instant('core.errorinvalidresponse')));
|
||||
}
|
||||
|
||||
if (!data) {
|
||||
return Promise.reject(Translate.instance.instant('core.serverconnection'));
|
||||
return Promise.reject(new CoreError(Translate.instance.instant('core.serverconnection')));
|
||||
} else if (typeof data != 'object') {
|
||||
this.logger.warn('Upload file: Response of type "' + typeof data + '" received, expecting "object"');
|
||||
|
||||
return Promise.reject(Translate.instance.instant('core.errorinvalidresponse'));
|
||||
return Promise.reject(new CoreError(Translate.instance.instant('core.errorinvalidresponse')));
|
||||
}
|
||||
|
||||
if (typeof data.exception !== 'undefined') {
|
||||
return Promise.reject(data.message);
|
||||
} else if (data && typeof data.error !== 'undefined') {
|
||||
return Promise.reject(data.error);
|
||||
return Promise.reject(new CoreWSError(data));
|
||||
} else if (typeof data.error !== 'undefined') {
|
||||
return Promise.reject(new CoreWSError({
|
||||
errorcode: data.errortype,
|
||||
message: data.error,
|
||||
}));
|
||||
} else if (data[0] && typeof data[0].error !== 'undefined') {
|
||||
return Promise.reject(data[0].error);
|
||||
return Promise.reject(new CoreWSError({
|
||||
errorcode: data[0].errortype,
|
||||
message: data[0].error,
|
||||
}));
|
||||
}
|
||||
|
||||
// We uploaded only 1 file, so we only return the first file returned.
|
||||
|
@ -818,7 +792,7 @@ export class CoreWSProvider {
|
|||
}).catch((error) => {
|
||||
this.logger.error('Error while uploading file', filePath, error);
|
||||
|
||||
return Promise.reject(Translate.instance.instant('core.errorinvalidresponse'));
|
||||
return Promise.reject(new CoreError(Translate.instance.instant('core.errorinvalidresponse')));
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -835,7 +809,7 @@ export class CoreWSProvider {
|
|||
responseType: 'text',
|
||||
};
|
||||
|
||||
const response = await this.sendHTTPRequest(url, options);
|
||||
const response = await this.sendHTTPRequest<string>(url, options);
|
||||
|
||||
const content = response.body;
|
||||
|
||||
|
@ -853,8 +827,7 @@ export class CoreWSProvider {
|
|||
* @param options Options for the request.
|
||||
* @return Promise resolved with the response.
|
||||
*/
|
||||
async sendHTTPRequest(url: string, options: HttpRequestOptions): Promise<HttpResponse<any>> {
|
||||
|
||||
async sendHTTPRequest<T = unknown>(url: string, options: HttpRequestOptions): Promise<HttpResponse<T>> {
|
||||
// Set default values.
|
||||
options.responseType = options.responseType || 'json';
|
||||
options.timeout = typeof options.timeout == 'undefined' ? this.getRequestTimeout() : options.timeout;
|
||||
|
@ -867,8 +840,8 @@ export class CoreWSProvider {
|
|||
|
||||
const content = await CoreFile.instance.readFile(url, format);
|
||||
|
||||
return new HttpResponse({
|
||||
body: content,
|
||||
return new HttpResponse<T>({
|
||||
body: <T> content,
|
||||
headers: null,
|
||||
status: 200,
|
||||
statusText: 'OK',
|
||||
|
@ -876,81 +849,78 @@ export class CoreWSProvider {
|
|||
});
|
||||
}
|
||||
|
||||
return new Promise<HttpResponse<any>>((resolve, reject): void => {
|
||||
// We cannot use Ionic Native plugin because it doesn't have the sendRequest method.
|
||||
(<any> cordova).plugin.http.sendRequest(url, options, (response) => {
|
||||
resolve(new CoreNativeToAngularHttpResponse(response));
|
||||
}, reject);
|
||||
});
|
||||
return NativeHttp.instance.sendRequest(url, options).then((response) => new CoreNativeToAngularHttpResponse(response));
|
||||
} else {
|
||||
let observable: Observable<any>;
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
let observable: Observable<HttpResponse<any>>;
|
||||
const angularOptions = <AngularHttpRequestOptions> options;
|
||||
|
||||
// Use Angular's library.
|
||||
switch (options.method) {
|
||||
switch (angularOptions.method) {
|
||||
case 'get':
|
||||
observable = Http.instance.get(url, {
|
||||
headers: options.headers,
|
||||
params: options.params,
|
||||
headers: angularOptions.headers,
|
||||
params: angularOptions.params,
|
||||
observe: 'response',
|
||||
responseType: <any> options.responseType,
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
responseType: <any> angularOptions.responseType,
|
||||
});
|
||||
break;
|
||||
|
||||
case 'post':
|
||||
if (options.serializer == 'json') {
|
||||
options.data = JSON.stringify(options.data);
|
||||
if (angularOptions.serializer == 'json') {
|
||||
angularOptions.data = JSON.stringify(angularOptions.data);
|
||||
}
|
||||
|
||||
observable = Http.instance.post(url, options.data, {
|
||||
headers: options.headers,
|
||||
observable = Http.instance.post(url, angularOptions.data, {
|
||||
headers: angularOptions.headers,
|
||||
observe: 'response',
|
||||
responseType: <any> options.responseType,
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
responseType: <any> angularOptions.responseType,
|
||||
});
|
||||
break;
|
||||
|
||||
case 'head':
|
||||
observable = Http.instance.head(url, {
|
||||
headers: options.headers,
|
||||
headers: angularOptions.headers,
|
||||
observe: 'response',
|
||||
responseType: <any> options.responseType
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
responseType: <any> angularOptions.responseType,
|
||||
});
|
||||
break;
|
||||
|
||||
default:
|
||||
return Promise.reject('Method not implemented yet.');
|
||||
return Promise.reject(new CoreError('Method not implemented yet.'));
|
||||
}
|
||||
|
||||
if (options.timeout) {
|
||||
observable = observable.pipe(timeout(options.timeout));
|
||||
if (angularOptions.timeout) {
|
||||
observable = observable.pipe(timeout(angularOptions.timeout));
|
||||
}
|
||||
|
||||
return observable.toPromise();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a URL works (it returns a 2XX status).
|
||||
*
|
||||
* @param url URL to check.
|
||||
* @return Promise resolved with boolean: whether it works.
|
||||
*/
|
||||
async urlWorks(url: string): Promise<boolean> {
|
||||
try {
|
||||
const result = await this.performHead(url);
|
||||
|
||||
return result.status >= 200 && result.status < 300;
|
||||
} catch (error) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export class CoreWS extends makeSingleton(CoreWSProvider) {}
|
||||
|
||||
/**
|
||||
* Error returned by a WS call.
|
||||
*/
|
||||
export interface CoreWSError {
|
||||
/**
|
||||
* The error message.
|
||||
*/
|
||||
message: string;
|
||||
|
||||
/**
|
||||
* Name of the exception. Undefined for local errors (fake WS errors).
|
||||
*/
|
||||
exception?: string;
|
||||
|
||||
/**
|
||||
* The error code. Undefined for local errors (fake WS errors).
|
||||
*/
|
||||
errorcode?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* File upload options.
|
||||
*/
|
||||
|
@ -1084,7 +1054,7 @@ export type CoreWSPreSets = {
|
|||
* Defaults to false. Clean multibyte Unicode chars from data.
|
||||
*/
|
||||
cleanUnicode?: boolean;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* PreSets accepted by AJAX WS calls.
|
||||
|
@ -1109,7 +1079,7 @@ export type CoreWSAjaxPreSets = {
|
|||
* Whether to send the parameters via GET. Only if noLogin is true.
|
||||
*/
|
||||
useGet?: boolean;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Options for HTTP requests.
|
||||
|
@ -1118,17 +1088,17 @@ export type HttpRequestOptions = {
|
|||
/**
|
||||
* The HTTP method.
|
||||
*/
|
||||
method: string;
|
||||
method: 'get' | 'post' | 'put' | 'patch' | 'head' | 'delete' | 'options' | 'upload' | 'download';
|
||||
|
||||
/**
|
||||
* Payload to send to the server. Only applicable on post, put or patch methods.
|
||||
*/
|
||||
data?: any;
|
||||
data?: Record<string, unknown>;
|
||||
|
||||
/**
|
||||
* Query params to be appended to the URL (only applicable on get, head, delete, upload or download methods).
|
||||
*/
|
||||
params?: any;
|
||||
params?: Record<string, string | number>;
|
||||
|
||||
/**
|
||||
* Response type. Defaults to json.
|
||||
|
@ -1143,7 +1113,7 @@ export type HttpRequestOptions = {
|
|||
/**
|
||||
* Serializer to use. Defaults to 'urlencoded'. Only for mobile environments.
|
||||
*/
|
||||
serializer?: string;
|
||||
serializer?: 'json' | 'urlencoded' | 'utf8' | 'multipart';
|
||||
|
||||
/**
|
||||
* Whether to follow redirects. Defaults to true. Only for mobile environments.
|
||||
|
@ -1153,16 +1123,45 @@ export type HttpRequestOptions = {
|
|||
/**
|
||||
* Headers. Only for mobile environments.
|
||||
*/
|
||||
headers?: {[name: string]: string};
|
||||
headers?: Record<string, string>;
|
||||
|
||||
/**
|
||||
* File paths to use for upload or download. Only for mobile environments.
|
||||
*/
|
||||
filePath?: string;
|
||||
filePath?: string | string[];
|
||||
|
||||
/**
|
||||
* Name to use during upload. Only for mobile environments.
|
||||
*/
|
||||
name?: string;
|
||||
name?: string | string[];
|
||||
};
|
||||
|
||||
/**
|
||||
* Options for JSON HTTP requests using Angular Http.
|
||||
*/
|
||||
type AngularHttpRequestOptions = Omit<HttpRequestOptions, 'data'|'params'> & {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
data?: Record<string, any> | string;
|
||||
params?: HttpParams | {
|
||||
[param: string]: string | string[];
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Data needed to retry a WS call.
|
||||
*/
|
||||
type RetryCall = {
|
||||
method: string;
|
||||
siteUrl: string;
|
||||
data: unknown;
|
||||
preSets: CoreWSPreSets;
|
||||
deferred: PromiseDefer<unknown>;
|
||||
};
|
||||
|
||||
/**
|
||||
* Downloaded file entry. It includes some calculated data.
|
||||
*/
|
||||
export type CoreWSDownloadedFileEntry = FileEntry & {
|
||||
extension?: string; // File extension.
|
||||
path?: string; // File path.
|
||||
};
|
||||
|
|
|
@ -38,7 +38,7 @@ export class CoreArray {
|
|||
*/
|
||||
static flatten<T>(arr: T[][]): T[] {
|
||||
if ('flat' in arr) {
|
||||
return (arr as any).flat();
|
||||
return (arr as any).flat(); // eslint-disable-line @typescript-eslint/no-explicit-any
|
||||
}
|
||||
|
||||
return [].concat(...arr);
|
||||
|
|
|
@ -15,7 +15,13 @@
|
|||
import { Injector, NgZone as NgZoneService } from '@angular/core';
|
||||
import { HttpClient } from '@angular/common/http';
|
||||
|
||||
import { Platform as PlatformService } from '@ionic/angular';
|
||||
import {
|
||||
Platform as PlatformService,
|
||||
AlertController as AlertControllerService,
|
||||
LoadingController as LoadingControllerService,
|
||||
ModalController as ModalControllerService,
|
||||
ToastController as ToastControllerService,
|
||||
} from '@ionic/angular';
|
||||
|
||||
import { Clipboard as ClipboardService } from '@ionic-native/clipboard/ngx';
|
||||
import { Diagnostic as DiagnosticService } from '@ionic-native/diagnostic/ngx';
|
||||
|
@ -25,7 +31,9 @@ import { FileOpener as FileOpenerService } from '@ionic-native/file-opener/ngx';
|
|||
import { FileTransfer as FileTransferService } from '@ionic-native/file-transfer/ngx';
|
||||
import { Geolocation as GeolocationService } from '@ionic-native/geolocation/ngx';
|
||||
import { Globalization as GlobalizationService } from '@ionic-native/globalization/ngx';
|
||||
import { HTTP } from '@ionic-native/http/ngx';
|
||||
import { InAppBrowser as InAppBrowserService } from '@ionic-native/in-app-browser/ngx';
|
||||
import { WebView as WebViewService } from '@ionic-native/ionic-webview/ngx';
|
||||
import { Keyboard as KeyboardService } from '@ionic-native/keyboard/ngx';
|
||||
import { LocalNotifications as LocalNotificationsService } from '@ionic-native/local-notifications/ngx';
|
||||
import { Network as NetworkService } from '@ionic-native/network/ngx';
|
||||
|
@ -74,6 +82,7 @@ export class Globalization extends makeSingleton(GlobalizationService) {}
|
|||
export class InAppBrowser extends makeSingleton(InAppBrowserService) {}
|
||||
export class Keyboard extends makeSingleton(KeyboardService) {}
|
||||
export class LocalNotifications extends makeSingleton(LocalNotificationsService) {}
|
||||
export class NativeHttp extends makeSingleton(HTTP) {}
|
||||
export class Network extends makeSingleton(NetworkService) {}
|
||||
export class Push extends makeSingleton(PushService) {}
|
||||
export class QRScanner extends makeSingleton(QRScannerService) {}
|
||||
|
@ -81,12 +90,17 @@ export class StatusBar extends makeSingleton(StatusBarService) {}
|
|||
export class SplashScreen extends makeSingleton(SplashScreenService) {}
|
||||
export class SQLite extends makeSingleton(SQLiteService) {}
|
||||
export class WebIntent extends makeSingleton(WebIntentService) {}
|
||||
export class WebView extends makeSingleton(WebViewService) {}
|
||||
export class Zip extends makeSingleton(ZipService) {}
|
||||
|
||||
// Convert some Angular and Ionic injectables to singletons.
|
||||
export class NgZone extends makeSingleton(NgZoneService) {}
|
||||
export class Http extends makeSingleton(HttpClient) {}
|
||||
export class Platform extends makeSingleton(PlatformService) {}
|
||||
export class AlertController extends makeSingleton(AlertControllerService) {}
|
||||
export class LoadingController extends makeSingleton(LoadingControllerService) {}
|
||||
export class ModalController extends makeSingleton(ModalControllerService) {}
|
||||
export class ToastController extends makeSingleton(ToastControllerService) {}
|
||||
|
||||
// Convert external libraries injectables.
|
||||
export class Translate extends makeSingleton(TranslateService) {}
|
||||
|
|
|
@ -400,7 +400,7 @@ function unserialize (str) {
|
|||
}
|
||||
}
|
||||
|
||||
function substr_replace (str, replace, start, length) { // eslint-disable-line camelcase
|
||||
function substr_replace (str, replace, start, length) {
|
||||
// discuss at: https://locutus.io/php/substr_replace/
|
||||
// original by: Brett Zamir (https://brett-zamir.me)
|
||||
// example 1: substr_replace('ABCDEFGH:/MNRPQR/', 'bob', 0)
|
||||
|
|
|
@ -24,6 +24,7 @@ export type CoreWindowOpenOptions = {
|
|||
/**
|
||||
* NavController to use when opening the link in the app.
|
||||
*/
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
navCtrl?: any; // @todo NavController;
|
||||
};
|
||||
|
||||
|
@ -36,7 +37,7 @@ export class CoreWindow {
|
|||
private constructor() {
|
||||
// Nothing to do.
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* "Safe" implementation of window.open. It will open the URL without overriding the app.
|
||||
*
|
||||
|
@ -60,11 +61,13 @@ export class CoreWindow {
|
|||
await CoreUtils.instance.openFile(url);
|
||||
} else {
|
||||
let treated: boolean;
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
options = options || {};
|
||||
|
||||
if (name != '_system') {
|
||||
// Check if it can be opened in the app.
|
||||
treated = false; // @todo await CoreContentLinksHelper.instance.handleLink(url, undefined, options.navCtrl, true, true);
|
||||
treated = false;
|
||||
// @todo await CoreContentLinksHelper.instance.handleLink(url, undefined, options.navCtrl, true, true);
|
||||
}
|
||||
|
||||
if (!treated) {
|
||||
|
|
|
@ -6,7 +6,8 @@
|
|||
"cordova-plugin-file-transfer",
|
||||
"cordova-plugin-inappbrowser",
|
||||
"cordova",
|
||||
"node"
|
||||
"node",
|
||||
"dom-mediacapture-record"
|
||||
],
|
||||
"paths": {
|
||||
"@/*": ["*"],
|
||||
|
|
|
@ -22,7 +22,8 @@
|
|||
"cordova-plugin-inappbrowser",
|
||||
"cordova",
|
||||
"jest",
|
||||
"node"
|
||||
"node",
|
||||
"dom-mediacapture-record"
|
||||
],
|
||||
"paths": {
|
||||
"@/*": ["*"],
|
||||
|
|
Loading…
Reference in New Issue