1# Downloading Files 2 3To download files on the web page, you can use the following method to invoke the web APIs. 4 5## Listening for Downloads Initiated from Pages 6 7Call [setDownloadDelegate()](../reference/apis-arkweb/arkts-apis-webview-WebviewController.md#setdownloaddelegate11) to register a **DownloadDelegate** object with the **Web** component to listen for downloads initiated from pages. While the **Web** component downloads resources as requested, it notifies the application of the download progress through the **DownloadDelegate** object. 8 9In the following example, the **index.html** and **download.html** files are added to the **rawfile** folder of the application. After the application is started, a **Web** component is created and the **index.html** file is loaded. After **setDownloadDelegate** is clicked, a **DownloadDelegate** object is registered with the **Web** component. This **DownloadDelegate** object listens for any downloads initiated by clicking the download button on the page. 10 11```ts 12// xxx.ets 13import { webview } from '@kit.ArkWeb'; 14import { BusinessError } from '@kit.BasicServicesKit'; 15 16@Entry 17@Component 18struct WebComponent { 19 controller: webview.WebviewController = new webview.WebviewController(); 20 delegate: webview.WebDownloadDelegate = new webview.WebDownloadDelegate(); 21 22 build() { 23 Column() { 24 Button('setDownloadDelegate') 25 .onClick(() => { 26 try { 27 this.delegate.onBeforeDownload((webDownloadItem: webview.WebDownloadItem) => { 28 console.log("will start a download."); 29 // Pass in a download path and start the download. 30 // If the path is invalid, the file will be downloaded to the default directory at /data/storage/el2/base/cache/web/. 31 webDownloadItem.start("/data/storage/el2/base/cache/web/" + webDownloadItem.getSuggestedFileName()); 32 }) 33 this.delegate.onDownloadUpdated((webDownloadItem: webview.WebDownloadItem) => { 34 // Unique ID of a download task. 35 console.log("download update guid: " + webDownloadItem.getGuid()); 36 // Download progress. 37 console.log("download update percent complete: " + webDownloadItem.getPercentComplete()); 38 // Current download speed. 39 console.log("download update speed: " + webDownloadItem.getCurrentSpeed()) 40 }) 41 this.delegate.onDownloadFailed((webDownloadItem: webview.WebDownloadItem) => { 42 console.error("download failed guid: " + webDownloadItem.getGuid()); 43 // Error code of a download task failure. 44 console.error("download failed last error code: " + webDownloadItem.getLastErrorCode()); 45 }) 46 this.delegate.onDownloadFinish((webDownloadItem: webview.WebDownloadItem) => { 47 console.log("download finish guid: " + webDownloadItem.getGuid()); 48 }) 49 this.controller.setDownloadDelegate(this.delegate); 50 } catch (error) { 51 console.error(`ErrorCode: ${(error as BusinessError).code}, Message: ${(error as BusinessError).message}`); 52 } 53 }) 54 Web({ src: $rawfile('index.html'), controller: this.controller }) 55 } 56 } 57} 58``` 59 60HTML file to be loaded: 61```html 62<!-- index.html --> 63<!DOCTYPE html> 64<html> 65<body> 66// Click the download button in the lower right corner of the video to trigger a download task. 67<video controls="controls" width="800px" height="580px" 68 src="http://vjs.zencdn.net/v/oceans.mp4" 69 type="video/mp4"> 70</video> 71<a href='data:text/html,%3Ch1%3EHello%2C%20World%21%3C%2Fh1%3E' download='download.html'>Download the download.html</a> 72</body> 73</html> 74``` 75 76HTML file to be downloaded: 77```html 78<!-- download.html --> 79<!DOCTYPE html> 80<html> 81<body> 82<h1>download test</h1> 83</body> 84</html> 85``` 86 87## Initiating a Download Task 88 89Call [startDownload()](../reference/apis-arkweb/arkts-apis-webview-WebviewController.md#startdownload11) to initiate a download task. 90For a download initiated by it, the **Web** component works out the referrer based on the currently displayed URL and its own default referrer policy. 91 92 In the following example, clicking **setDownloadDelegate** registers a listener class with the **Web** component, and clicking **startDownload** initiates a download task. 93 The application is notified of the download task progress through the configured **DownloadDelegate** object. 94 95```ts 96// xxx.ets 97import { webview } from '@kit.ArkWeb'; 98import { BusinessError } from '@kit.BasicServicesKit'; 99 100@Entry 101@Component 102struct WebComponent { 103 controller: webview.WebviewController = new webview.WebviewController(); 104 delegate: webview.WebDownloadDelegate = new webview.WebDownloadDelegate(); 105 106 build() { 107 Column() { 108 Button('setDownloadDelegate') 109 .onClick(() => { 110 try { 111 this.delegate.onBeforeDownload((webDownloadItem: webview.WebDownloadItem) => { 112 console.log("will start a download."); 113 // Pass in a download path and start the download. 114 webDownloadItem.start("/data/storage/el2/base/cache/web/" + webDownloadItem.getSuggestedFileName()); 115 }) 116 this.delegate.onDownloadUpdated((webDownloadItem: webview.WebDownloadItem) => { 117 console.log("download update guid: " + webDownloadItem.getGuid()); 118 }) 119 this.delegate.onDownloadFailed((webDownloadItem: webview.WebDownloadItem) => { 120 console.error("download failed guid: " + webDownloadItem.getGuid()); 121 }) 122 this.delegate.onDownloadFinish((webDownloadItem: webview.WebDownloadItem) => { 123 console.log("download finish guid: " + webDownloadItem.getGuid()); 124 }) 125 this.controller.setDownloadDelegate(this.delegate); 126 } catch (error) { 127 console.error(`ErrorCode: ${(error as BusinessError).code}, Message: ${(error as BusinessError).message}`); 128 } 129 }) 130 Button('startDownload') 131 .onClick(() => { 132 try { 133 // The specified download address here is https://www.example.com/. 134 // Replace it with the URL from which you want to download files. 135 this.controller.startDownload('https://www.example.com/'); 136 } catch (error) { 137 console.error(`ErrorCode: ${(error as BusinessError).code}, Message: ${(error as BusinessError).message}`); 138 } 139 }) 140 Web({ src: 'www.example.com', controller: this.controller }) 141 } 142 } 143} 144``` 145 146Use [DocumentViewPicker()](../reference/apis-core-file-kit/js-apis-file-picker.md#documentviewpicker) to obtain the default download directory and set it as the download directory. 147```ts 148// xxx.ets 149import { webview } from '@kit.ArkWeb'; 150import { BusinessError } from '@kit.BasicServicesKit'; 151import { picker, fileUri } from '@kit.CoreFileKit'; 152@Entry 153@Component 154struct WebComponent { 155 controller: webview.WebviewController = new webview.WebviewController(); 156 delegate: webview.WebDownloadDelegate = new webview.WebDownloadDelegate(); 157 158 build() { 159 Column() { 160 Button('setDownloadDelegate') 161 .onClick(() => { 162 try { 163 this.delegate.onBeforeDownload((webDownloadItem: webview.WebDownloadItem) => { 164 console.log("will start a download."); 165 // Use DocumentViewPicker() to obtain the default download directory and set it as the download directory. 166 getDownloadPathFromPicker().then((downloadPath) => { 167 webDownloadItem.start(downloadPath + '/' + webDownloadItem.getSuggestedFileName()); 168 }); 169 170 }) 171 this.delegate.onDownloadUpdated((webDownloadItem: webview.WebDownloadItem) => { 172 // Unique ID of a download task. 173 console.log("download update guid: " + webDownloadItem.getGuid()); 174 // Download progress. 175 console.log("download update percent complete: " + webDownloadItem.getPercentComplete()); 176 // Current download speed. 177 console.log("download update speed: " + webDownloadItem.getCurrentSpeed()) 178 }) 179 this.delegate.onDownloadFailed((webDownloadItem: webview.WebDownloadItem) => { 180 console.error("download failed guid: " + webDownloadItem.getGuid()); 181 // Error code of a download task failure. 182 console.error("download failed last error code: " + webDownloadItem.getLastErrorCode()); 183 }) 184 this.delegate.onDownloadFinish((webDownloadItem: webview.WebDownloadItem) => { 185 console.log("download finish guid: " + webDownloadItem.getGuid()); 186 }) 187 this.controller.setDownloadDelegate(this.delegate); 188 } catch (error) { 189 console.error(`ErrorCode: ${(error as BusinessError).code}, Message: ${(error as BusinessError).message}`); 190 } 191 }) 192 Web({ src: $rawfile('index.html'), controller: this.controller }) 193 } 194 } 195 196} 197function getDownloadPathFromPicker(): Promise<string> { 198 return new Promise<string>(resolve => { 199 try { 200 const documentSaveOptions = new picker.DocumentSaveOptions(); 201 documentSaveOptions.pickerMode = picker.DocumentPickerMode.DOWNLOAD 202 const documentPicker = new picker.DocumentViewPicker(); 203 documentPicker.save(documentSaveOptions).then(async (documentSaveResult: Array<string>) => { 204 if (documentSaveResult.length <= 0) { 205 resolve(''); 206 return; 207 } 208 const uriString = documentSaveResult[0]; 209 if (!uriString) { 210 resolve(''); 211 return; 212 } 213 const uri = new fileUri.FileUri(uriString); 214 resolve(uri.path); 215 }).catch((err: BusinessError) => { 216 console.error(`ErrorCode: ${err.code}, Message: ${err.message}`); 217 resolve(''); 218 }); 219 } catch (error) { 220 resolve(''); 221 } 222 }) 223} 224``` 225 226> **NOTE** 227> 228>Call [WebDownloadItem.start](../reference/apis-arkweb/arkts-apis-webview-WebDownloadItem.md#start11) to specify the path for storing downloaded files. 229> 230>Note that **WebDownloadItem.start** does not start the download. The download process starts when the user clicks the page link. **WebDownloadItem.start** is used to move the content that has been downloaded to the temporary directory (**/data/storage/el2/base/cache/web/Temp/**) to the specified path, and directly save the remaining content to the specified path. Call[WebDownloadItem.cancel](../reference/apis-arkweb/arkts-apis-webview-WebDownloadItem.md#cancel11) to cancel the current download task. In this case, the temporary file is deleted. 231> 232>If you do not want to download the file to the temporary directory before **WebDownloadItem.start**, you can also use **WebDownloadItem.cancel** to interrupt the download. In addition, the interrupted download can be resumed using [WebDownloadManager.resumeDownload](../reference/apis-arkweb/arkts-apis-webview-WebDownloadManager.md#resumedownload11). 233 234## Resuming Unfinished Download Tasks Due to Process Exit 235When the **Web** component is started, you can resume the unfinished download task through the [resumeDownload()](../reference/apis-arkweb/arkts-apis-webview-WebDownloadManager.md#resumedownload11) API. 236 237In the following example, the **record** button is used to save the current download task to a persistent file. After the application is restarted, the **recovery** button can be used to resume the persistent download task. If multiple download tasks need to be saved, the application can adjust the persistence time and mode as required. 238```ts 239// xxx.ets 240import { webview } from '@kit.ArkWeb'; 241import { BusinessError } from '@kit.BasicServicesKit'; 242import { downloadUtil, fileName, filePath } from './downloadUtil'; // downloadUtil.ets is described below. 243 244@Entry 245@Component 246struct WebComponent { 247 controller: webview.WebviewController = new webview.WebviewController(); 248 delegate: webview.WebDownloadDelegate = new webview.WebDownloadDelegate(); 249 download: webview.WebDownloadItem = new webview.WebDownloadItem(); 250 // Used to record failed download tasks. 251 failedData: Uint8Array = new Uint8Array(); 252 253 aboutToAppear(): void { 254 downloadUtil.init(this.getUIContext()); 255 } 256 257 build() { 258 Column() { 259 Button('setDownloadDelegate') 260 .onClick(() => { 261 try { 262 this.delegate.onBeforeDownload((webDownloadItem: webview.WebDownloadItem) => { 263 console.log("will start a download."); 264 // Pass in a download path and start the download. 265 webDownloadItem.start("/data/storage/el2/base/cache/web/" + webDownloadItem.getSuggestedFileName()); 266 }) 267 this.delegate.onDownloadUpdated((webDownloadItem: webview.WebDownloadItem) => { 268 console.log("download update percent complete: " + webDownloadItem.getPercentComplete()); 269 this.download = webDownloadItem; 270 }) 271 this.delegate.onDownloadFailed((webDownloadItem: webview.WebDownloadItem) => { 272 console.error("download failed guid: " + webDownloadItem.getGuid()); 273 // Serialize the failed download task to a byte array. 274 this.failedData = webDownloadItem.serialize(); 275 }) 276 this.delegate.onDownloadFinish((webDownloadItem: webview.WebDownloadItem) => { 277 console.log("download finish guid: " + webDownloadItem.getGuid()); 278 }) 279 this.controller.setDownloadDelegate(this.delegate); 280 webview.WebDownloadManager.setDownloadDelegate(this.delegate); 281 } catch (error) { 282 console.error(`ErrorCode: ${(error as BusinessError).code}, Message: ${(error as BusinessError).message}`); 283 } 284 }) 285 Button('startDownload') 286 .onClick(() => { 287 try { 288 // The specified download address here is https://www.example.com/. 289 // Replace it with the URL from which you want to download files. 290 this.controller.startDownload('https://www.example.com/'); 291 } catch (error) { 292 console.error(`ErrorCode: ${(error as BusinessError).code}, Message: ${(error as BusinessError).message}`); 293 } 294 }) 295 // Serialize and save the current download task information for subsequent download task resumption. 296 // This example shows the one-task download scenario. For multiple download tasks, extend the code as required. 297 Button('record') 298 .onClick(() => { 299 try { 300 // Save the downloaded data to a persistent file. 301 downloadUtil.saveDownloadInfo(downloadUtil.uint8ArrayToStr(this.download.serialize())); 302 } catch (error) { 303 console.error(`ErrorCode: ${(error as BusinessError).code}, Message: ${(error as BusinessError).message}`); 304 } 305 }) 306 // Resume the download task from the serialized download task information. 307 // Ensure that the WebDownloadManager.setDownloadDelegate setting is complete when the button is triggered. 308 Button('recovery') 309 .onClick(() => { 310 try { 311 // The persistence file is available by default. You can set it as required. 312 let webDownloadItem = 313 webview.WebDownloadItem.deserialize(downloadUtil.strToUint8Array(downloadUtil.readFileSync(filePath, fileName))); 314 webview.WebDownloadManager.resumeDownload(webDownloadItem); 315 } catch (error) { 316 console.error(`ErrorCode: ${(error as BusinessError).code}, Message: ${(error as BusinessError).message}`); 317 } 318 }) 319 320 Web({ src: 'www.example.com', controller: this.controller }) 321 } 322 } 323} 324``` 325 326Download the task information persistence utility file. 327```ts 328// downloadUtil.ets 329import { util } from '@kit.ArkTS'; 330import fileStream from '@ohos.file.fs'; 331 332const helper = new util.Base64Helper(); 333 334export let filePath : string; 335export const fileName = 'demoFile.txt'; 336export namespace downloadUtil { 337 338 export function init(context: UIContext): void { 339 filePath = context.getHostContext()!.filesDir; 340 } 341 342 export function uint8ArrayToStr(uint8Array: Uint8Array): string { 343 return helper.encodeToStringSync(uint8Array); 344 } 345 346 export function strToUint8Array(str: string): Uint8Array { 347 return helper.decodeSync(str); 348 } 349 350 export function saveDownloadInfo(downloadInfo: string): void { 351 if (!fileExists(filePath)) { 352 mkDirectorySync(filePath); 353 } 354 355 writeToFileSync(filePath, fileName, downloadInfo); 356 } 357 358 export function fileExists(filePath: string): boolean { 359 try { 360 return fileStream.accessSync(filePath); 361 } catch (error) { 362 return false; 363 } 364 } 365 366 export function mkDirectorySync(directoryPath: string, recursion?: boolean): void { 367 try { 368 fileStream.mkdirSync(directoryPath, recursion ?? false); 369 } catch (error) { 370 console.error(`mk dir error. err message: ${error.message}, err code: ${error.code}`); 371 } 372 } 373 374 export function writeToFileSync(dir: string, fileName: string, msg: string): void { 375 let file = fileStream.openSync(dir + '/' + fileName, fileStream.OpenMode.WRITE_ONLY | fileStream.OpenMode.CREATE); 376 fileStream.writeSync(file.fd, msg); 377 } 378 379 export function readFileSync(dir: string, fileName: string): string { 380 return fileStream.readTextSync(dir + '/' + fileName); 381 } 382 383} 384``` 385