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