Skip to content

Commit

Permalink
[200ok-ch#932] WIP Implemented android local storage API
Browse files Browse the repository at this point in the history
* Interactions do not work, need to see why
  • Loading branch information
ieugen committed Jan 9, 2023
1 parent 5145338 commit bca35e6
Show file tree
Hide file tree
Showing 5 changed files with 273 additions and 27 deletions.
134 changes: 130 additions & 4 deletions android/app/src/main/java/com/twohundredok/organice/OrganiceSync.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import android.annotation.SuppressLint;
import android.content.Intent;
import android.net.Uri;
import android.os.ParcelFileDescriptor;

import androidx.activity.result.ActivityResult;
import androidx.documentfile.provider.DocumentFile;
Expand All @@ -14,7 +15,15 @@
import com.getcapacitor.annotation.ActivityCallback;
import com.getcapacitor.annotation.CapacitorPlugin;

import java.io.BufferedReader;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.Objects;
import java.util.stream.Collectors;

/**
Expand Down Expand Up @@ -47,8 +56,9 @@ public void pickDirectory(PluginCall call) {
/**
* Companion callback to {@link OrganiceSync::pickDirectory} .
* Handles the result of "pick a directory" action.
*
* <p>
* Will save permissions to the directory.
*
* @param call
* @param result
*/
Expand Down Expand Up @@ -100,7 +110,9 @@ public static JSObject asFileMetaData(DocumentFile d) {
}
DocumentFile parentFile = d.getParentFile();
JSObject o = new JSObject();
o.put("id", d.getUri());
o.put("uri", d.getUri());
o.put("path", d.getUri());
o.put("lastModified", d.lastModified());
o.put("length", d.length());
o.put("name", d.getName());
Expand All @@ -114,6 +126,7 @@ public static JSObject asFileMetaData(DocumentFile d) {

/**
* List files in a directory.
*
* @param call Expect a "uri" string parameter.
*/
@PluginMethod
Expand All @@ -135,12 +148,125 @@ public void listFiles(PluginCall call) {
JSObject ret = new JSObject();
ret.put("error", true);
ret.put("uri", uri);
ret.put("cause", "Usi is not a directory ");
ret.put("errorMessage", "Usi is not a directory ");
call.reject("Usi is not a directory " + uri);
}
} else {
call.reject("Uri is null");
}
}

/**
* Update / write to a file.
* Perform a full file write.
*
* @param call Expect a "uri" string parameter.
*/
@PluginMethod
public void putFileContents(PluginCall call) {
String uriStr = call.getString("uri");
String data = call.getString("contents");
if (uriStr != null) {
Uri uri = Uri.parse(Uri.decode(uriStr));
try {
ParcelFileDescriptor pfd = getActivity().getContentResolver().
openFileDescriptor(uri, "w");
FileOutputStream fileOutputStream =
new FileOutputStream(pfd.getFileDescriptor());
fileOutputStream.write(data.getBytes(StandardCharsets.UTF_8));
// Let the document provider know you're done by closing the stream.
fileOutputStream.close();
pfd.close();
} catch (FileNotFoundException e) {
JSObject o = new JSObject();
o.put("error", true);
o.put("uri", uri);
o.put("errorMessage", e.getLocalizedMessage());
call.reject("File not found" + uri, o);
} catch (IOException e) {
JSObject o = new JSObject();
o.put("error", true);
o.put("uri", uri);
o.put("errorMessage", e.getLocalizedMessage());
call.reject("Exception writing uri" + uri, o);
}
} else {
call.reject("Uri is null");
}
}

/**
* Read a document to a string.
* https://developer.android.com/training/data-storage/shared/documents-files#input_stream
*
* @param uri
* @return
* @throws IOException
*/
private String readTextFromUri(Uri uri) throws IOException {
StringBuilder stringBuilder = new StringBuilder();
try (InputStream inputStream =
getActivity().getContentResolver().openInputStream(uri);
BufferedReader reader = new BufferedReader(
new InputStreamReader(Objects.requireNonNull(inputStream)))) {
String line;
while ((line = reader.readLine()) != null) {
stringBuilder.append(line);
}
}
return stringBuilder.toString();
}

/**
* @param call
*/
@PluginMethod
public void getFileContentsAndMetadata(PluginCall call) {
String uriStr = call.getString("uri");
if (uriStr != null) {
Uri uri = Uri.parse(Uri.decode(uriStr));
try {
var d = DocumentFile.fromSingleUri(getContext(), uri);
var contents = readTextFromUri(uri);
JSObject r = asFileMetaData(d);
r.put("contents", contents);
call.resolve(r);
} catch (FileNotFoundException e) {
JSObject o = new JSObject();
o.put("error", true);
o.put("uri", uri);
o.put("errorMessage", e.getLocalizedMessage());
call.reject("File not found" + uri, o);
} catch (IOException e) {
JSObject o = new JSObject();
o.put("error", true);
o.put("uri", uri);
o.put("errorMessage", e.getLocalizedMessage());
call.reject("Exception writing uri" + uri, o);
}
} else {
call.reject("Uri is null");
}
}

/**
* @param call
*/
@PluginMethod
public void deleteFile(PluginCall call) {
String uriStr = call.getString("uri");
if (uriStr != null) {
Uri uri = Uri.parse(Uri.decode(uriStr));
var f = DocumentFile.fromSingleUri(getContext(), uri);
var deleted = f.delete();
if (deleted) {
call.resolve();
} else {
call.reject("File was not deleted");
}
} else {
call.reject("Uri is null");
}
call.reject("Uri is null");
return;
}

}
10 changes: 10 additions & 0 deletions src/App.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import { setDisappearingLoadingMessage, restoreStaticFile } from './actions/base

import createDropboxSyncBackendClient from './sync_backend_clients/dropbox_sync_backend_client';
import createWebDAVSyncBackendClient from './sync_backend_clients/webdav_sync_backend_client';
import createAndroidSyncBackendClient from './sync_backend_clients/android_sync_backend_client';
import createGitLabSyncBackendClient, {
createGitlabOAuth,
} from './sync_backend_clients/gitlab_sync_backend_client';
Expand Down Expand Up @@ -144,6 +145,15 @@ export function handleAuthenticatedSyncService(initialState) {
client,
});
break;
case 'AndroidStorage':
client = createAndroidSyncBackendClient(
getPersistedField("orgDirectory")
);
initialState.syncBackend = Map({
isAuthenticated: true,
client,
});
break;
default:
}
}
Expand Down
28 changes: 10 additions & 18 deletions src/components/SyncServiceSignIn/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,15 @@ import './stylesheet.css';
import DropboxLogo from './dropbox.svg';
import GitLabLogo from './gitlab.svg';

import { persistField } from '../../util/settings_persister';
import { getPersistedField, persistField } from '../../util/settings_persister';
import {
createGitlabOAuth,
gitLabProjectIdFromURL,
} from '../../sync_backend_clients/gitlab_sync_backend_client';

import OrganiceSync from '../../organice_android_sync';
import {
pickDirectory,
} from '../../sync_backend_clients/android_sync_backend_client';

import { Dropbox } from 'dropbox';
import _ from 'lodash';
Expand Down Expand Up @@ -151,22 +153,11 @@ function GitLab() {
);
}

const pickDirectory = async () => {
// 2. Pick files
const result = await OrganiceSync.pickDirectory();
return result
};

const listDirectory = async (uri) => {
const result = await OrganiceSync.listFiles({uri: uri})
return result;
}

function LocalStorage() {
function AndroidStorage() {
const [isVisible, setIsVisible] = useState(false);
const toggleVisible = () => setIsVisible(!isVisible);

const defaultOrgDirectory = '/org';
const defaultOrgDirectory = getPersistedField('orgDirectory');
const [orgDirectory, setOrgDirectory] = useState(defaultOrgDirectory);

return (
Expand All @@ -183,10 +174,10 @@ function LocalStorage() {
event.preventDefault();
pickDirectory().then(result => {
const {uri} = result
persistField('authenticatedSyncService', 'LocalStorage');
persistField('authenticatedSyncService', 'AndroidStorage');
persistField('orgDirectory', uri);
listDirectory(uri).then( result => alert("Files are" + JSON.stringify(result)))
})
window.location = window.location.origin + '?android';
}}
>
<p>
Expand All @@ -197,6 +188,7 @@ function LocalStorage() {
type="text"
value={orgDirectory}
className="textfield"
readOnly
onChange={(e) => setOrgDirectory(e.target.value)}
/>
</p>
Expand Down Expand Up @@ -250,7 +242,7 @@ export default class SyncServiceSignIn extends PureComponent {
</div>

<div className="sync-service-container">
<LocalStorage />
<AndroidStorage />
</div>

<footer className="sync-service-sign-in__help-text">
Expand Down
Loading

0 comments on commit bca35e6

Please sign in to comment.