Skip to content
Open
26 changes: 26 additions & 0 deletions filesystem/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@ const readFilePath = async () => {
* [`stat(...)`](#stat)
* [`rename(...)`](#rename)
* [`copy(...)`](#copy)
* [`isPortableStorageAvailable()`](#isportablestorageavailable)
* [`checkPermissions()`](#checkpermissions)
* [`requestPermissions()`](#requestpermissions)
* [`downloadFile(...)`](#downloadfile)
Expand Down Expand Up @@ -309,6 +310,22 @@ Copy a file or directory
--------------------


### isPortableStorageAvailable()

```typescript
isPortableStorageAvailable() => Promise<isPortableStorageAvailableResult>
```

Check if portable storage is available (SD Card for Android)
Only available on Android

**Returns:** <code>Promise&lt;<a href="#isportablestorageavailableresult">isPortableStorageAvailableResult</a>&gt;</code>

**Since:** 5.0.5

--------------------


### checkPermissions()

```typescript
Expand Down Expand Up @@ -533,6 +550,13 @@ Add a listener to file download progress events.
| **`uri`** | <code>string</code> | The uri where the file was copied into | 4.0.0 |


#### isPortableStorageAvailableResult

| Prop | Type | Description | Since |
| --------------- | -------------------- | ----------------------------------------------------------------- | ----- |
| **`available`** | <code>boolean</code> | Is portable storage available (SD Card) Only available on Android | 5.0.5 |


#### PermissionStatus

| Prop | Type |
Expand Down Expand Up @@ -607,6 +631,8 @@ A listener function that receives progress events.
| **`Cache`** | <code>'CACHE'</code> | The Cache directory. Can be deleted in cases of low memory, so use this directory to write app-specific files. that your app can re-create easily. | 1.0.0 |
| **`External`** | <code>'EXTERNAL'</code> | The external directory. On iOS it will use the Documents directory. On Android it's the directory on the primary shared/external storage device where the application can place persistent files it owns. These files are internal to the applications, and not typically visible to the user as media. Files will be deleted when the application is uninstalled. | 1.0.0 |
| **`ExternalStorage`** | <code>'EXTERNAL_STORAGE'</code> | The external storage directory. On iOS it will use the Documents directory. On Android it's the primary shared/external storage directory. It's not accesible on Android 10 unless the app enables legacy External Storage by adding `android:requestLegacyExternalStorage="true"` in the `application` tag in the `AndroidManifest.xml`. It's not accesible on Android 11 or newer. | 1.0.0 |
| **`PortableStorage`** | <code>'PORTABLE_STORAGE'</code> | SDCARD when configured as Portable Storage Points to application private storage on the SD Card when configured as Portable Storage. On iOS it will use the Documents directory | 5.0.5 |



#### Encoding
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@

import android.content.Context;
import android.net.Uri;
import android.os.Build;
import android.os.Environment;
import android.util.Base64;
import androidx.core.os.EnvironmentCompat;
import com.capacitorjs.plugins.filesystem.exceptions.CopyFailedException;
import com.capacitorjs.plugins.filesystem.exceptions.DirectoryExistsException;
import com.capacitorjs.plugins.filesystem.exceptions.DirectoryNotFoundException;
Expand All @@ -26,6 +28,8 @@
import java.nio.channels.FileChannel;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import org.json.JSONException;

Expand Down Expand Up @@ -212,10 +216,75 @@ public File getDirectory(String directory) {
return c.getExternalFilesDir(null);
case "EXTERNAL_STORAGE":
return Environment.getExternalStorageDirectory();
case "PORTABLE_STORAGE":
return this.getPortableStorage();
}
return null;
}

public boolean isPortableStorageAvailable() {
String[] storageDirectories = getFilteredStorageDirectories();
return storageDirectories.length > 0;
}

private File getPortableStorage() {
String[] storageDirectories = getFilteredStorageDirectories();

if (storageDirectories.length == 0) {
return Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOCUMENTS);
} else {
return new File(storageDirectories[0]);
}
}

private String[] getFilteredStorageDirectories() {
List<String> results = new ArrayList<String>();

Context c = this.context;

File[] externalDirs = c.getExternalFilesDirs(null);

// Get a list of external file systems and filter for removable storage only
for (File file : externalDirs) {
if (file == null) {
continue;
}
String applicationPath = file.getPath();

boolean addPath = false;

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
addPath = Environment.isExternalStorageRemovable(file);
} else {
addPath = Environment.MEDIA_MOUNTED.equals(EnvironmentCompat.getStorageState(file));
}

if (addPath) {
results.add(applicationPath);
}
}

//Remove paths which may not be external SDCard, like OTG
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
for (int i = 0; i < results.size(); i++) {
if (!results.get(i).toLowerCase().matches(".*[0-9a-f]{4}[-][0-9a-f]{4}.*")) {
results.remove(i--);
}
}
} else {
for (int i = 0; i < results.size(); i++) {
if (!results.get(i).toLowerCase().contains("ext") && !results.get(i).toLowerCase().contains("sdcard")) {
results.remove(i--);
}
}
}

String[] storageDirectories = new String[results.size()];
for (int i = 0; i < results.size(); ++i) storageDirectories[i] = results.get(i);

return storageDirectories;
}

public File getFileObject(String path, String directory) {
if (directory == null) {
Uri u = Uri.parse(path);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,13 @@ public void load() {

private static final String PERMISSION_DENIED_ERROR = "Unable to do file operation, user denied permission request";

@PluginMethod
public void isPortableStorageAvailable(PluginCall call) {
JSObject ret = new JSObject();
ret.put("available", implementation.isPortableStorageAvailable());
call.resolve(ret);
}

@PluginMethod
public void readFile(PluginCall call) {
String path = call.getString("path");
Expand Down
29 changes: 29 additions & 0 deletions filesystem/src/definitions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,16 @@ export enum Directory {
* @since 1.0.0
*/
ExternalStorage = 'EXTERNAL_STORAGE',

/**
* SDCARD when configured as Portable Storage
* Points to application private storage on the SD Card when configured as
* Portable Storage.
* On iOS it will use the Documents directory
*
* @since 5.0.5
*/
PortableStorage = 'PORTABLE_STORAGE',
}

export enum Encoding {
Expand Down Expand Up @@ -482,6 +492,16 @@ export interface CopyResult {
uri: string;
}

export interface isPortableStorageAvailableResult {
/**
* Is portable storage available (SD Card)
* Only available on Android
*
* @since 5.0.5
*/
available: boolean;
}

export interface DownloadFileOptions extends HttpOptions {
/**
* The path the downloaded file should be moved to.
Expand Down Expand Up @@ -529,6 +549,7 @@ export interface DownloadFileResult {
*/
blob?: Blob;
}

export interface ProgressStatus {
/**
* The url of the file being downloaded.
Expand Down Expand Up @@ -635,6 +656,14 @@ export interface FilesystemPlugin {
*/
copy(options: CopyOptions): Promise<CopyResult>;

/**
* Check if portable storage is available (SD Card for Android)
* Only available on Android
*
* @since 5.0.5
*/
isPortableStorageAvailable(): Promise<isPortableStorageAvailableResult>;

/**
* Check read/write permissions.
* Required on Android, only when using `Directory.Documents` or
Expand Down
5 changes: 5 additions & 0 deletions filesystem/src/web.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import type {
WriteFileOptions,
WriteFileResult,
Directory,
isPortableStorageAvailableResult,
DownloadFileOptions,
DownloadFileResult,
ProgressStatus,
Expand Down Expand Up @@ -473,6 +474,10 @@ export class FilesystemWeb extends WebPlugin implements FilesystemPlugin {
return { publicStorage: 'granted' };
}

async isPortableStorageAvailable(): Promise<isPortableStorageAvailableResult> {
return { available: false };
}

/**
* Function that can perform a copy or a rename
* @param options the options for the rename operation
Expand Down