Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add rate to files and queue #1015

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/uploading.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ In addition to the file list, the queue tracks properties that indicate the prog
| queue.size | `number` – Total size of all files currently being uploaded in bytes. |
| queue.loaded | `number` – Number of bytes that have been uploaded to the server. |
| queue.progress | `number` – Current progress of all uploads, as a percentage in the range of 0 to 100. |
| queue.rate | `number` – Current time in ms it is taking to upload 1 byte. |

```hbs
{{#if queue.files.length}}
Expand Down
11 changes: 11 additions & 0 deletions ember-file-upload/src/queue.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,17 @@ export class Queue {
return [...this.#distinctFiles.values()];
}

/**
* The current time in ms it is taking to upload 1 byte.
*
* @defaultValue 0
*/
get rate(): number {
return this.files.reduce((acc, { rate }) => {
return acc + rate;
}, 0);
}

/**
* The total size of all files currently being uploaded in bytes.
*
Expand Down
11 changes: 11 additions & 0 deletions ember-file-upload/src/services/file-queue.ts
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,17 @@ export default class FileQueueService extends Service {
}, []);
}

/**
* The current time in ms it is taking to upload 1 byte.
*
* @defaultValue 0
*/
get rate(): number {
return this.files.reduce((acc, { rate }) => {
return acc + rate;
}, 0);
}

/**
* The total size of all files currently being uploaded in bytes.
*
Expand Down
16 changes: 16 additions & 0 deletions ember-file-upload/src/system/upload.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { assert } from '@ember/debug';
import { next } from '@ember/runloop';
import HTTPRequest from './http-request.ts';
import RSVP from 'rsvp';
import { waitForPromise } from '@ember/test-waiters';
Expand Down Expand Up @@ -65,6 +66,8 @@ export function onloadstart(
// The correct should be returned while progress
file.size = Math.max(file.size, event.total);
file.progress = (file.loaded / file.size) * 100;
file.bytesWhenProgressLastUpdated = event.loaded;
file.timestampWhenProgressLastUpdated = new Date().getTime();
}

export function onprogress(
Expand All @@ -88,6 +91,13 @@ export function onprogress(
}
file.loaded = Math.max(loaded, file.loaded);
file.progress = (file.loaded / file.size) * 100;

const updatedTime = new Date().getTime();

next(() => {
file.bytesWhenProgressLastUpdated = file.loaded;
file.timestampWhenProgressLastUpdated = updatedTime;
});
}

export function onloadend(
Expand All @@ -100,6 +110,8 @@ export function onloadend(
file.loaded = file.size;
file.progress = (file.loaded / file.size) * 100;
file.isUploadComplete = true;
file.bytesWhenProgressLastUpdated = 0;
file.timestampWhenProgressLastUpdated = 0;
}

export function upload(
Expand Down Expand Up @@ -142,10 +154,14 @@ export function upload(
request.onloadend = (event) => onloadend(file, event);

request.ontimeout = () => {
file.bytesWhenProgressLastUpdated = 0;
file.timestampWhenProgressLastUpdated = 0;
file.state = FileState.TimedOut;
file.queue?.flush();
};
request.onabort = () => {
file.bytesWhenProgressLastUpdated = 0;
file.timestampWhenProgressLastUpdated = 0;
file.state = FileState.Aborted;
file.queue?.flush();
};
Expand Down
24 changes: 24 additions & 0 deletions ember-file-upload/src/upload-file.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,19 @@ export class UploadFile {
this.#name = value;
}

/** The current speed in ms that it takes to upload one byte */
get rate() {
const updatedTime = new Date().getTime();
const timeElapsedSinceLastUpdate =
updatedTime - this.timestampWhenProgressLastUpdated;

const bytesTransferredSinceLastUpdate =
this.loaded - this.bytesWhenProgressLastUpdated;

// Divide by elapsed time to get bytes per millisecond
return bytesTransferredSinceLastUpdate / timeElapsedSinceLastUpdate;
}

#size = 0;

/** The size of the file in bytes. */
Expand Down Expand Up @@ -79,6 +92,11 @@ export class UploadFile {
return this.type.split('/').slice(-1)[0] ?? '';
}

/**
* Tracks the number of bytes that had been uploaded when progress values last changed.
*/
@tracked bytesWhenProgressLastUpdated = 0;

/** The number of bytes that have been uploaded to the server */
@tracked loaded = 0;

Expand Down Expand Up @@ -138,6 +156,12 @@ export class UploadFile {
// */
// source?: FileSource;

/**
* The timestamp of when the progress last updated in milliseconds. Used to calculate the time
* that has elapsed.
*/
@tracked timestampWhenProgressLastUpdated = 0;

/**
* Upload file with `application/octet-stream` content type.
*
Expand Down
14 changes: 13 additions & 1 deletion test-app/tests/integration/progress-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,10 @@ import { setupRenderingTest } from 'ember-qunit';
import { render } from '@ember/test-helpers';
import { hbs } from 'ember-cli-htmlbars';
import { selectFiles } from 'ember-file-upload/test-support';
import { type MirageTestContext, setupMirage } from 'ember-cli-mirage/test-support';
import {
type MirageTestContext,
setupMirage,
} from 'ember-cli-mirage/test-support';
import { TrackedArray } from 'tracked-built-ins';
import { type Asset } from 'test-app/components/demo-upload';
import type Owner from '@ember/owner';
Expand Down Expand Up @@ -66,13 +69,16 @@ module('Integration | progress', function (hooks) {
selectFiles('#upload-photo', file, file, file);
}

assert.strictEqual(queue.rate, 0, 'rate should be 0 before uploads begin');

await firstFile.promise;

assert.strictEqual(
queue.progress,
33,
'first file uploaded - queue progress 33%',
);
assert.true(queue.rate > 0, 'rate should be > 0 when uploading');
assert.strictEqual(
queue.files.length,
3,
Expand All @@ -86,6 +92,7 @@ module('Integration | progress', function (hooks) {
66,
'second file uploaded - queue progress 66%',
);
assert.true(queue.rate > 0, 'rate should be > 0 when uploading');
assert.strictEqual(
queue.files.length,
3,
Expand All @@ -99,6 +106,11 @@ module('Integration | progress', function (hooks) {
0,
'third file uploaded - progress is back to 0% since the queue has been flushed',
);
assert.strictEqual(
queue.rate,
0,
'rate should be 0 after all uploads finish',
);
assert.strictEqual(
queue.files.length,
0,
Expand Down
15 changes: 14 additions & 1 deletion test-app/tests/unit/upload-file-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,10 @@ import { setupTest } from 'ember-qunit';
import { uploadHandler } from 'ember-file-upload';
import { UploadFile, FileSource } from 'ember-file-upload';

import { type MirageTestContext, setupMirage } from 'ember-cli-mirage/test-support';
import {
type MirageTestContext,
setupMirage,
} from 'ember-cli-mirage/test-support';

module('Unit | UploadFile', function (hooks) {
setupTest(hooks);
Expand Down Expand Up @@ -85,4 +88,14 @@ module('Unit | UploadFile', function (hooks) {
assert.strictEqual(file.size, 13);
assert.strictEqual(file.file.size, 9);
});

test('it correctly calculates rate', function (assert) {
const file = UploadFile.fromBlob(new Blob(['test text'], { type: 'text' }));
const twoSecondsAgo = new Date().getTime() - 2000;
file.timestampWhenProgressLastUpdated = twoSecondsAgo;
file.bytesWhenProgressLastUpdated = 5000;
file.loaded = 20000;
file.size = 500000;
assert.strictEqual(Math.ceil(file.rate), 8);
});
});
1 change: 1 addition & 0 deletions website/app/components/demo-upload.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
Drop to upload
{{else if queue.files.length}}
Uploading {{queue.files.length}} files. ({{queue.progress}}%)
Estimated time remaining: {{this.estimatedTimeRemaining queue.loaded queue.rate queue.size}}
{{else if dropzone.supported}}
Or drag and drop files here to upload them
{{/if}}
Expand Down
21 changes: 21 additions & 0 deletions website/app/components/demo-upload.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,27 @@ export default class DemoUploadComponent extends Component {
this.simulateUpload.perform(file);
}

estimatedTimeRemaining = (loaded, rate, size) => {
const bytesLoaded = loaded ?? 0;
const fileSize = size ?? 0;

if (bytesLoaded) {
const seconds = (fileSize - bytesLoaded) / (rate || 1);

const date = new Date(seconds * 1000);
const days = Math.floor(seconds / 86400);

const daysRem = days > 0 ? `${days}d ` : '';
const hoursRem = String(date.getUTCHours()).padStart(2, '0');
const minutesRem = String(date.getUTCMinutes()).padStart(2, '0');
const secondsRem = String(date.getUTCSeconds()).padStart(2, '0');

return `${daysRem}${hoursRem}:${minutesRem}:${secondsRem}`;
}

return '?';
};

@task({ enqueue: true })
*simulateUpload(file) {
file.state = FileState.Uploading;
Expand Down