@capgo/capacitor-downloader 0.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,17 @@
1
+ require 'json'
2
+
3
+ package = JSON.parse(File.read(File.join(__dir__, 'package.json')))
4
+
5
+ Pod::Spec.new do |s|
6
+ s.name = 'CapgoCapacitorDownloader'
7
+ s.version = package['version']
8
+ s.summary = package['description']
9
+ s.license = package['license']
10
+ s.homepage = package['repository']['url']
11
+ s.author = package['author']
12
+ s.source = { :git => package['repository']['url'], :tag => s.version.to_s }
13
+ s.source_files = 'ios/Sources/**/*.{swift,h,m,c,cc,mm,cpp}'
14
+ s.ios.deployment_target = '13.0'
15
+ s.dependency 'Capacitor'
16
+ s.swift_version = '5.1'
17
+ end
package/Package.swift ADDED
@@ -0,0 +1,30 @@
1
+ // swift-tools-version: 5.9
2
+ import PackageDescription
3
+
4
+ let package = Package(
5
+ name: "CapgoCapacitorDownloader",
6
+ platforms: [.iOS(.v13)],
7
+ products: [
8
+ .library(
9
+ name: "CapgoCapacitorDownloader",
10
+ targets: ["CapacitorDownloaderPlugin"])
11
+ ],
12
+ dependencies: [
13
+ .package(url: "https://github.com/ionic-team/capacitor-swift-pm.git", branch: "main"),
14
+ .package(url: "https://github.com/mzeeshanid/MZDownloadManager.git", from: "3.3.0")
15
+ ],
16
+ targets: [
17
+ .target(
18
+ name: "CapacitorDownloaderPlugin",
19
+ dependencies: [
20
+ .product(name: "Capacitor", package: "capacitor-swift-pm"),
21
+ .product(name: "Cordova", package: "capacitor-swift-pm"),
22
+ "MZDownloadManager"
23
+ ],
24
+ path: "ios/Sources/CapacitorDownloaderPlugin"),
25
+ .testTarget(
26
+ name: "CapacitorDownloaderPluginTests",
27
+ dependencies: ["CapacitorDownloaderPlugin"],
28
+ path: "ios/Tests/CapacitorDownloaderPluginTests")
29
+ ]
30
+ )
package/README.md ADDED
@@ -0,0 +1,206 @@
1
+ # @capgo/capacitor-downloader
2
+
3
+ Download file in background or foreground
4
+
5
+ WIP: the plugin is not yet ready for production
6
+
7
+ ## Install
8
+
9
+ ```bash
10
+ npm install @capgo/capacitor-downloader
11
+ npx cap sync
12
+ ```
13
+
14
+ ## API
15
+
16
+ <docgen-index>
17
+
18
+ * [`download(...)`](#download)
19
+ * [`pause(...)`](#pause)
20
+ * [`resume(...)`](#resume)
21
+ * [`stop(...)`](#stop)
22
+ * [`checkStatus(...)`](#checkstatus)
23
+ * [`getFileInfo(...)`](#getfileinfo)
24
+ * [`addListener('downloadProgress', ...)`](#addlistenerdownloadprogress-)
25
+ * [`addListener('downloadCompleted', ...)`](#addlistenerdownloadcompleted-)
26
+ * [`addListener('downloadFailed', ...)`](#addlistenerdownloadfailed-)
27
+ * [`removeAllListeners()`](#removealllisteners)
28
+ * [Interfaces](#interfaces)
29
+
30
+ </docgen-index>
31
+
32
+ <docgen-api>
33
+ <!--Update the source file JSDoc comments and rerun docgen to update the docs below-->
34
+
35
+ ### download(...)
36
+
37
+ ```typescript
38
+ download(options: DownloadOptions) => Promise<DownloadTask>
39
+ ```
40
+
41
+ | Param | Type |
42
+ | ------------- | ----------------------------------------------------------- |
43
+ | **`options`** | <code><a href="#downloadoptions">DownloadOptions</a></code> |
44
+
45
+ **Returns:** <code>Promise&lt;<a href="#downloadtask">DownloadTask</a>&gt;</code>
46
+
47
+ --------------------
48
+
49
+
50
+ ### pause(...)
51
+
52
+ ```typescript
53
+ pause(id: string) => Promise<void>
54
+ ```
55
+
56
+ | Param | Type |
57
+ | -------- | ------------------- |
58
+ | **`id`** | <code>string</code> |
59
+
60
+ --------------------
61
+
62
+
63
+ ### resume(...)
64
+
65
+ ```typescript
66
+ resume(id: string) => Promise<void>
67
+ ```
68
+
69
+ | Param | Type |
70
+ | -------- | ------------------- |
71
+ | **`id`** | <code>string</code> |
72
+
73
+ --------------------
74
+
75
+
76
+ ### stop(...)
77
+
78
+ ```typescript
79
+ stop(id: string) => Promise<void>
80
+ ```
81
+
82
+ | Param | Type |
83
+ | -------- | ------------------- |
84
+ | **`id`** | <code>string</code> |
85
+
86
+ --------------------
87
+
88
+
89
+ ### checkStatus(...)
90
+
91
+ ```typescript
92
+ checkStatus(id: string) => Promise<DownloadTask>
93
+ ```
94
+
95
+ | Param | Type |
96
+ | -------- | ------------------- |
97
+ | **`id`** | <code>string</code> |
98
+
99
+ **Returns:** <code>Promise&lt;<a href="#downloadtask">DownloadTask</a>&gt;</code>
100
+
101
+ --------------------
102
+
103
+
104
+ ### getFileInfo(...)
105
+
106
+ ```typescript
107
+ getFileInfo(path: string) => Promise<{ size: number; type: string; }>
108
+ ```
109
+
110
+ | Param | Type |
111
+ | ---------- | ------------------- |
112
+ | **`path`** | <code>string</code> |
113
+
114
+ **Returns:** <code>Promise&lt;{ size: number; type: string; }&gt;</code>
115
+
116
+ --------------------
117
+
118
+
119
+ ### addListener('downloadProgress', ...)
120
+
121
+ ```typescript
122
+ addListener(eventName: 'downloadProgress', listenerFunc: (progress: { id: string; progress: number; }) => void) => Promise<PluginListenerHandle>
123
+ ```
124
+
125
+ | Param | Type |
126
+ | ------------------ | --------------------------------------------------------------------- |
127
+ | **`eventName`** | <code>'downloadProgress'</code> |
128
+ | **`listenerFunc`** | <code>(progress: { id: string; progress: number; }) =&gt; void</code> |
129
+
130
+ **Returns:** <code>Promise&lt;<a href="#pluginlistenerhandle">PluginListenerHandle</a>&gt;</code>
131
+
132
+ --------------------
133
+
134
+
135
+ ### addListener('downloadCompleted', ...)
136
+
137
+ ```typescript
138
+ addListener(eventName: 'downloadCompleted', listenerFunc: (result: { id: string; }) => void) => Promise<PluginListenerHandle>
139
+ ```
140
+
141
+ | Param | Type |
142
+ | ------------------ | ------------------------------------------------- |
143
+ | **`eventName`** | <code>'downloadCompleted'</code> |
144
+ | **`listenerFunc`** | <code>(result: { id: string; }) =&gt; void</code> |
145
+
146
+ **Returns:** <code>Promise&lt;<a href="#pluginlistenerhandle">PluginListenerHandle</a>&gt;</code>
147
+
148
+ --------------------
149
+
150
+
151
+ ### addListener('downloadFailed', ...)
152
+
153
+ ```typescript
154
+ addListener(eventName: 'downloadFailed', listenerFunc: (error: { id: string; error: string; }) => void) => Promise<PluginListenerHandle>
155
+ ```
156
+
157
+ | Param | Type |
158
+ | ------------------ | --------------------------------------------------------------- |
159
+ | **`eventName`** | <code>'downloadFailed'</code> |
160
+ | **`listenerFunc`** | <code>(error: { id: string; error: string; }) =&gt; void</code> |
161
+
162
+ **Returns:** <code>Promise&lt;<a href="#pluginlistenerhandle">PluginListenerHandle</a>&gt;</code>
163
+
164
+ --------------------
165
+
166
+
167
+ ### removeAllListeners()
168
+
169
+ ```typescript
170
+ removeAllListeners() => Promise<void>
171
+ ```
172
+
173
+ --------------------
174
+
175
+
176
+ ### Interfaces
177
+
178
+
179
+ #### DownloadTask
180
+
181
+ | Prop | Type |
182
+ | -------------- | -------------------------------------------------------------------- |
183
+ | **`id`** | <code>string</code> |
184
+ | **`progress`** | <code>number</code> |
185
+ | **`state`** | <code>'PENDING' \| 'RUNNING' \| 'PAUSED' \| 'DONE' \| 'ERROR'</code> |
186
+
187
+
188
+ #### DownloadOptions
189
+
190
+ | Prop | Type |
191
+ | ----------------- | ---------------------------------------- |
192
+ | **`id`** | <code>string</code> |
193
+ | **`url`** | <code>string</code> |
194
+ | **`destination`** | <code>string</code> |
195
+ | **`headers`** | <code>{ [key: string]: string; }</code> |
196
+ | **`network`** | <code>'cellular' \| 'wifi-only'</code> |
197
+ | **`priority`** | <code>'high' \| 'normal' \| 'low'</code> |
198
+
199
+
200
+ #### PluginListenerHandle
201
+
202
+ | Prop | Type |
203
+ | ------------ | ----------------------------------------- |
204
+ | **`remove`** | <code>() =&gt; Promise&lt;void&gt;</code> |
205
+
206
+ </docgen-api>
@@ -0,0 +1,57 @@
1
+ ext {
2
+ junitVersion = project.hasProperty('junitVersion') ? rootProject.ext.junitVersion : '4.13.2'
3
+ androidxAppCompatVersion = project.hasProperty('androidxAppCompatVersion') ? rootProject.ext.androidxAppCompatVersion : '1.6.1'
4
+ androidxJunitVersion = project.hasProperty('androidxJunitVersion') ? rootProject.ext.androidxJunitVersion : '1.1.5'
5
+ androidxEspressoCoreVersion = project.hasProperty('androidxEspressoCoreVersion') ? rootProject.ext.androidxEspressoCoreVersion : '3.5.1'
6
+ }
7
+
8
+ buildscript {
9
+ repositories {
10
+ google()
11
+ mavenCentral()
12
+ }
13
+ dependencies {
14
+ classpath 'com.android.tools.build:gradle:8.2.1'
15
+ }
16
+ }
17
+
18
+ apply plugin: 'com.android.library'
19
+
20
+ android {
21
+ namespace "ee.forgr.capacitor.plugin.downloader"
22
+ compileSdk project.hasProperty('compileSdkVersion') ? rootProject.ext.compileSdkVersion : 34
23
+ defaultConfig {
24
+ minSdkVersion project.hasProperty('minSdkVersion') ? rootProject.ext.minSdkVersion : 22
25
+ targetSdkVersion project.hasProperty('targetSdkVersion') ? rootProject.ext.targetSdkVersion : 34
26
+ versionCode 1
27
+ versionName "1.0"
28
+ testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
29
+ }
30
+ buildTypes {
31
+ release {
32
+ minifyEnabled false
33
+ proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
34
+ }
35
+ }
36
+ lintOptions {
37
+ abortOnError false
38
+ }
39
+ compileOptions {
40
+ sourceCompatibility JavaVersion.VERSION_17
41
+ targetCompatibility JavaVersion.VERSION_17
42
+ }
43
+ }
44
+
45
+ repositories {
46
+ google()
47
+ mavenCentral()
48
+ }
49
+
50
+ dependencies {
51
+ implementation fileTree(dir: 'libs', include: ['*.jar'])
52
+ implementation project(':capacitor-android')
53
+ implementation "androidx.appcompat:appcompat:$androidxAppCompatVersion"
54
+ testImplementation "junit:junit:$junitVersion"
55
+ androidTestImplementation "androidx.test.ext:junit:$androidxJunitVersion"
56
+ androidTestImplementation "androidx.test.espresso:espresso-core:$androidxEspressoCoreVersion"
57
+ }
@@ -0,0 +1,4 @@
1
+ <manifest xmlns:android="http://schemas.android.com/apk/res/android">
2
+ <uses-permission android:name="android.permission.INTERNET" />
3
+ <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
4
+ </manifest>
@@ -0,0 +1,262 @@
1
+ package ee.forgr.capacitor.plugin.downloader;
2
+
3
+ import android.app.DownloadManager;
4
+ import android.content.BroadcastReceiver;
5
+ import android.content.Context;
6
+ import android.content.Intent;
7
+ import android.content.IntentFilter;
8
+ import android.database.Cursor;
9
+ import android.net.Uri;
10
+ import android.os.Handler;
11
+ import android.os.Looper;
12
+
13
+ import com.getcapacitor.JSObject;
14
+ import com.getcapacitor.Plugin;
15
+ import com.getcapacitor.PluginCall;
16
+ import com.getcapacitor.PluginMethod;
17
+ import com.getcapacitor.annotation.CapacitorPlugin;
18
+
19
+ import java.io.File;
20
+ import java.util.HashMap;
21
+ import java.util.Iterator;
22
+ import java.util.Map;
23
+
24
+ @CapacitorPlugin(name = "CapacitorDownloader")
25
+ public class CapacitorDownloaderPlugin extends Plugin {
26
+
27
+ private DownloadManager downloadManager;
28
+ private final Map<String, Long> downloads = new HashMap<>();
29
+ private final Handler handler = new Handler(Looper.getMainLooper());
30
+ private BroadcastReceiver downloadReceiver;
31
+
32
+ @Override
33
+ public void load() {
34
+ downloadManager = (DownloadManager) getContext().getSystemService(Context.DOWNLOAD_SERVICE);
35
+ registerDownloadReceiver();
36
+ }
37
+
38
+ private void registerDownloadReceiver() {
39
+ downloadReceiver = new BroadcastReceiver() {
40
+ @Override
41
+ public void onReceive(Context context, Intent intent) {
42
+ long receivedDownloadId = intent.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, -1);
43
+ String downloadId = getDownloadIdByValue(receivedDownloadId);
44
+ if (downloadId != null) {
45
+ checkDownloadStatus(downloadId);
46
+ }
47
+ }
48
+ };
49
+ getContext().registerReceiver(downloadReceiver, new IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE));
50
+ }
51
+
52
+ private String getDownloadIdByValue(long value) {
53
+ for (Map.Entry<String, Long> entry : downloads.entrySet()) {
54
+ if (entry.getValue() == value) {
55
+ return entry.getKey();
56
+ }
57
+ }
58
+ return null;
59
+ }
60
+
61
+ @PluginMethod
62
+ public void download(PluginCall call) {
63
+ String id = call.getString("id");
64
+ String url = call.getString("url");
65
+ String destination = call.getString("destination");
66
+
67
+ if (id == null || url == null || destination == null) {
68
+ call.reject("Missing required parameters");
69
+ return;
70
+ }
71
+
72
+ DownloadManager.Request request = new DownloadManager.Request(Uri.parse(url))
73
+ .setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED)
74
+ .setAllowedOverMetered(true)
75
+ .setAllowedOverRoaming(true);
76
+
77
+ // Handle custom destination
78
+ File destinationFile = new File(getContext().getExternalFilesDir(null), destination);
79
+ Uri destinationUri = Uri.fromFile(destinationFile);
80
+ request.setDestinationUri(destinationUri);
81
+
82
+ JSObject headers = call.getObject("headers");
83
+ if (headers != null) {
84
+ for (Iterator<String> it = headers.keys(); it.hasNext(); ) {
85
+ String key = it.next();
86
+ request.addRequestHeader(key, headers.getString(key));
87
+ }
88
+ }
89
+
90
+ String network = call.getString("network");
91
+ if ("wifi-only".equals(network)) {
92
+ request.setAllowedNetworkTypes(DownloadManager.Request.NETWORK_WIFI);
93
+ } else {
94
+ request.setAllowedNetworkTypes(DownloadManager.Request.NETWORK_MOBILE | DownloadManager.Request.NETWORK_WIFI);
95
+ }
96
+
97
+ long downloadId = downloadManager.enqueue(request);
98
+ downloads.put(id, downloadId);
99
+
100
+ JSObject result = new JSObject();
101
+ result.put("id", id);
102
+ result.put("status", DownloadManager.STATUS_PENDING);
103
+ call.resolve(result);
104
+
105
+ // Start a periodic progress check
106
+ startProgressCheck(id, downloadId);
107
+ }
108
+
109
+ private void startProgressCheck(final String id, final long downloadId) {
110
+ handler.post(new Runnable() {
111
+ @Override
112
+ public void run() {
113
+ if (checkDownloadStatus(id)) {
114
+ handler.postDelayed(this, 1000); // Check every second
115
+ }
116
+ }
117
+ });
118
+ }
119
+
120
+ private boolean checkDownloadStatus(String id) {
121
+ DownloadManager.Query query = new DownloadManager.Query().setFilterById(downloads.get(id));
122
+ try (Cursor cursor = downloadManager.query(query)) {
123
+ if (cursor.moveToFirst()) {
124
+ int status = cursor.getInt(cursor.getColumnIndexOrThrow(DownloadManager.COLUMN_STATUS));
125
+ long bytesDownloaded = cursor.getLong(cursor.getColumnIndexOrThrow(DownloadManager.COLUMN_BYTES_DOWNLOADED_SO_FAR));
126
+ long bytesTotal = cursor.getLong(cursor.getColumnIndexOrThrow(DownloadManager.COLUMN_TOTAL_SIZE_BYTES));
127
+
128
+ float progress = bytesTotal > 0 ? (float) bytesDownloaded / bytesTotal : 0f;
129
+
130
+ JSObject progressData = new JSObject();
131
+ progressData.put("id", id);
132
+ progressData.put("progress", progress);
133
+ notifyListeners("downloadProgress", progressData);
134
+
135
+ if (status == DownloadManager.STATUS_SUCCESSFUL) {
136
+ JSObject completedData = new JSObject();
137
+ completedData.put("id", id);
138
+ notifyListeners("downloadCompleted", completedData);
139
+ return false; // Stop checking progress
140
+ } else if (status == DownloadManager.STATUS_FAILED) {
141
+ JSObject failedData = new JSObject();
142
+ failedData.put("id", id);
143
+ failedData.put("error", "Download failed");
144
+ notifyListeners("downloadFailed", failedData);
145
+ return false; // Stop checking progress
146
+ }
147
+ }
148
+ }
149
+ return true; // Continue checking progress
150
+ }
151
+
152
+ @PluginMethod
153
+ public void pause(PluginCall call) {
154
+ // DownloadManager doesn't support pausing individual downloads
155
+ call.reject("Pausing individual downloads is not supported on Android");
156
+ }
157
+
158
+ @PluginMethod
159
+ public void resume(PluginCall call) {
160
+ // DownloadManager doesn't support resuming individual downloads
161
+ call.reject("Resuming individual downloads is not supported on Android");
162
+ }
163
+
164
+ @PluginMethod
165
+ public void stop(PluginCall call) {
166
+ String id = call.getString("id");
167
+ if (id == null || !downloads.containsKey(id)) {
168
+ call.reject("Download not found");
169
+ return;
170
+ }
171
+ int removedDownloads = downloadManager.remove(downloads.get(id));
172
+ downloads.remove(id);
173
+ call.resolve(new JSObject().put("removed", removedDownloads > 0));
174
+ }
175
+
176
+ @PluginMethod
177
+ public void checkStatus(PluginCall call) {
178
+ String id = call.getString("id");
179
+ if (id == null || !downloads.containsKey(id)) {
180
+ call.reject("Download not found");
181
+ return;
182
+ }
183
+
184
+ DownloadManager.Query query = new DownloadManager.Query().setFilterById(downloads.get(id));
185
+ try (Cursor cursor = downloadManager.query(query)) {
186
+ if (cursor.moveToFirst()) {
187
+ JSObject result = getDownloadStatus(cursor);
188
+ call.resolve(result);
189
+ } else {
190
+ call.reject("Download not found");
191
+ }
192
+ }
193
+ }
194
+
195
+ private JSObject getDownloadStatus(Cursor cursor) {
196
+ int status = cursor.getInt(cursor.getColumnIndexOrThrow(DownloadManager.COLUMN_STATUS));
197
+ long bytesDownloaded = cursor.getLong(cursor.getColumnIndexOrThrow(DownloadManager.COLUMN_BYTES_DOWNLOADED_SO_FAR));
198
+ long bytesTotal = cursor.getLong(cursor.getColumnIndexOrThrow(DownloadManager.COLUMN_TOTAL_SIZE_BYTES));
199
+
200
+ JSObject result = new JSObject();
201
+ result.put("status", status);
202
+ result.put("bytesDownloaded", bytesDownloaded);
203
+ result.put("bytesTotal", bytesTotal);
204
+
205
+ if (status == DownloadManager.STATUS_FAILED) {
206
+ int reason = cursor.getInt(cursor.getColumnIndexOrThrow(DownloadManager.COLUMN_REASON));
207
+ result.put("reason", reason);
208
+ result.put("reasonText", getReasonText(status, reason));
209
+ }
210
+
211
+ return result;
212
+ }
213
+
214
+ private String getReasonText(int status, int reason) {
215
+ if (status == DownloadManager.STATUS_FAILED) {
216
+ switch (reason) {
217
+ case DownloadManager.ERROR_CANNOT_RESUME:
218
+ return "ERROR_CANNOT_RESUME";
219
+ case DownloadManager.ERROR_DEVICE_NOT_FOUND:
220
+ return "ERROR_DEVICE_NOT_FOUND";
221
+ case DownloadManager.ERROR_FILE_ALREADY_EXISTS:
222
+ return "ERROR_FILE_ALREADY_EXISTS";
223
+ case DownloadManager.ERROR_FILE_ERROR:
224
+ return "ERROR_FILE_ERROR";
225
+ case DownloadManager.ERROR_HTTP_DATA_ERROR:
226
+ return "ERROR_HTTP_DATA_ERROR";
227
+ case DownloadManager.ERROR_INSUFFICIENT_SPACE:
228
+ return "ERROR_INSUFFICIENT_SPACE";
229
+ case DownloadManager.ERROR_TOO_MANY_REDIRECTS:
230
+ return "ERROR_TOO_MANY_REDIRECTS";
231
+ case DownloadManager.ERROR_UNHANDLED_HTTP_CODE:
232
+ return "ERROR_UNHANDLED_HTTP_CODE";
233
+ default:
234
+ return "ERROR_UNKNOWN";
235
+ }
236
+ }
237
+ return "UNKNOWN";
238
+ }
239
+
240
+ @PluginMethod
241
+ public void getFileInfo(PluginCall call) {
242
+ String path = call.getString("path");
243
+ if (path == null) {
244
+ call.reject("Missing path");
245
+ return;
246
+ }
247
+
248
+ Uri fileUri = Uri.parse(path);
249
+ JSObject result = new JSObject();
250
+ result.put("size", fileUri.getPath() != null ? new java.io.File(fileUri.getPath()).length() : 0);
251
+ result.put("type", getContext().getContentResolver().getType(fileUri));
252
+ call.resolve(result);
253
+ }
254
+
255
+ @Override
256
+ protected void handleOnDestroy() {
257
+ super.handleOnDestroy();
258
+ if (downloadReceiver != null) {
259
+ getContext().unregisterReceiver(downloadReceiver);
260
+ }
261
+ }
262
+ }
File without changes