Skip to content

Commit

Permalink
Open up for saving to temporary location (you can by that skip reques…
Browse files Browse the repository at this point in the history
…ting the permission `WRITE_EXTERNAL_STORAGE`)
  • Loading branch information
SimonSimCity committed Jun 10, 2016
1 parent b0c4863 commit 14b6769
Show file tree
Hide file tree
Showing 8 changed files with 82 additions and 33 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,9 @@ public void StartDownload (Android.App.DownloadManager downloadManager, string d
request.AddRequestHeader (header.Key, header.Value);
}

request.SetDestinationUri (Uri.FromFile (new Java.IO.File (destinationPathName)));
if (destinationPathName != null) {
request.SetDestinationUri (Uri.FromFile (new Java.IO.File (destinationPathName)));
}

Id = downloadManager.Enqueue (request);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,12 @@ public void Start (IDownloadFile i)
{
var file = (DownloadFileImplementation)i;

file.StartDownload (_downloadManager, PathNameForDownloadedFile (file));
string destinationPathName = null;
if (PathNameForDownloadedFile != null) {
destinationPathName = PathNameForDownloadedFile (file);
}

file.StartDownload (_downloadManager, destinationPathName);
Queue.Add (file);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,31 +69,42 @@ public override void DidFinishDownloading (NSUrlSession session, NSUrlSessionDow
return;
}

MoveDownloadedFile (file, location);
var success = true;
if (Controller.PathNameForDownloadedFile != null) {
var destinationPathName = Controller.PathNameForDownloadedFile (file);
if (destinationPathName != null) {
success = MoveDownloadedFile (file, location, destinationPathName);
}
}

// If the file destination is unknown or was moved successfully ...
if (success) {
file.Status = DownloadFileStatus.COMPLETED;
}

Controller.Queue.Remove (file);
}

/**
* Move the downloaded file to it's destination and remove it from the download-queue.
* Move the downloaded file to it's destination
*/
public void MoveDownloadedFile (DownloadFileImplementation file, NSUrl location)
public bool MoveDownloadedFile (DownloadFileImplementation file, NSUrl location, string destinationPathName)
{
NSFileManager fileManager = NSFileManager.DefaultManager;

var destinationURL = new NSUrl (Controller.PathNameForDownloadedFile (file), false);
var destinationURL = new NSUrl (destinationPathName, false);
NSError removeCopy;
NSError errorCopy;

fileManager.Remove (destinationURL, out removeCopy);
bool success = fileManager.Copy (location, destinationURL, out errorCopy);
var success = fileManager.Copy (location, destinationURL, out errorCopy);

if (success) {
file.Status = DownloadFileStatus.COMPLETED;
} else {
if (!success) {
file.StatusDetails = errorCopy.LocalizedDescription;
file.Status = DownloadFileStatus.FAILED;
}

Controller.Queue.Remove (file);
return success;
}

public override void DidFinishEventsForBackgroundSession(NSUrlSession session)
Expand Down
30 changes: 18 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ Add the nuget package to your cross-platform project and to every platform speci
_AppDelegate.cs_
```
/**
* Save the completion-handler we get when the app starts back from the background.
* Save the completion-handler we get when the app opens from the background.
* This method informs iOS that the app has finished all internal processing and can sleep again.
*/
public override void HandleEventsForBackgroundUrl(UIApplication application, string sessionIdentifier, Action completionHandler)
Expand All @@ -27,13 +27,8 @@ _AppDelegate.cs_
}
```

As of iOS 9, your URL MUST be secured or you have to add the domain to the list of exceptions. See [https://developer.apple.com/library/ios/releasenotes/General/WhatsNewIniOS/Articles/iOS9.html#//apple_ref/doc/uid/TP40016198-SW14](https://developer.apple.com/library/ios/releasenotes/General/WhatsNewIniOS/Articles/iOS9.html#//apple_ref/doc/uid/TP40016198-SW14)
### Android

Depending on the location you want the file to be saved, you may have to ask for the permission `WRITE_EXTERNAL_STORAGE`:
```
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
```
As of iOS 9, your URL must be secured or you have to add the domain to the list of exceptions. See [https://developer.apple.com/library/ios/releasenotes/General/WhatsNewIniOS/Articles/iOS9.html#//apple_ref/doc/uid/TP40016198-SW14](https://developer.apple.com/library/ios/releasenotes/General/WhatsNewIniOS/Articles/iOS9.html#//apple_ref/doc/uid/TP40016198-SW14)
#### Android

_Activity.cs_
```
Expand All @@ -53,13 +48,19 @@ You can now start a download by adding the following code:
downloadManager.Start(file);
```

This will add the file to a native library, which starts the download of that file. You can watch the properties of the `IDownloadFile` instance and execute some code if e.g. the status changes to `COMPLETED`, but you can also watch the `IDownloadManager.Queue` and execute some code if the list of files, that will be downloaded or are currently downloading changes.
This will add the file to a native library, which starts the download of that file. You can watch the properties of the `IDownloadFile` instance and execute some code if e.g. the status changes to `COMPLETED`, you can also watch the `IDownloadManager.Queue` and execute some code if the list of files, that will be downloaded or are currently downloading changes.

After a download has completed, it is removed from `IDownloadManager.Queue`.
After a download has been completed, the instance of `IDownloadFile` is then removed from `IDownloadManager.Queue`.

### Where are the files stored?

Usually, you would expect to set the path to the `IDownloadFile` instance, you get when calling `downloadManager.CreateDownloadFile(url)`. But, as this background-downloader even continues when the app crashed, you have to be able to reconstruct the path in every stage of the app. The correct way is to register a method as early as possible, that, in every circumstance, can reconstruct the path the file should be saved. This code line could look like following:
#### Default Option - Temperory Location

When you choose not to provide your own path before starting the download, the downloaded files are stored at a temporary directory and may be removed by the OS e.g. when the system runs out of space. You can move this file to a decided destination by listening on whether the status of the files changes to `DownloadFileStatus.COMPLETED`.

#### Recommended Option - Custom Location

Usually, you would expect to set the path to the `IDownloadFile` instance, you get when calling `downloadManager.CreateDownloadFile(url)`. But, as this download manager even continues downloading when the app crashed, you have to be able to reconstruct the path in every stage of the app. The correct way is to register a method as early as possible, that, in every circumstance, can reconstruct the path that the file should be saved. This method could look like following:
```
CrossDownloadManager.Current.PathNameForDownloadedFile = new System.Func<IDownloadFile, string> (file => {
#if __IOS__
Expand All @@ -74,8 +75,13 @@ Usually, you would expect to set the path to the `IDownloadFile` instance, you g
#endif
});
```
##### Additional for Andriod

On Android, the destination location must be a located outside of your Apps internal directory (see [#10](https://github.com/SimonSimCity/Xamarin-CrossDownloadManager/issues/10) for details). To allow your app to write to that location, you will need the the permission `WRITE_EXTERNAL_STORAGE` (See [Android API for DownloadManager.Request.setDestinationUri()](https://developer.android.com/reference/android/app/DownloadManager.Request.html#setDestinationUri%28android.net.Uri%29):
```
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
```

Please be aware of that the destination on Android MUST be on an external storage. See [#10](https://github.com/SimonSimCity/Xamarin-CrossDownloadManager/issues/10)

### I want to use $FAVORITE_IOC_LIBRARY

Expand Down
13 changes: 9 additions & 4 deletions Sample/Droid/MainActivity.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,11 @@ void InitDownloadManager ()
CrossDownloadManager.Init (this);

// Define where the files should be stored. MUST be an external storage. (see https://github.com/SimonSimCity/Xamarin-CrossDownloadManager/issues/10)
CrossDownloadManager.Current.PathNameForDownloadedFile = new Func<IDownloadFile, string> (file => {
string fileName = Android.Net.Uri.Parse (file.Url).Path.Split ('/').Last ();
return Path.Combine (ApplicationContext.GetExternalFilesDir (Android.OS.Environment.DirectoryDownloads).AbsolutePath, fileName);
});
// If you skip this, you neither need the permission `WRITE_EXTERNAL_STORAGE`.
//CrossDownloadManager.Current.PathNameForDownloadedFile = new Func<IDownloadFile, string> (file => {
// string fileName = Android.Net.Uri.Parse (file.Url).Path.Split ('/').Last ();
// return Path.Combine (ApplicationContext.GetExternalFilesDir (Android.OS.Environment.DirectoryDownloads).AbsolutePath, fileName);
//});
}

NotificationClickedBroadcastReceiver _receiverNotificationClicked;
Expand Down Expand Up @@ -80,6 +81,10 @@ protected override void OnCreate (Bundle savedInstanceState)
case DownloadFileStatus.FAILED:
case DownloadFileStatus.CANCELED:
button.Text = "Downloading finished.";

// Get the path this file was saved to. When you didn't set a custom path, this will be some temporary directory.
var nativeDownloadManager = (DownloadManager)ApplicationContext.GetSystemService (DownloadService);
System.Diagnostics.Debug.WriteLine (nativeDownloadManager.GetUriForDownloadedFile (((DownloadFileImplementation)sender).Id));

This comment has been minimized.

Copy link
@SimonSimCity

SimonSimCity Jun 14, 2016

Author Owner

Use ApplicationContext.ContentResolver.OpenInputStream() if you want to copy the file (you can't move it).

break;
}
}
Expand Down
5 changes: 4 additions & 1 deletion Sample/Droid/Properties/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" android:versionCode="1" android:versionName="1.0" package="com.example.download_example">
<uses-sdk android:minSdkVersion="15" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<!--
You have to add this permission when setting `PathNameForDownloadedFile`.
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
-->
<application android:label="Download Example">
</application>
</manifest>
18 changes: 17 additions & 1 deletion Sample/iOS/ExtendedUrlSessionDownloadDelegate.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using Plugin.DownloadManager;
using Foundation;
using Plugin.DownloadManager;

namespace DownloadExample.iOS
{
Expand All @@ -17,6 +18,21 @@ public override void DidFinishEventsForBackgroundSession (Foundation.NSUrlSessio

base.DidFinishEventsForBackgroundSession (session);
}

public override void DidFinishDownloading (NSUrlSession session, NSUrlSessionDownloadTask downloadTask, NSUrl location)
{
// In case you need to access the IDownloadFile implementation, you have to load it before calling the base-method.
var file = getDownloadFileByTask (downloadTask);
if (file == null) {
return;
}

// This base-method sets the state to "COMPLETED" and moves the file if `PathNameForDownloadedFile` is set.
base.DidFinishDownloading (session, downloadTask, location);

// If you don't set `PathNameForDownloadedFile`, you can do what you want with the file now.
System.Diagnostics.Debug.WriteLine (location.AbsoluteString);
}
}
}

9 changes: 5 additions & 4 deletions Sample/iOS/ViewController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,11 @@ public override void ViewDidLoad ()
{
base.ViewDidLoad ();

CrossDownloadManager.Current.PathNameForDownloadedFile = new Func<IDownloadFile, string> (file => {
string fileName = (new NSUrl (file.Url, false)).LastPathComponent;
return Path.Combine (Environment.GetFolderPath (Environment.SpecialFolder.MyDocuments), fileName);
});
// If you want to take full control of the saved file, see `ExtendedUrlSessionDownloadDelegate`
//CrossDownloadManager.Current.PathNameForDownloadedFile = new Func<IDownloadFile, string> (file => {
// string fileName = (new NSUrl (file.Url, false)).LastPathComponent;
// return Path.Combine (Environment.GetFolderPath (Environment.SpecialFolder.MyDocuments), fileName);
//});

var foo = new Downloader ();

Expand Down

0 comments on commit 14b6769

Please sign in to comment.