MOBILE-2302 core: Implement core-file component
|
@ -216,3 +216,12 @@ core-format-text, *[core-format-text] {
|
|||
display: inline;
|
||||
}
|
||||
}
|
||||
|
||||
// Media item, ideal for icons.
|
||||
.item-media {
|
||||
min-height: $item-media-height + ($content-padding * 2);
|
||||
> img:first-child {
|
||||
max-width: $item-media-width;
|
||||
max-height: $item-media-height;
|
||||
}
|
||||
}
|
||||
|
|
After Width: | Height: | Size: 3.8 KiB |
After Width: | Height: | Size: 4.5 KiB |
After Width: | Height: | Size: 4.6 KiB |
After Width: | Height: | Size: 4.0 KiB |
After Width: | Height: | Size: 4.2 KiB |
After Width: | Height: | Size: 3.4 KiB |
After Width: | Height: | Size: 3.3 KiB |
After Width: | Height: | Size: 4.8 KiB |
After Width: | Height: | Size: 4.8 KiB |
After Width: | Height: | Size: 3.4 KiB |
After Width: | Height: | Size: 3.6 KiB |
After Width: | Height: | Size: 3.7 KiB |
After Width: | Height: | Size: 3.7 KiB |
After Width: | Height: | Size: 1.1 KiB |
After Width: | Height: | Size: 1.2 KiB |
After Width: | Height: | Size: 4.4 KiB |
After Width: | Height: | Size: 2.1 KiB |
After Width: | Height: | Size: 5.7 KiB |
After Width: | Height: | Size: 3.2 KiB |
After Width: | Height: | Size: 3.4 KiB |
After Width: | Height: | Size: 4.4 KiB |
After Width: | Height: | Size: 4.1 KiB |
After Width: | Height: | Size: 3.1 KiB |
After Width: | Height: | Size: 3.5 KiB |
After Width: | Height: | Size: 4.2 KiB |
After Width: | Height: | Size: 5.2 KiB |
After Width: | Height: | Size: 3.6 KiB |
After Width: | Height: | Size: 4.1 KiB |
After Width: | Height: | Size: 4.4 KiB |
After Width: | Height: | Size: 4.5 KiB |
After Width: | Height: | Size: 3.9 KiB |
After Width: | Height: | Size: 4.9 KiB |
After Width: | Height: | Size: 4.6 KiB |
After Width: | Height: | Size: 4.2 KiB |
After Width: | Height: | Size: 5.2 KiB |
After Width: | Height: | Size: 3.6 KiB |
After Width: | Height: | Size: 4.6 KiB |
After Width: | Height: | Size: 2.1 KiB |
After Width: | Height: | Size: 4.1 KiB |
After Width: | Height: | Size: 5.3 KiB |
After Width: | Height: | Size: 4.1 KiB |
After Width: | Height: | Size: 2.8 KiB |
|
@ -329,6 +329,21 @@ export class SQLiteDB {
|
|||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Format the data to insert in the database. Removes undefined entries so they are stored as null instead of 'undefined'.
|
||||
*
|
||||
* @param {object} data Data to insert.
|
||||
*/
|
||||
protected formatDataToInsert(data: object) : void {
|
||||
// Remove undefined entries and convert null to "NULL".
|
||||
for (let name in data) {
|
||||
let value = data[name];
|
||||
if (typeof value == 'undefined') {
|
||||
delete data[name];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all the records from a table.
|
||||
*
|
||||
|
@ -585,6 +600,8 @@ export class SQLiteDB {
|
|||
* @return {any[]} Array with the SQL query and the params.
|
||||
*/
|
||||
protected getSqlInsertQuery(table: string, data: object) : any[] {
|
||||
this.formatDataToInsert(data);
|
||||
|
||||
let keys = Object.keys(data),
|
||||
fields = keys.join(','),
|
||||
questionMarks = ',?'.repeat(keys.length).substr(1);
|
||||
|
@ -773,6 +790,8 @@ export class SQLiteDB {
|
|||
sql,
|
||||
params;
|
||||
|
||||
this.formatDataToInsert(data);
|
||||
|
||||
for (let key in data) {
|
||||
sets.push(`${key} = ?`);
|
||||
}
|
||||
|
|
|
@ -24,6 +24,7 @@ import { CoreIframeComponent } from './iframe/iframe';
|
|||
import { CoreProgressBarComponent } from './progress-bar/progress-bar';
|
||||
import { CoreEmptyBoxComponent } from './empty-box/empty-box';
|
||||
import { CoreSearchBoxComponent } from './search-box/search-box';
|
||||
import { CoreFileComponent } from './file/file';
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
|
@ -34,7 +35,8 @@ import { CoreSearchBoxComponent } from './search-box/search-box';
|
|||
CoreIframeComponent,
|
||||
CoreProgressBarComponent,
|
||||
CoreEmptyBoxComponent,
|
||||
CoreSearchBoxComponent
|
||||
CoreSearchBoxComponent,
|
||||
CoreFileComponent
|
||||
],
|
||||
imports: [
|
||||
IonicModule,
|
||||
|
@ -49,7 +51,8 @@ import { CoreSearchBoxComponent } from './search-box/search-box';
|
|||
CoreIframeComponent,
|
||||
CoreProgressBarComponent,
|
||||
CoreEmptyBoxComponent,
|
||||
CoreSearchBoxComponent
|
||||
CoreSearchBoxComponent,
|
||||
CoreFileComponent
|
||||
]
|
||||
})
|
||||
export class CoreComponentsModule {}
|
||||
|
|
|
@ -0,0 +1,13 @@
|
|||
<a ion-item text-wrap class="item-media" (click)="download($event, true)" [class.item-2-button-right]="canDelete">
|
||||
<img [src]="fileIcon" alt="" role="presentation" item-start />
|
||||
<p>{{fileName}}</p>
|
||||
<div class="buttons" item-end>
|
||||
<button ion-button clear icon-only (click)="download($event)" *ngIf="!isDownloading && showDownload" [attr.aria-label]="'core.download' | translate">
|
||||
<ion-icon [name]="isDownloaded ? 'refresh' : 'cloud-download'"></ion-icon>
|
||||
</button>
|
||||
<button ion-button clear icon-only (click)="delete($event)" *ngIf="!isDownloading && canDelete" [attr.aria-label]="'core.delete' | translate" color="danger">
|
||||
<ion-icon name="trash"></ion-icon>
|
||||
</button>
|
||||
</div>
|
||||
<ion-spinner *ngIf="isDownloading" item-end></ion-spinner>
|
||||
</a>
|
|
@ -0,0 +1,28 @@
|
|||
core-loading {
|
||||
.mm-loading-container {
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
padding-top: 10px;
|
||||
clear: both;
|
||||
}
|
||||
|
||||
.mm-loading-content {
|
||||
padding-bottom: 1px; /* This makes height be real */
|
||||
}
|
||||
|
||||
&.mm-loading-noheight .mm-loading-content {
|
||||
height: auto;
|
||||
}
|
||||
}
|
||||
|
||||
.scroll-content > .padding > core-loading > .mm-loading-container,
|
||||
ion-content[padding] > .scroll-content > core-loading > .mm-loading-container,
|
||||
.mm-loading-center .mm-loading-container {
|
||||
display: table;
|
||||
|
||||
.mm-loading-spinner {
|
||||
display: table-cell;
|
||||
text-align: center;
|
||||
vertical-align: middle;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,291 @@
|
|||
// (C) Copyright 2015 Martin Dougiamas
|
||||
//
|
||||
// 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 { Component, Input, Output, OnInit, OnDestroy, EventEmitter } from '@angular/core';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { CoreAppProvider } from '../../providers/app';
|
||||
import { CoreEventsProvider } from '../../providers/events';
|
||||
import { CoreFileProvider } from '../../providers/file';
|
||||
import { CoreFilepoolProvider } from '../../providers/filepool';
|
||||
import { CoreSitesProvider } from '../../providers/sites';
|
||||
import { CoreDomUtilsProvider } from '../../providers/utils/dom';
|
||||
import { CoreMimetypeUtilsProvider } from '../../providers/utils/mimetype';
|
||||
import { CoreUtilsProvider } from '../../providers/utils/utils';
|
||||
import { CoreConstants } from '../../core/constants';
|
||||
|
||||
/**
|
||||
* Component to handle a remote file. Shows the file name, icon (depending on mimetype) and a button
|
||||
* to download/refresh it.
|
||||
*/
|
||||
@Component({
|
||||
selector: 'core-file',
|
||||
templateUrl: 'file.html'
|
||||
})
|
||||
export class CoreFileComponent implements OnInit, OnDestroy {
|
||||
@Input() file: any; // The file. Must have a property 'filename' and a 'fileurl' or 'url'
|
||||
@Input() component?: string; // Component the file belongs to.
|
||||
@Input() componentId?: string|number; // Component ID.
|
||||
@Input() timemodified?: number; // If set, the value will be used to check if the file is outdated.
|
||||
@Input() canDelete?: boolean|string; // Whether file can be deleted.
|
||||
@Input() alwaysDownload?: boolean|string; // Whether it should always display the refresh button when the file is downloaded.
|
||||
// Use it for files that you cannot determine if they're outdated or not.
|
||||
@Input() canDownload?: boolean|string = true; // Whether file can be downloaded.
|
||||
@Output() onDelete?: EventEmitter<string>; // Will notify when the delete button is clicked.
|
||||
|
||||
isDownloaded: boolean;
|
||||
isDownloading: boolean;
|
||||
showDownload: boolean;
|
||||
fileIcon: string;
|
||||
fileName: string;
|
||||
|
||||
protected fileUrl: string;
|
||||
protected siteId: string;
|
||||
protected fileSize: number;
|
||||
protected observer;
|
||||
|
||||
constructor(private translate: TranslateService, private sitesProvider: CoreSitesProvider, private utils: CoreUtilsProvider,
|
||||
private domUtils: CoreDomUtilsProvider, private filepoolProvider: CoreFilepoolProvider,
|
||||
private fileProvider: CoreFileProvider, private appProvider: CoreAppProvider,
|
||||
private mimeUtils: CoreMimetypeUtilsProvider, private eventsProvider: CoreEventsProvider) {
|
||||
this.onDelete = new EventEmitter();
|
||||
}
|
||||
|
||||
/**
|
||||
* Component being initialized.
|
||||
*/
|
||||
ngOnInit() {
|
||||
this.canDelete = this.utils.isTrueOrOne(this.canDelete);
|
||||
this.alwaysDownload = this.utils.isTrueOrOne(this.alwaysDownload);
|
||||
this.canDownload = this.utils.isTrueOrOne(this.canDownload);
|
||||
this.timemodified = this.timemodified || 0;
|
||||
|
||||
this.fileUrl = this.file.fileurl || this.file.url;
|
||||
this.siteId = this.sitesProvider.getCurrentSiteId();
|
||||
this.fileSize = this.file.filesize;
|
||||
this.fileName = this.file.filename;
|
||||
|
||||
if (this.file.isexternalfile) {
|
||||
this.alwaysDownload = true; // Always show the download button in external files.
|
||||
}
|
||||
|
||||
this.fileIcon = this.mimeUtils.getFileIcon(this.file.filename);
|
||||
|
||||
if (this.canDownload) {
|
||||
this.calculateState();
|
||||
|
||||
// Update state when receiving events about this file.
|
||||
this.filepoolProvider.getFileEventNameByUrl(this.siteId, this.fileUrl).then((eventName) => {
|
||||
this.observer = this.eventsProvider.on(eventName, () => {
|
||||
this.calculateState();
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience function to get the file state and set variables based on it.
|
||||
*
|
||||
* @return {Promise<void>} Promise resolved when state has been calculated.
|
||||
*/
|
||||
protected calculateState() : Promise<void> {
|
||||
return this.filepoolProvider.getFileStateByUrl(this.siteId, this.fileUrl, this.timemodified).then((state) => {
|
||||
let canDownload = this.sitesProvider.getCurrentSite().canDownloadFiles();
|
||||
|
||||
this.isDownloaded = state === CoreConstants.downloaded || state === CoreConstants.outdated;
|
||||
this.isDownloading = canDownload && state === CoreConstants.downloading;
|
||||
this.showDownload = canDownload && (state === CoreConstants.notDownloaded || state === CoreConstants.outdated ||
|
||||
(this.alwaysDownload && state === CoreConstants.downloaded));
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Download the file.
|
||||
*
|
||||
* @return {Promise<string>} Promise resolved when file is downloaded.
|
||||
*/
|
||||
protected downloadFile() : Promise<string> {
|
||||
if (!this.sitesProvider.getCurrentSite().canDownloadFiles()) {
|
||||
this.domUtils.showErrorModal('core.cannotdownloadfiles', true);
|
||||
return Promise.reject(null);
|
||||
}
|
||||
|
||||
this.isDownloading = true;
|
||||
return this.filepoolProvider.downloadUrl(this.siteId, this.fileUrl, false, this.component, this.componentId,
|
||||
this.timemodified, undefined, undefined, this.file).catch(() => {
|
||||
|
||||
// Call calculateState to make sure we have the right state.
|
||||
return this.calculateState().then(() => {
|
||||
if (this.isDownloaded) {
|
||||
return this.filepoolProvider.getInternalUrlByUrl(this.siteId, this.fileUrl);
|
||||
} else {
|
||||
return Promise.reject(null);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience function to open a file, downloading it if needed.
|
||||
*
|
||||
* @return {Promise<string>} Promise resolved when file is opened.
|
||||
*/
|
||||
protected openFile() : Promise<any> {
|
||||
let fixedUrl = this.sitesProvider.getCurrentSite().fixPluginfileURL(this.fileUrl),
|
||||
promise;
|
||||
|
||||
if (this.fileProvider.isAvailable()) {
|
||||
promise = Promise.resolve().then(() => {
|
||||
// The file system is available.
|
||||
let isWifi = !this.appProvider.isNetworkAccessLimited(),
|
||||
isOnline = this.appProvider.isOnline();
|
||||
|
||||
if (this.isDownloaded && !this.showDownload) {
|
||||
// File is downloaded, get the local file URL.
|
||||
return this.filepoolProvider.getUrlByUrl(this.siteId, this.fileUrl,
|
||||
this.component, this.componentId, this.timemodified, false, false, this.file);
|
||||
} else {
|
||||
if (!isOnline && !this.isDownloaded) {
|
||||
// Not downloaded and user is offline, reject.
|
||||
return Promise.reject(this.translate.instant('core.networkerrormsg'));
|
||||
}
|
||||
|
||||
let isDownloading = this.isDownloading;
|
||||
this.isDownloading = true; // This check could take a while, show spinner.
|
||||
return this.filepoolProvider.shouldDownloadBeforeOpen(fixedUrl, this.fileSize).then(() => {
|
||||
if (isDownloading) {
|
||||
// It's already downloading, stop.
|
||||
return;
|
||||
}
|
||||
// Download and then return the local URL.
|
||||
return this.downloadFile();
|
||||
}, () => {
|
||||
// Start the download if in wifi, but return the URL right away so the file is opened.
|
||||
if (isWifi && isOnline) {
|
||||
this.downloadFile();
|
||||
}
|
||||
|
||||
if (isDownloading || !this.isDownloaded || isOnline) {
|
||||
// Not downloaded or outdated and online, return the online URL.
|
||||
return fixedUrl;
|
||||
} else {
|
||||
// Outdated but offline, so we return the local URL.
|
||||
return this.filepoolProvider.getUrlByUrl(this.siteId, this.fileUrl,
|
||||
this.component, this.componentId, this.timemodified, false, false, this.file);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
} else {
|
||||
// Use the online URL.
|
||||
promise = Promise.resolve(fixedUrl);
|
||||
}
|
||||
|
||||
return promise.then((url) => {
|
||||
if (!url) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (url.indexOf('http') === 0) {
|
||||
return this.utils.openOnlineFile(url).catch((error) => {
|
||||
// Error opening the file, some apps don't allow opening online files.
|
||||
if (!this.fileProvider.isAvailable()) {
|
||||
return Promise.reject(error);
|
||||
} else if (this.isDownloading) {
|
||||
return Promise.reject(this.translate.instant('core.erroropenfiledownloading'));
|
||||
}
|
||||
|
||||
let subPromise;
|
||||
|
||||
if (status === CoreConstants.notDownloaded) {
|
||||
// File is not downloaded, download and then return the local URL.
|
||||
subPromise = this.downloadFile();
|
||||
} else {
|
||||
// File is outdated and can't be opened in online, return the local URL.
|
||||
subPromise = this.filepoolProvider.getInternalUrlByUrl(this.siteId, this.fileUrl);
|
||||
}
|
||||
|
||||
return subPromise.then((url) => {
|
||||
return this.utils.openFile(url);
|
||||
});
|
||||
});
|
||||
} else {
|
||||
return this.utils.openFile(url);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Download a file and, optionally, open it afterwards.
|
||||
*
|
||||
* @param {Event} e Click event.
|
||||
* @param {boolean} openAfterDownload Whether the file should be opened after download.
|
||||
*/
|
||||
download(e: Event, openAfterDownload: boolean) : void {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
let promise;
|
||||
|
||||
if (this.isDownloading && !openAfterDownload) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.appProvider.isOnline() && (!openAfterDownload || (openAfterDownload && !this.isDownloaded))) {
|
||||
this.domUtils.showErrorModal('core.networkerrormsg', true);
|
||||
return;
|
||||
}
|
||||
|
||||
if (openAfterDownload) {
|
||||
// File needs to be opened now.
|
||||
this.openFile().catch((error) => {
|
||||
this.domUtils.showErrorModalDefault(error, 'core.errordownloading', true);
|
||||
});
|
||||
} else {
|
||||
// File doesn't need to be opened (it's a prefetch). Show confirm modal if file size is defined and it's big.
|
||||
promise = this.fileSize ? this.domUtils.confirmDownloadSize({size: this.fileSize, total: true}) : Promise.resolve();
|
||||
promise.then(() => {
|
||||
// User confirmed, add the file to queue.
|
||||
this.filepoolProvider.invalidateFileByUrl(this.siteId, this.fileUrl).finally(() => {
|
||||
this.isDownloading = true;
|
||||
this.filepoolProvider.addToQueueByUrl(this.siteId, this.fileUrl, this.component,
|
||||
this.componentId, this.timemodified, undefined, undefined, 0, this.file).catch((error) => {
|
||||
this.domUtils.showErrorModalDefault(error, 'core.errordownloading', true);
|
||||
this.calculateState();
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Delete the file.
|
||||
*
|
||||
* @param {Event} e Click event.
|
||||
*/
|
||||
deleteFile(e: Event) : void {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
if (this.canDelete) {
|
||||
this.onDelete.emit();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Component destroyed.
|
||||
*/
|
||||
ngOnDestroy() {
|
||||
this.observer && this.observer.off();
|
||||
}
|
||||
}
|
|
@ -25,7 +25,7 @@
|
|||
<p class="item-heading">{{ 'core.teachers' | translate }}</p>
|
||||
<p *ngFor="let contact of course.contacts">{{contact.fullname}}</p>
|
||||
</a>
|
||||
<!-- <mm-file ng-repeat="file in course.overviewfiles" file="file" component="{{component}}" component-id="{{course.id}}"></mm-file> -->
|
||||
<core-file *ngFor="let file of course.overviewfiles" [file]="file" [component]="component" [componentId]="course.id"></core-file>
|
||||
<div *ngIf="!isEnrolled">
|
||||
<ion-item text-wrap *ngFor="let instance of selfEnrolInstances">
|
||||
<p class="item-heading">{{ instance.name }}</p>
|
||||
|
|
|
@ -350,7 +350,6 @@ export class FileMock extends File {
|
|||
(<any>navigator).webkitPersistentStorage.requestQuota(500 * 1024 * 1024, (granted) => {
|
||||
window.requestFileSystem(LocalFileSystem.PERSISTENT, granted, (entry) => {
|
||||
basePath = entry.root.toURL();
|
||||
// this.fileProvider.setHTMLBasePath(basePath);
|
||||
resolve(basePath);
|
||||
}, reject);
|
||||
}, reject);
|
||||
|
|
|
@ -98,7 +98,7 @@ export class CoreFileProvider {
|
|||
* @return {boolean} Whether the plugin is available.
|
||||
*/
|
||||
isAvailable() : boolean {
|
||||
return typeof window.resolveLocalFileSystemURL !== 'undefined' && typeof FileTransfer !== 'undefined';
|
||||
return typeof window.resolveLocalFileSystemURL !== 'undefined';
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -327,7 +327,7 @@ export class CoreFilepoolProvider {
|
|||
component: component,
|
||||
componentId: componentId || ''
|
||||
};
|
||||
return db.insertOrUpdateRecord(this.LINKS_TABLE, newEntry, null);
|
||||
return db.insertOrUpdateRecord(this.LINKS_TABLE, newEntry, undefined);
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -410,7 +410,7 @@ export class CoreFilepoolProvider {
|
|||
* @return {Promise<any>} Promise resolved when the file is downloaded.
|
||||
*/
|
||||
protected addToQueue(siteId: string, fileId: string, url: string, priority: number, revision: number, timemodified: number,
|
||||
filePath: string, options: any = {}, link?: any) : Promise<any> {
|
||||
filePath: string, onProgress?: (event: any) => any, options: any = {}, link?: any) : Promise<any> {
|
||||
this.logger.debug(`Adding ${fileId} to the queue`);
|
||||
|
||||
return this.appDB.insertRecord(this.QUEUE_TABLE, {
|
||||
|
@ -429,7 +429,7 @@ export class CoreFilepoolProvider {
|
|||
// Check if the queue is running.
|
||||
this.checkQueueProcessing();
|
||||
this.notifyFileDownloading(siteId, fileId);
|
||||
return this.getQueuePromise(siteId, fileId);
|
||||
return this.getQueuePromise(siteId, fileId, true, onProgress);
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -479,8 +479,7 @@ export class CoreFilepoolProvider {
|
|||
|
||||
// Retrieve the queue deferred now if it exists to prevent errors if file is removed from queue
|
||||
// while we're checking if the file is in queue.
|
||||
queueDeferred = this.getQueueDeferred(siteId, fileId, false);
|
||||
queueDeferred.onProgress = onProgress;
|
||||
queueDeferred = this.getQueueDeferred(siteId, fileId, false, onProgress);
|
||||
|
||||
return this.hasFileInQueue(siteId, fileId).then((entry: CoreFilepoolQueueEntry) => {
|
||||
let foundLink = false,
|
||||
|
@ -530,7 +529,7 @@ export class CoreFilepoolProvider {
|
|||
// Update only when required.
|
||||
this.logger.debug(`Updating file ${fileId} which is already in queue`);
|
||||
return this.appDB.updateRecords(this.QUEUE_TABLE, newData, primaryKey).then(() => {
|
||||
return this.getQueuePromise(siteId, fileId);
|
||||
return this.getQueuePromise(siteId, fileId, true, onProgress);
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -540,14 +539,17 @@ export class CoreFilepoolProvider {
|
|||
// might have finished now and the deferred wouldn't be in the array anymore.
|
||||
return queueDeferred.promise;
|
||||
} else {
|
||||
return this.getQueuePromise(siteId, fileId);
|
||||
// Create a new deferred and return its promise.
|
||||
return this.getQueuePromise(siteId, fileId, true, onProgress);
|
||||
}
|
||||
} else {
|
||||
return this.addToQueue(siteId, fileId, fileUrl, priority, revision, timemodified, filePath, options, link);
|
||||
return this.addToQueue(
|
||||
siteId, fileId, fileUrl, priority, revision, timemodified, filePath, onProgress, options, link);
|
||||
}
|
||||
}, () => {
|
||||
// Unsure why we could not get the record, let's add to the queue anyway.
|
||||
return this.addToQueue(siteId, fileId, fileUrl, priority, revision, timemodified, filePath, options, link);
|
||||
return this.addToQueue(
|
||||
siteId, fileId, fileUrl, priority, revision, timemodified, filePath, onProgress, options, link);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -596,15 +598,17 @@ export class CoreFilepoolProvider {
|
|||
// Check if the file should be downloaded.
|
||||
if (sizeUnknown) {
|
||||
if (downloadUnknown && isWifi) {
|
||||
return this.addToQueueByUrl(siteId, fileUrl, component, componentId, timemodified, null, null, 0, options);
|
||||
return this.addToQueueByUrl(
|
||||
siteId, fileUrl, component, componentId, timemodified, undefined, undefined, 0, options);
|
||||
}
|
||||
} else if (size <= this.DOWNLOAD_THRESHOLD || (isWifi && size <= this.WIFI_DOWNLOAD_THRESHOLD)) {
|
||||
return this.addToQueueByUrl(siteId, fileUrl, component, componentId, timemodified, null, null, 0, options);
|
||||
return this.addToQueueByUrl(
|
||||
siteId, fileUrl, component, componentId, timemodified, undefined, undefined, 0, options);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
// No need to check size, just add it to the queue.
|
||||
return this.addToQueueByUrl(siteId, fileUrl, component, componentId, timemodified, null, null, 0, options);
|
||||
return this.addToQueueByUrl(siteId, fileUrl, component, componentId, timemodified, undefined, undefined, 0, options);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -817,9 +821,11 @@ export class CoreFilepoolProvider {
|
|||
}
|
||||
|
||||
if (prefetch) {
|
||||
promises.push(this.addToQueueByUrl(siteId, url, component, componentId, timemodified, null, null, 0, options));
|
||||
promises.push(this.addToQueueByUrl(
|
||||
siteId, url, component, componentId, timemodified, undefined, undefined, 0, options));
|
||||
} else {
|
||||
promises.push(this.downloadUrl(siteId, url, ignoreStale, component, componentId, timemodified, null, null, options));
|
||||
promises.push(this.downloadUrl(
|
||||
siteId, url, ignoreStale, component, componentId, timemodified, undefined, undefined, options));
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -901,7 +907,7 @@ export class CoreFilepoolProvider {
|
|||
|
||||
if (prefetch) {
|
||||
promise = this.addToQueueByUrl(
|
||||
siteId, fileUrl, component, componentId, file.timemodified, path, null, 0, options);
|
||||
siteId, fileUrl, component, componentId, file.timemodified, path, undefined, 0, options);
|
||||
} else {
|
||||
promise = this.downloadUrl(
|
||||
siteId, fileUrl, false, component, componentId, file.timemodified, onFileProgress, path, options);
|
||||
|
@ -1816,9 +1822,10 @@ export class CoreFilepoolProvider {
|
|||
* @param {string} siteId The site ID.
|
||||
* @param {string} fileId The file ID.
|
||||
* @param {boolean} [create=true] True if it should create a new deferred if it doesn't exist.
|
||||
* @param {Function} [onProgress] Function to call on progress.
|
||||
* @return {any} Deferred.
|
||||
*/
|
||||
protected getQueueDeferred(siteId: string, fileId: string, create = true): any {
|
||||
protected getQueueDeferred(siteId: string, fileId: string, create = true, onProgress?: (event: any) => any): any {
|
||||
if (!this.queueDeferreds[siteId]) {
|
||||
if (!create) {
|
||||
return;
|
||||
|
@ -1831,6 +1838,11 @@ export class CoreFilepoolProvider {
|
|||
}
|
||||
this.queueDeferreds[siteId][fileId] = this.utils.promiseDefer();
|
||||
}
|
||||
|
||||
if (onProgress) {
|
||||
this.queueDeferreds[siteId][fileId].onProgress = onProgress;
|
||||
}
|
||||
|
||||
return this.queueDeferreds[siteId][fileId];
|
||||
}
|
||||
|
||||
|
@ -1854,10 +1866,11 @@ export class CoreFilepoolProvider {
|
|||
* @param {string} siteId The site ID.
|
||||
* @param {string} fileId The file ID.
|
||||
* @param {boolean} [create=true] True if it should create a new promise if it doesn't exist.
|
||||
* @param {Function} [onProgress] Function to call on progress.
|
||||
* @return {Promise<any>} Promise.
|
||||
*/
|
||||
protected getQueuePromise(siteId: string, fileId: string, create = true) : Promise<any> {
|
||||
return this.getQueueDeferred(siteId, fileId, create).promise;
|
||||
protected getQueuePromise(siteId: string, fileId: string, create = true, onProgress?: (event: any) => any) : Promise<any> {
|
||||
return this.getQueueDeferred(siteId, fileId, create, onProgress).promise;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -2248,7 +2261,9 @@ export class CoreFilepoolProvider {
|
|||
|
||||
promise.then(() => {
|
||||
// All good, we schedule next execution.
|
||||
setTimeout(this.processQueue, this.QUEUE_PROCESS_INTERVAL);
|
||||
setTimeout(() => {
|
||||
this.processQueue();
|
||||
}, this.QUEUE_PROCESS_INTERVAL);
|
||||
|
||||
}, (error) => {
|
||||
|
||||
|
@ -2270,7 +2285,7 @@ export class CoreFilepoolProvider {
|
|||
* @return {Promise} Resolved on success. Rejected on failure.
|
||||
*/
|
||||
protected processImportantQueueItem() : Promise<any> {
|
||||
return this.appDB.getRecords(this.QUEUE_TABLE, null, 'priority DESC, added ASC', null, 0, 1).then((items) => {
|
||||
return this.appDB.getRecords(this.QUEUE_TABLE, undefined, 'priority DESC, added ASC', undefined, 0, 1).then((items) => {
|
||||
let item = items.pop();
|
||||
if (!item) {
|
||||
return Promise.reject(this.ERR_QUEUE_IS_EMPTY);
|
||||
|
@ -2290,16 +2305,17 @@ export class CoreFilepoolProvider {
|
|||
* @return {Promise<any>} Resolved on success. Rejected on failure.
|
||||
*/
|
||||
protected processQueueItem(item: CoreFilepoolQueueEntry) : Promise<any> {
|
||||
// Cast optional fields to undefined instead of null.
|
||||
let siteId = item.siteId,
|
||||
fileId = item.fileId,
|
||||
fileUrl = item.url,
|
||||
options = {
|
||||
revision: item.revision,
|
||||
timemodified: item.timemodified,
|
||||
isexternalfile: item.isexternalfile,
|
||||
repositorytype: item.repositorytype
|
||||
revision: item.revision || undefined,
|
||||
timemodified: item.timemodified || undefined,
|
||||
isexternalfile: item.isexternalfile || undefined,
|
||||
repositorytype: item.repositorytype || undefined
|
||||
},
|
||||
filePath = item.path,
|
||||
filePath = item.path || undefined,
|
||||
links = item.links || [];
|
||||
|
||||
this.logger.debug('Processing queue item: ' + siteId + ', ' + fileId);
|
||||
|
@ -2335,7 +2351,7 @@ export class CoreFilepoolProvider {
|
|||
// Whoops, we have an error...
|
||||
let dropFromQueue = false;
|
||||
|
||||
if (typeof errorObject != 'undefined' && errorObject.source === fileUrl) {
|
||||
if (errorObject && errorObject.source === fileUrl) {
|
||||
// This is most likely a FileTransfer error.
|
||||
if (errorObject.code === 1) { // FILE_NOT_FOUND_ERR.
|
||||
// The file was not found, most likely a 404, we remove from queue.
|
||||
|
|
|
@ -185,7 +185,7 @@ export class CoreMimetypeUtilsProvider {
|
|||
}
|
||||
}
|
||||
|
||||
return 'img/files/' + icon + '-64.png';
|
||||
return 'assets/img/files/' + icon + '-64.png';
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -194,7 +194,7 @@ export class CoreMimetypeUtilsProvider {
|
|||
* @return {string} The path to a folder icon.
|
||||
*/
|
||||
getFolderIcon() : string {
|
||||
return 'img/files/folder-64.png';
|
||||
return 'assets/img/files/folder-64.png';
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -171,3 +171,10 @@ $item-wp-avatar-size: 54px;
|
|||
|
||||
@import "roboto";
|
||||
@import "noto-sans";
|
||||
|
||||
// Moodle Mobile variables
|
||||
// --------------------------------------------------
|
||||
|
||||
// Small avatar ideal for icons.
|
||||
$item-media-width: 32px !default;
|
||||
$item-media-height: 32px !default;
|
||||
|
|