| OLD | NEW |
| 1 // Copyright 2016 The Chromium Authors. All rights reserved. | 1 // Copyright 2016 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 #include "ios/chrome/browser/reading_list/url_downloader.h" | 5 #include "ios/chrome/browser/reading_list/url_downloader.h" |
| 6 | 6 |
| 7 #include <string> | 7 #include <string> |
| 8 #include <vector> | 8 #include <vector> |
| 9 | 9 |
| 10 #include "base/files/file_path.h" | 10 #include "base/files/file_path.h" |
| 11 #include "base/files/file_util.h" | 11 #include "base/files/file_util.h" |
| 12 #include "base/md5.h" | 12 #include "base/md5.h" |
| 13 #include "base/memory/ptr_util.h" | 13 #include "base/memory/ptr_util.h" |
| 14 #include "base/path_service.h" | 14 #include "base/path_service.h" |
| 15 #include "ios/chrome/browser/chrome_paths.h" | 15 #include "ios/chrome/browser/chrome_paths.h" |
| 16 #include "ios/chrome/browser/dom_distiller/distiller_viewer.h" | 16 #include "ios/chrome/browser/dom_distiller/distiller_viewer.h" |
| 17 #include "ios/web/public/web_thread.h" | 17 #include "ios/web/public/web_thread.h" |
| 18 #include "url/gurl.h" | 18 #include "url/gurl.h" |
| 19 | 19 |
| 20 namespace { | 20 namespace { |
| 21 char const kOfflineDirectory[] = "Offline"; | 21 char const kOfflineDirectory[] = "Offline"; |
| 22 | |
| 23 // TODO(crbug.com/629771): Handle errors & retrying of failed saves, including | |
| 24 // distillation failure. | |
| 25 | |
| 26 } // namespace | 22 } // namespace |
| 27 | 23 |
| 28 // URLDownloader | 24 // URLDownloader |
| 29 | 25 |
| 30 URLDownloader::URLDownloader( | 26 URLDownloader::URLDownloader( |
| 31 dom_distiller::DomDistillerService* distiller_service, | 27 dom_distiller::DomDistillerService* distiller_service, |
| 32 PrefService* prefs, | 28 PrefService* prefs, |
| 33 base::FilePath chrome_profile_path, | 29 base::FilePath chrome_profile_path, |
| 34 const SuccessCompletion& download_completion, | 30 const DownloadCompletion& download_completion, |
| 35 const SuccessCompletion& delete_completion) | 31 const SuccessCompletion& delete_completion) |
| 36 : distiller_service_(distiller_service), | 32 : distiller_service_(distiller_service), |
| 37 pref_service_(prefs), | 33 pref_service_(prefs), |
| 38 download_completion_(download_completion), | 34 download_completion_(download_completion), |
| 39 delete_completion_(delete_completion), | 35 delete_completion_(delete_completion), |
| 40 working_(false), | 36 working_(false), |
| 41 base_directory_(chrome_profile_path), | 37 base_directory_(chrome_profile_path), |
| 42 task_tracker_() {} | 38 task_tracker_() {} |
| 43 | 39 |
| 44 URLDownloader::~URLDownloader() { | 40 URLDownloader::~URLDownloader() { |
| (...skipping 18 matching lines...) Expand all Loading... |
| 63 } | 59 } |
| 64 | 60 |
| 65 void URLDownloader::DownloadOfflineURL(const GURL& url) { | 61 void URLDownloader::DownloadOfflineURL(const GURL& url) { |
| 66 if (std::find(tasks_.begin(), tasks_.end(), std::make_pair(DOWNLOAD, url)) == | 62 if (std::find(tasks_.begin(), tasks_.end(), std::make_pair(DOWNLOAD, url)) == |
| 67 tasks_.end()) { | 63 tasks_.end()) { |
| 68 tasks_.push_back(std::make_pair(DOWNLOAD, url)); | 64 tasks_.push_back(std::make_pair(DOWNLOAD, url)); |
| 69 HandleNextTask(); | 65 HandleNextTask(); |
| 70 } | 66 } |
| 71 } | 67 } |
| 72 | 68 |
| 73 void URLDownloader::DownloadCompletionHandler(const GURL& url, bool success) { | 69 void URLDownloader::DownloadCompletionHandler(const GURL& url, |
| 70 const std::string& title, |
| 71 SuccessState success) { |
| 74 DCHECK(working_); | 72 DCHECK(working_); |
| 75 download_completion_.Run(url, success); | 73 |
| 76 distiller_.reset(); | 74 auto post_delete = base::Bind( |
| 77 working_ = false; | 75 [](URLDownloader* _this, const GURL& url, const std::string& title, |
| 78 HandleNextTask(); | 76 SuccessState success) { |
| 77 _this->download_completion_.Run( |
| 78 url, success, |
| 79 GURL(std::string(url::kFileScheme) + url::kStandardSchemeSeparator + |
| 80 _this->OfflineURLPagePath(url).value()), |
| 81 title); |
| 82 _this->distiller_.reset(); |
| 83 _this->working_ = false; |
| 84 _this->HandleNextTask(); |
| 85 }, |
| 86 base::Unretained(this), url, title, success); |
| 87 |
| 88 // If downloading failed, clean up any partial download. |
| 89 if (success == ERROR_RETRY || success == ERROR_PERMANENT) { |
| 90 task_tracker_.PostTaskAndReply( |
| 91 web::WebThread::GetTaskRunnerForThread(web::WebThread::FILE).get(), |
| 92 FROM_HERE, base::Bind( |
| 93 [](const base::FilePath& offline_directory_path) { |
| 94 base::DeleteFile(offline_directory_path, true); |
| 95 }, |
| 96 OfflineURLDirectoryPath(url)), |
| 97 post_delete); |
| 98 } else { |
| 99 post_delete.Run(); |
| 100 } |
| 79 } | 101 } |
| 80 | 102 |
| 81 void URLDownloader::DeleteCompletionHandler(const GURL& url, bool success) { | 103 void URLDownloader::DeleteCompletionHandler(const GURL& url, bool success) { |
| 82 DCHECK(working_); | 104 DCHECK(working_); |
| 83 delete_completion_.Run(url, success); | 105 delete_completion_.Run(url, success); |
| 84 working_ = false; | 106 working_ = false; |
| 85 HandleNextTask(); | 107 HandleNextTask(); |
| 86 } | 108 } |
| 87 | 109 |
| 88 void URLDownloader::HandleNextTask() { | 110 void URLDownloader::HandleNextTask() { |
| (...skipping 13 matching lines...) Expand all Loading... |
| 102 base::Bind(&base::DeleteFile, OfflineURLDirectoryPath(url), true), | 124 base::Bind(&base::DeleteFile, OfflineURLDirectoryPath(url), true), |
| 103 base::Bind(&URLDownloader::DeleteCompletionHandler, | 125 base::Bind(&URLDownloader::DeleteCompletionHandler, |
| 104 base::Unretained(this), url)); | 126 base::Unretained(this), url)); |
| 105 } else if (task.first == DOWNLOAD) { | 127 } else if (task.first == DOWNLOAD) { |
| 106 DCHECK(!distiller_); | 128 DCHECK(!distiller_); |
| 107 OfflineURLExists(url, base::Bind(&URLDownloader::DownloadURL, | 129 OfflineURLExists(url, base::Bind(&URLDownloader::DownloadURL, |
| 108 base::Unretained(this), url)); | 130 base::Unretained(this), url)); |
| 109 } | 131 } |
| 110 } | 132 } |
| 111 | 133 |
| 112 void URLDownloader::DownloadURL(GURL url, bool offlineURLExists) { | 134 void URLDownloader::DownloadURL(GURL url, bool offline_url_exists) { |
| 113 if (offlineURLExists) { | 135 if (offline_url_exists) { |
| 114 DownloadCompletionHandler(url, false); | 136 DownloadCompletionHandler(url, std::string(), DOWNLOAD_EXISTS); |
| 115 return; | 137 return; |
| 116 } | 138 } |
| 117 | 139 |
| 118 distiller_.reset(new dom_distiller::DistillerViewer( | 140 distiller_.reset(new dom_distiller::DistillerViewer( |
| 119 distiller_service_, pref_service_, url, | 141 distiller_service_, pref_service_, url, |
| 120 base::Bind(&URLDownloader::DistillerCallback, base::Unretained(this)))); | 142 base::Bind(&URLDownloader::DistillerCallback, base::Unretained(this)))); |
| 121 } | 143 } |
| 122 | 144 |
| 123 void URLDownloader::DistillerCallback( | 145 void URLDownloader::DistillerCallback( |
| 124 const GURL& pageURL, | 146 const GURL& page_url, |
| 125 const std::string& html, | 147 const std::string& html, |
| 126 const std::vector<dom_distiller::DistillerViewerInterface::ImageInfo>& | 148 const std::vector<dom_distiller::DistillerViewerInterface::ImageInfo>& |
| 127 images) { | 149 images, |
| 128 std::vector<dom_distiller::DistillerViewer::ImageInfo> imagesBlock = images; | 150 const std::string& title) { |
| 129 std::string blockHTML = html; | 151 std::vector<dom_distiller::DistillerViewer::ImageInfo> images_block = images; |
| 152 std::string block_html = html; |
| 153 if (html.empty()) { |
| 154 // TODO identify retry from permanent errors |
| 155 DownloadCompletionHandler(page_url, std::string(), ERROR_PERMANENT); |
| 156 return; |
| 157 } |
| 130 task_tracker_.PostTaskAndReplyWithResult( | 158 task_tracker_.PostTaskAndReplyWithResult( |
| 131 web::WebThread::GetTaskRunnerForThread(web::WebThread::FILE).get(), | 159 web::WebThread::GetTaskRunnerForThread(web::WebThread::FILE).get(), |
| 132 FROM_HERE, | 160 FROM_HERE, |
| 133 base::Bind(&URLDownloader::SaveDistilledHTML, base::Unretained(this), | 161 base::Bind(&URLDownloader::SaveDistilledHTML, base::Unretained(this), |
| 134 pageURL, imagesBlock, blockHTML), | 162 page_url, images_block, block_html), |
| 135 base::Bind(&URLDownloader::DownloadCompletionHandler, | 163 base::Bind(&URLDownloader::DownloadCompletionHandler, |
| 136 base::Unretained(this), pageURL)); | 164 base::Unretained(this), page_url, title)); |
| 137 } | 165 } |
| 138 | 166 |
| 139 bool URLDownloader::SaveDistilledHTML( | 167 URLDownloader::SuccessState URLDownloader::SaveDistilledHTML( |
| 140 const GURL& url, | 168 const GURL& url, |
| 141 std::vector<dom_distiller::DistillerViewerInterface::ImageInfo> images, | 169 std::vector<dom_distiller::DistillerViewerInterface::ImageInfo> images, |
| 142 std::string html) { | 170 std::string html) { |
| 143 if (CreateOfflineURLDirectory(url)) { | 171 if (CreateOfflineURLDirectory(url)) { |
| 144 return SaveHTMLForURL(SaveAndReplaceImagesInHTML(url, html, images), url); | 172 return SaveHTMLForURL(SaveAndReplaceImagesInHTML(url, html, images), url) |
| 173 ? DOWNLOAD_SUCCESS |
| 174 : ERROR_PERMANENT; |
| 145 } | 175 } |
| 146 return false; | 176 return ERROR_PERMANENT; |
| 147 } | 177 } |
| 148 | 178 |
| 149 base::FilePath URLDownloader::OfflineDirectoryPath() { | 179 base::FilePath URLDownloader::OfflineDirectoryPath() { |
| 150 return base_directory_.Append(kOfflineDirectory); | 180 return base_directory_.Append(kOfflineDirectory); |
| 151 } | 181 } |
| 152 | 182 |
| 153 base::FilePath URLDownloader::OfflineURLDirectoryPath(const GURL& url) { | 183 base::FilePath URLDownloader::OfflineURLDirectoryPath(const GURL& url) { |
| 154 std::string hash = base::MD5String(url.spec()); | 184 std::string hash = base::MD5String(url.spec()); |
| 155 return OfflineDirectoryPath().Append(hash); | 185 return OfflineDirectoryPath().Append(hash); |
| 156 } | 186 } |
| 157 | 187 |
| 158 base::FilePath URLDownloader::OfflineURLPagePath(const GURL& url) { | 188 base::FilePath URLDownloader::OfflineURLPagePath(const GURL& url) { |
| 159 return OfflineURLDirectoryPath(url).Append("page.html"); | 189 return OfflineURLDirectoryPath(url).Append("page.html"); |
| 160 } | 190 } |
| 161 | 191 |
| 162 bool URLDownloader::CreateOfflineURLDirectory(const GURL& url) { | 192 bool URLDownloader::CreateOfflineURLDirectory(const GURL& url) { |
| 163 base::FilePath path = OfflineURLDirectoryPath(url); | 193 base::FilePath path = OfflineURLDirectoryPath(url); |
| 164 if (!DirectoryExists(path)) { | 194 if (!DirectoryExists(path)) { |
| 165 return CreateDirectoryAndGetError(path, nil); | 195 return CreateDirectoryAndGetError(path, nil); |
| 166 } | 196 } |
| 167 return true; | 197 return true; |
| 168 } | 198 } |
| 169 | 199 |
| 170 std::string URLDownloader::SaveImage(const GURL& url, | 200 bool URLDownloader::SaveImage(const GURL& url, |
| 171 const GURL& imageURL, | 201 const GURL& image_url, |
| 172 const std::string& data) { | 202 const std::string& data, |
| 173 base::FilePath path = | 203 base::FilePath& path) { |
| 174 OfflineURLDirectoryPath(url).Append(base::MD5String(imageURL.spec())); | 204 path = OfflineURLDirectoryPath(url).Append(base::MD5String(image_url.spec())); |
| 175 if (!base::PathExists(path)) { | 205 if (!base::PathExists(path)) { |
| 176 base::WriteFile(path, data.c_str(), data.length()); | 206 return base::WriteFile(path, data.c_str(), data.length()) > 0; |
| 177 } | 207 } |
| 178 return path.AsUTF8Unsafe(); | 208 return true; |
| 179 } | 209 } |
| 180 | 210 |
| 181 // TODO(crbug.com/625621) DomDistiller doesn't correctly handle srcset | |
| 182 // attributes in img tags. | |
| 183 std::string URLDownloader::SaveAndReplaceImagesInHTML( | 211 std::string URLDownloader::SaveAndReplaceImagesInHTML( |
| 184 const GURL& url, | 212 const GURL& url, |
| 185 const std::string& html, | 213 const std::string& html, |
| 186 const std::vector<dom_distiller::DistillerViewerInterface::ImageInfo>& | 214 const std::vector<dom_distiller::DistillerViewerInterface::ImageInfo>& |
| 187 images) { | 215 images) { |
| 188 std::string mutableHTML = html; | 216 std::string mutable_html = html; |
| 189 for (size_t i = 0; i < images.size(); i++) { | 217 for (size_t i = 0; i < images.size(); i++) { |
| 190 const std::string& localImagePath = | 218 base::FilePath local_image_path; |
| 191 SaveImage(url, images[i].url, images[i].data); | 219 if (!SaveImage(url, images[i].url, images[i].data, local_image_path)) { |
| 192 const std::string& imageURL = images[i].url.spec(); | 220 return std::string(); |
| 193 size_t imageURLSize = imageURL.size(); | 221 } |
| 194 size_t pos = mutableHTML.find(imageURL, 0); | 222 const std::string& image_url = images[i].url.spec(); |
| 223 size_t image_url_size = image_url.size(); |
| 224 size_t pos = mutable_html.find(image_url, 0); |
| 195 while (pos != std::string::npos) { | 225 while (pos != std::string::npos) { |
| 196 mutableHTML.replace(pos, imageURLSize, localImagePath); | 226 mutable_html.replace(pos, image_url_size, |
| 197 pos = mutableHTML.find(imageURL, pos + imageURLSize); | 227 local_image_path.AsUTF8Unsafe()); |
| 228 pos = mutable_html.find(image_url, pos + image_url_size); |
| 198 } | 229 } |
| 199 } | 230 } |
| 200 return mutableHTML; | 231 return mutable_html; |
| 201 } | 232 } |
| 202 | 233 |
| 203 bool URLDownloader::SaveHTMLForURL(std::string html, const GURL& url) { | 234 bool URLDownloader::SaveHTMLForURL(std::string html, const GURL& url) { |
| 235 if (html.empty()) { |
| 236 return false; |
| 237 } |
| 204 base::FilePath path = OfflineURLPagePath(url); | 238 base::FilePath path = OfflineURLPagePath(url); |
| 205 return base::WriteFile(path, html.c_str(), html.length()) < 0; | 239 return base::WriteFile(path, html.c_str(), html.length()) > 0; |
| 206 } | 240 } |
| OLD | NEW |