Merge pull request #8061 from deepak1556/resume_download_api

session: api to resume canceled downloads from previous session
This commit is contained in:
Kevin Sawicki 2016-12-09 09:39:14 -08:00 коммит произвёл GitHub
Родитель 54d27a390b d705f4cbac
Коммит 952e3bac2c
9 изменённых файлов: 245 добавлений и 15 удалений

Просмотреть файл

@ -146,6 +146,10 @@ const GURL& DownloadItem::GetURL() const {
return download_item_->GetURL();
}
const std::vector<GURL>& DownloadItem::GetURLChain() const {
return download_item_->GetUrlChain();
}
content::DownloadItem::DownloadState DownloadItem::GetState() const {
return download_item_->GetState();
}
@ -162,6 +166,18 @@ base::FilePath DownloadItem::GetSavePath() const {
return save_path_;
}
std::string DownloadItem::GetLastModifiedTime() const {
return download_item_->GetLastModifiedTime();
}
std::string DownloadItem::GetETag() const {
return download_item_->GetETag();
}
double DownloadItem::GetStartTime() const {
return download_item_->GetStartTime().ToDoubleT();
}
// static
void DownloadItem::BuildPrototype(v8::Isolate* isolate,
v8::Local<v8::FunctionTemplate> prototype) {
@ -180,10 +196,14 @@ void DownloadItem::BuildPrototype(v8::Isolate* isolate,
.SetMethod("getFilename", &DownloadItem::GetFilename)
.SetMethod("getContentDisposition", &DownloadItem::GetContentDisposition)
.SetMethod("getURL", &DownloadItem::GetURL)
.SetMethod("getURLChain", &DownloadItem::GetURLChain)
.SetMethod("getState", &DownloadItem::GetState)
.SetMethod("isDone", &DownloadItem::IsDone)
.SetMethod("setSavePath", &DownloadItem::SetSavePath)
.SetMethod("getSavePath", &DownloadItem::GetSavePath);
.SetMethod("getSavePath", &DownloadItem::GetSavePath)
.SetMethod("getLastModifiedTime", &DownloadItem::GetLastModifiedTime)
.SetMethod("getETag", &DownloadItem::GetETag)
.SetMethod("getStartTime", &DownloadItem::GetStartTime);
}
// static

Просмотреть файл

@ -6,6 +6,7 @@
#define ATOM_BROWSER_API_ATOM_API_DOWNLOAD_ITEM_H_
#include <string>
#include <vector>
#include "atom/browser/api/trackable_object.h"
#include "base/files/file_path.h"
@ -38,10 +39,14 @@ class DownloadItem : public mate::TrackableObject<DownloadItem>,
std::string GetFilename() const;
std::string GetContentDisposition() const;
const GURL& GetURL() const;
const std::vector<GURL>& GetURLChain() const;
content::DownloadItem::DownloadState GetState() const;
bool IsDone() const;
void SetSavePath(const base::FilePath& path);
base::FilePath GetSavePath() const;
std::string GetLastModifiedTime() const;
std::string GetETag() const;
double GetStartTime() const;
protected:
DownloadItem(v8::Isolate* isolate, content::DownloadItem* download_item);

Просмотреть файл

@ -34,6 +34,7 @@
#include "chrome/common/pref_names.h"
#include "components/prefs/pref_service.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/download_manager_delegate.h"
#include "content/public/browser/storage_partition.h"
#include "native_mate/dictionary.h"
#include "native_mate/object_template_builder.h"
@ -331,6 +332,25 @@ void OnClearStorageDataDone(const base::Closure& callback) {
callback.Run();
}
void DownloadIdCallback(content::DownloadManager* download_manager,
const base::FilePath& path,
const std::vector<GURL>& url_chain,
const std::string& mime_type,
int64_t offset,
int64_t length,
const std::string& last_modified,
const std::string& etag,
const base::Time& start_time,
uint32_t id) {
download_manager->CreateDownloadItem(
base::GenerateGUID(), id, path, path, url_chain, GURL(), GURL(), GURL(),
GURL(), mime_type, mime_type, start_time, base::Time(), etag,
last_modified, offset, length, std::string(),
content::DownloadItem::INTERRUPTED,
content::DownloadDangerType::DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS,
content::DOWNLOAD_INTERRUPT_REASON_NETWORK_TIMEOUT, false);
}
} // namespace
Session::Session(v8::Isolate* isolate, AtomBrowserContext* browser_context)
@ -357,10 +377,10 @@ void Session::OnDownloadCreated(content::DownloadManager* manager,
v8::Locker locker(isolate());
v8::HandleScope handle_scope(isolate());
bool prevent_default = Emit(
"will-download",
DownloadItem::Create(isolate(), item),
item->GetWebContents());
auto handle = DownloadItem::Create(isolate(), item);
if (item->GetState() == content::DownloadItem::INTERRUPTED)
handle->SetSavePath(item->GetTargetFilePath());
bool prevent_default = Emit("will-download", handle, item->GetWebContents());
if (prevent_default) {
item->Cancel(true);
item->Remove();
@ -520,6 +540,37 @@ void Session::GetBlobData(
callback));
}
void Session::CreateInterruptedDownload(const mate::Dictionary& options) {
int64_t offset = 0, length = 0;
double start_time = 0.0;
std::string mime_type, last_modified, etag;
base::FilePath path;
std::vector<GURL> url_chain;
options.Get("path", &path);
options.Get("urlChain", &url_chain);
options.Get("mimeType", &mime_type);
options.Get("offset", &offset);
options.Get("length", &length);
options.Get("lastModified", &last_modified);
options.Get("eTag", &etag);
options.Get("startTime", &start_time);
if (path.empty() || url_chain.empty() || length == 0) {
isolate()->ThrowException(v8::Exception::Error(mate::StringToV8(
isolate(), "Must pass non-empty path, urlChain and length.")));
return;
}
if (offset >= length) {
isolate()->ThrowException(v8::Exception::Error(mate::StringToV8(
isolate(), "Must pass an offset value less than length.")));
return;
}
auto download_manager =
content::BrowserContext::GetDownloadManager(browser_context());
download_manager->GetDelegate()->GetNextId(base::Bind(
&DownloadIdCallback, download_manager, path, url_chain, mime_type, offset,
length, last_modified, etag, base::Time::FromDoubleT(start_time)));
}
v8::Local<v8::Value> Session::Cookies(v8::Isolate* isolate) {
if (cookies_.IsEmpty()) {
auto handle = Cookies::Create(isolate, browser_context());
@ -603,6 +654,8 @@ void Session::BuildPrototype(v8::Isolate* isolate,
.SetMethod("setUserAgent", &Session::SetUserAgent)
.SetMethod("getUserAgent", &Session::GetUserAgent)
.SetMethod("getBlobData", &Session::GetBlobData)
.SetMethod("createInterruptedDownload",
&Session::CreateInterruptedDownload)
.SetProperty("cookies", &Session::Cookies)
.SetProperty("protocol", &Session::Protocol)
.SetProperty("webRequest", &Session::WebRequest);

Просмотреть файл

@ -79,6 +79,7 @@ class Session: public mate::TrackableObject<Session>,
std::string GetUserAgent();
void GetBlobData(const std::string& uuid,
const AtomBlobReader::CompletionCallback& callback);
void CreateInterruptedDownload(const mate::Dictionary& options);
v8::Local<v8::Value> Cookies(v8::Isolate* isolate);
v8::Local<v8::Value> Protocol(v8::Isolate* isolate);
v8::Local<v8::Value> WebRequest(v8::Isolate* isolate);

Просмотреть файл

@ -146,3 +146,23 @@ header.
#### `downloadItem.getState()`
Returns `String` - The current state. Can be `progressing`, `completed`, `cancelled` or `interrupted`.
**Note:** The following methods are useful specifically to resume a
`cancelled` item when session is restarted.
#### `downloadItem.getURLChain()`
Returns `String[]` - The complete url chain of the item including any redirects.
#### `downloadItem.getLastModifiedTime()`
Returns `String` - Last-Modified header value.
#### `downloadItem.getETag()`
Returns `String` - ETag header value.
#### `downloadItem.getStartTime()`
Returns `Double` - Number of seconds since the UNIX epoch when the download was
started.

Просмотреть файл

@ -344,6 +344,25 @@ Returns `String` - The user agent for this session.
Returns `Blob` - The blob data associated with the `identifier`.
#### `ses.createInterruptedDownload(options)`
* `options` Object
* `path` String - Absolute path of the download.
* `urlChain` String[] - Complete URL chain for the download.
* `mimeType` String (optional)
* `offset` Integer - Start range for the download.
* `length` Integer - Total length of the download.
* `lastModified` String - Last-Modified header value.
* `eTag` String - ETag header value.
* `startTime` Double (optional) - Time when download was started in
number of seconds since UNIX epoch.
Allows resuming `cancelled` or `interrupted` downloads from previous `Session`.
The API will generate a [DownloadItem](download-item.md) that can be accessed with the [will-download](#event-will-download)
event. The [DownloadItem](download-item.md) will not have any `WebContents` associated with it and
the initial state will be `interrupted`. The download will start only when the
`resume` API is called on the [DownloadItem](download-item.md).
### Instance Properties
The following properties are available on instances of `Session`:

Просмотреть файл

@ -3,6 +3,7 @@ const http = require('http')
const https = require('https')
const path = require('path')
const fs = require('fs')
const send = require('send')
const {closeWindow} = require('./window-helpers')
const {ipcRenderer, remote} = require('electron')
@ -288,7 +289,9 @@ describe('session module', function () {
res.end(mockPDF)
downloadServer.close()
})
var assertDownload = function (event, state, url, mimeType, receivedBytes, totalBytes, disposition, filename, port, savePath) {
var assertDownload = function (event, state, url, mimeType,
receivedBytes, totalBytes, disposition,
filename, port, savePath) {
assert.equal(state, 'completed')
assert.equal(filename, 'mock.pdf')
assert.equal(savePath, path.join(__dirname, 'fixtures', 'mock.pdf'))
@ -306,8 +309,12 @@ describe('session module', function () {
var port = downloadServer.address().port
ipcRenderer.sendSync('set-download-option', false, false)
w.loadURL(url + ':' + port)
ipcRenderer.once('download-done', function (event, state, url, mimeType, receivedBytes, totalBytes, disposition, filename, savePath) {
assertDownload(event, state, url, mimeType, receivedBytes, totalBytes, disposition, filename, port, savePath)
ipcRenderer.once('download-done', function (event, state, url,
mimeType, receivedBytes,
totalBytes, disposition,
filename, savePath) {
assertDownload(event, state, url, mimeType, receivedBytes,
totalBytes, disposition, filename, port, savePath)
done()
})
})
@ -322,8 +329,12 @@ describe('session module', function () {
webview.addEventListener('did-finish-load', function () {
webview.downloadURL(url + ':' + port + '/')
})
ipcRenderer.once('download-done', function (event, state, url, mimeType, receivedBytes, totalBytes, disposition, filename, savePath) {
assertDownload(event, state, url, mimeType, receivedBytes, totalBytes, disposition, filename, port, savePath)
ipcRenderer.once('download-done', function (event, state, url,
mimeType, receivedBytes,
totalBytes, disposition,
filename, savePath) {
assertDownload(event, state, url, mimeType, receivedBytes,
totalBytes, disposition, filename, port, savePath)
document.body.removeChild(webview)
done()
})
@ -336,7 +347,10 @@ describe('session module', function () {
var port = downloadServer.address().port
ipcRenderer.sendSync('set-download-option', true, false)
w.loadURL(url + ':' + port + '/')
ipcRenderer.once('download-done', function (event, state, url, mimeType, receivedBytes, totalBytes, disposition, filename) {
ipcRenderer.once('download-done', function (event, state, url,
mimeType, receivedBytes,
totalBytes, disposition,
filename) {
assert.equal(state, 'cancelled')
assert.equal(filename, 'mock.pdf')
assert.equal(mimeType, 'application/pdf')
@ -356,7 +370,10 @@ describe('session module', function () {
var port = downloadServer.address().port
ipcRenderer.sendSync('set-download-option', true, false)
w.loadURL(url + ':' + port + '/?testFilename')
ipcRenderer.once('download-done', function (event, state, url, mimeType, receivedBytes, totalBytes, disposition, filename) {
ipcRenderer.once('download-done', function (event, state, url,
mimeType, receivedBytes,
totalBytes, disposition,
filename) {
assert.equal(state, 'cancelled')
assert.equal(filename, 'download.pdf')
assert.equal(mimeType, 'application/pdf')
@ -565,4 +582,83 @@ describe('session module', function () {
w.loadURL(url)
})
})
describe('ses.createInterruptedDownload(options)', function () {
it('can create an interrupted download item', function (done) {
ipcRenderer.sendSync('set-download-option', true, false)
const filePath = path.join(__dirname, 'fixtures', 'mock.pdf')
const options = {
path: filePath,
urlChain: ['http://127.0.0.1/'],
mimeType: 'application/pdf',
offset: 0,
length: 5242880
}
w.webContents.session.createInterruptedDownload(options)
ipcRenderer.once('download-created', function (event, state, urlChain,
mimeType, receivedBytes,
totalBytes, filename,
savePath) {
assert.equal(state, 'interrupted')
assert.deepEqual(urlChain, ['http://127.0.0.1/'])
assert.equal(mimeType, 'application/pdf')
assert.equal(receivedBytes, 0)
assert.equal(totalBytes, 5242880)
assert.equal(savePath, filePath)
done()
})
})
it('can be resumed', function (done) {
const fixtures = path.join(__dirname, 'fixtures')
const downloadFilePath = path.join(fixtures, 'logo.png')
const rangeServer = http.createServer(function (req, res) {
let options = {
root: fixtures
}
send(req, req.url, options)
.on('error', function (error) {
done(error)
}).pipe(res)
})
ipcRenderer.sendSync('set-download-option', true, false, downloadFilePath)
rangeServer.listen(0, '127.0.0.1', function () {
const port = rangeServer.address().port
const downloadUrl = `http://127.0.0.1:${port}/assets/logo.png`
const callback = function (event, state, url, mimeType,
receivedBytes, totalBytes, disposition,
filename, savePath, urlChain,
lastModifiedTime, eTag) {
if (state === 'cancelled') {
const options = {
path: savePath,
urlChain: urlChain,
mimeType: mimeType,
offset: receivedBytes,
length: totalBytes,
lastModified: lastModifiedTime,
eTag: eTag
}
ipcRenderer.sendSync('set-download-option', false, false, downloadFilePath)
w.webContents.session.createInterruptedDownload(options)
} else {
assert.equal(state, 'completed')
assert.equal(filename, 'logo.png')
assert.equal(savePath, downloadFilePath)
assert.equal(url, downloadUrl)
assert.equal(mimeType, 'image/png')
assert.equal(receivedBytes, 14022)
assert.equal(totalBytes, 14022)
assert(fs.existsSync(downloadFilePath))
fs.unlinkSync(downloadFilePath)
rangeServer.close()
ipcRenderer.removeListener('download-done', callback)
done()
}
}
ipcRenderer.on('download-done', callback)
w.webContents.downloadURL(downloadUrl)
})
})
})
})

Просмотреть файл

@ -10,6 +10,7 @@
"mocha": "^3.1.0",
"multiparty": "^4.1.2",
"q": "^1.4.1",
"send": "^0.14.1",
"temp": "^0.8.3",
"walkdir": "0.0.11",
"ws": "^1.1.1",

Просмотреть файл

@ -137,8 +137,16 @@ app.on('ready', function () {
// For session's download test, listen 'will-download' event in browser, and
// reply the result to renderer for verifying
var downloadFilePath = path.join(__dirname, '..', 'fixtures', 'mock.pdf')
ipcMain.on('set-download-option', function (event, needCancel, preventDefault) {
ipcMain.on('set-download-option', function (event, needCancel, preventDefault, filePath = downloadFilePath) {
window.webContents.session.once('will-download', function (e, item) {
window.webContents.send('download-created',
item.getState(),
item.getURLChain(),
item.getMimeType(),
item.getReceivedBytes(),
item.getTotalBytes(),
item.getFilename(),
item.getSavePath())
if (preventDefault) {
e.preventDefault()
const url = item.getURL()
@ -151,7 +159,11 @@ app.on('ready', function () {
}
})
} else {
item.setSavePath(downloadFilePath)
if (item.getState() === 'interrupted' && !needCancel) {
item.resume()
} else {
item.setSavePath(filePath)
}
item.on('done', function (e, state) {
window.webContents.send('download-done',
state,
@ -161,7 +173,10 @@ app.on('ready', function () {
item.getTotalBytes(),
item.getContentDisposition(),
item.getFilename(),
item.getSavePath())
item.getSavePath(),
item.getURLChain(),
item.getLastModifiedTime(),
item.getETag())
})
if (needCancel) item.cancel()
}