Skip to content

Commit b50502a

Browse files
committed
Upload works (poc)
1 parent 9f96ea1 commit b50502a

File tree

12 files changed

+215
-24
lines changed

12 files changed

+215
-24
lines changed

README.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,17 @@ It works as follows:
1111
result with the docspell android app. It will upload it to the
1212
configured url.
1313

14+
15+
## Building
16+
17+
Using gradle:
18+
19+
``` shell
20+
gradle assembleRelease
21+
```
22+
23+
Settings for signing must be made available.
24+
1425
## Helpers
1526

1627
- https://nixos.wiki/wiki/Android

app/build.gradle

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ android {
99
minSdkVersion 21
1010
targetSdkVersion 30
1111
versionCode 1
12-
versionName "1.0"
12+
versionName "0.1.0"
1313

1414
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
1515
}
@@ -24,6 +24,24 @@ android {
2424
sourceCompatibility JavaVersion.VERSION_1_8
2525
targetCompatibility JavaVersion.VERSION_1_8
2626
}
27+
signingConfigs {
28+
release {
29+
storeFile file(RELEASE_STORE_FILE)
30+
storePassword RELEASE_STORE_PASSWORD
31+
keyAlias RELEASE_KEY_ALIAS
32+
keyPassword RELEASE_KEY_PASSWORD
33+
34+
// Optional, specify signing versions used
35+
v1SigningEnabled true
36+
v2SigningEnabled true
37+
}
38+
}
39+
40+
buildTypes {
41+
release {
42+
signingConfig signingConfigs.release
43+
}
44+
}
2745
}
2846

2947
dependencies {
@@ -33,10 +51,9 @@ dependencies {
3351
implementation 'androidx.appcompat:appcompat:1.1.0'
3452
implementation 'com.google.android.material:material:1.1.0'
3553
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
36-
implementation 'androidx.navigation:navigation-fragment:2.3.0'
37-
implementation 'androidx.navigation:navigation-ui:2.3.0'
3854
implementation 'me.dm7.barcodescanner:zxing:1.9.13'
3955
implementation "androidx.swiperefreshlayout:swiperefreshlayout:1.1.0"
56+
implementation 'com.squareup.okhttp3:okhttp:4.8.0'
4057
testImplementation 'junit:junit:4.13'
4158
androidTestImplementation 'androidx.test.ext:junit:1.1.1'
4259
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'

app/proguard-rules.pro

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414

1515
# Uncomment this to preserve the line number information for
1616
# debugging stack traces.
17-
-keepattributes SourceFile,LineNumberTable
17+
#-keepattributes SourceFile,LineNumberTable
1818

1919
# If you keep the line number information, uncomment this to
2020
# hide the original source file name.

app/src/main/java/org/docspell/docspellshare/activity/AddUrlActivity.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,15 @@
44
import android.os.Bundle;
55
import android.view.View;
66
import android.widget.EditText;
7+
78
import androidx.annotation.Nullable;
89
import androidx.appcompat.app.AppCompatActivity;
10+
911
import com.google.android.material.snackbar.Snackbar;
12+
1013
import org.docspell.docspellshare.R;
11-
import org.docspell.docspellshare.util.Strings;
1214
import org.docspell.docspellshare.data.UrlItem;
15+
import org.docspell.docspellshare.util.Strings;
1316

1417
public class AddUrlActivity extends AppCompatActivity {
1518
private static final int GET_QR_CODE = 1;

app/src/main/java/org/docspell/docspellshare/activity/MainActivity.java

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
11
package org.docspell.docspellshare.activity;
22

3-
import android.app.Activity;
43
import android.content.Intent;
5-
import android.graphics.Color;
64
import android.os.Bundle;
75
import android.util.Log;
86
import android.view.LayoutInflater;
@@ -11,7 +9,6 @@
119
import android.widget.Switch;
1210
import android.widget.TextView;
1311

14-
import androidx.annotation.ColorInt;
1512
import androidx.annotation.NonNull;
1613
import androidx.annotation.Nullable;
1714
import androidx.appcompat.app.AppCompatActivity;

app/src/main/java/org/docspell/docspellshare/activity/QrCodeActivity.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,11 @@
22

33
import android.content.Intent;
44
import android.os.Bundle;
5+
56
import androidx.appcompat.app.AppCompatActivity;
7+
68
import com.google.zxing.Result;
9+
710
import me.dm7.barcodescanner.zxing.ZXingScannerView;
811

912
public class QrCodeActivity extends AppCompatActivity implements ZXingScannerView.ResultHandler {

app/src/main/java/org/docspell/docspellshare/activity/ShareActivity.java

Lines changed: 18 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -10,19 +10,24 @@
1010

1111
import androidx.appcompat.app.AppCompatActivity;
1212

13-
import org.docspell.docspellshare.http.HttpRequest;
1413
import org.docspell.docspellshare.R;
15-
import org.docspell.docspellshare.util.Strings;
14+
import org.docspell.docspellshare.data.UrlItem;
15+
import org.docspell.docspellshare.http.HttpRequest;
1616
import org.docspell.docspellshare.http.UploadManager;
17+
import org.docspell.docspellshare.util.DataStore;
18+
import org.docspell.docspellshare.util.Strings;
1719

1820
import java.util.Collections;
1921
import java.util.List;
2022

2123
public class ShareActivity extends AppCompatActivity {
2224

25+
private DataStore dataStore;
26+
2327
@Override
2428
protected void onCreate(Bundle savedInstanceState) {
2529
super.onCreate(savedInstanceState);
30+
this.dataStore = new DataStore(this);
2631
setContentView(R.layout.activity_share);
2732
UploadManager.getInstance()
2833
.setProgress(
@@ -49,7 +54,7 @@ protected void onCreate(Bundle savedInstanceState) {
4954
} else {
5055
Uri uri = intent.getParcelableExtra(Intent.EXTRA_STREAM);
5156
if (uri != null) {
52-
handleFiles(Collections.singletonList(uri));
57+
handleFiles(Collections.singletonList(uri), type);
5358
}
5459
}
5560
} else if (Intent.ACTION_SEND_MULTIPLE.equals(action) && type != null) {
@@ -58,20 +63,23 @@ protected void onCreate(Bundle savedInstanceState) {
5863
} else {
5964
List<Uri> fileUris = intent.getParcelableArrayListExtra(Intent.EXTRA_STREAM);
6065
if (fileUris != null) {
61-
handleFiles(fileUris);
66+
handleFiles(fileUris, type);
6267
}
6368
}
6469
} else {
6570
Log.i("missing", "handling action '" + action + "' / '" + type + "' not implemented");
6671
}
6772
}
6873

69-
void handleFiles(List<Uri> uris) {
70-
HttpRequest.Builder req = HttpRequest.newBuilder();
71-
ContentResolver resolver = getContentResolver();
72-
for (Uri uri : uris) {
73-
req.addFile(resolver, uri);
74+
void handleFiles(List<Uri> uris, String type) {
75+
String url = dataStore.getDefaultUrl().map(UrlItem::getUrl).orElse(null);
76+
if (url != null) {
77+
HttpRequest.Builder req = HttpRequest.newBuilder().setUrl(url);
78+
ContentResolver resolver = getContentResolver();
79+
for (Uri uri : uris) {
80+
req.addFile(resolver, uri, type);
81+
}
82+
UploadManager.getInstance().submit(req.build());
7483
}
75-
UploadManager.getInstance().submit(req.build());
7684
}
7785
}

app/src/main/java/org/docspell/docspellshare/data/Option.java

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,12 @@ public static <A> Option<A> empty() {
2222
return (Option) None.INSTANCE;
2323
}
2424

25+
public abstract A orElse(A defaultValue);
26+
2527
public abstract <B> Option<B> map(Fun<A, B> f);
2628

29+
public abstract <B> Option<B> flatMap(Fun<A, Option<B>> f);
30+
2731
public abstract <B> B fold(Fun<A, B> fa, Lazy<B> fe);
2832

2933
public abstract Option<A> filter(Fun<A, Boolean> pred);
@@ -41,6 +45,16 @@ public Some(A value) {
4145
this.value = value;
4246
}
4347

48+
@Override
49+
public <B> Option<B> flatMap(Fun<A, Option<B>> f) {
50+
return f.apply(value);
51+
}
52+
53+
@Override
54+
public A orElse(A defaultValue) {
55+
return value;
56+
}
57+
4458
@Override
4559
public Option<A> filter(Fun<A, Boolean> pred) {
4660
if (Boolean.TRUE.equals(pred.apply(value))) {
@@ -88,11 +102,21 @@ public String toString() {
88102
public static final class None extends Option<Void> {
89103
private static final Option<Void> INSTANCE = new None();
90104

105+
@Override
106+
public <B> Option<B> flatMap(Fun<Void, Option<B>> f) {
107+
return (Option) this;
108+
}
109+
91110
@Override
92111
public <B> Option<B> map(Fun<Void, B> f) {
93112
return (Option) this;
94113
}
95114

115+
@Override
116+
public Void orElse(Void defaultValue) {
117+
return defaultValue;
118+
}
119+
96120
@Override
97121
public <B> B fold(Fun<Void, B> fa, Lazy<B> fe) {
98122
return fe.get();

app/src/main/java/org/docspell/docspellshare/http/HttpRequest.java

Lines changed: 101 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,27 +3,124 @@
33
import android.content.ContentResolver;
44
import android.net.Uri;
55

6+
import androidx.annotation.NonNull;
7+
8+
import org.docspell.docspellshare.data.Option;
9+
10+
import java.io.IOException;
11+
import java.io.InputStream;
12+
import java.util.ArrayList;
13+
import java.util.Arrays;
14+
import java.util.List;
15+
import java.util.concurrent.TimeUnit;
16+
17+
import okhttp3.ConnectionSpec;
18+
import okhttp3.MediaType;
19+
import okhttp3.MultipartBody;
20+
import okhttp3.OkHttpClient;
21+
import okhttp3.Request;
22+
import okhttp3.RequestBody;
23+
import okio.BufferedSink;
24+
import okio.Okio;
25+
import okio.Source;
26+
27+
import static org.docspell.docspellshare.util.Strings.requireNonEmpty;
28+
629
public final class HttpRequest {
7-
private HttpRequest() {}
30+
private static final OkHttpClient client =
31+
new OkHttpClient.Builder()
32+
.connectionSpecs(
33+
Arrays.asList(
34+
ConnectionSpec.MODERN_TLS,
35+
ConnectionSpec.COMPATIBLE_TLS,
36+
ConnectionSpec.CLEARTEXT))
37+
.readTimeout(5, TimeUnit.MINUTES)
38+
.writeTimeout(5, TimeUnit.MINUTES)
39+
.followRedirects(true)
40+
.followSslRedirects(true)
41+
.build();
42+
43+
private final String url;
44+
private final List<DataPart> data;
45+
46+
private HttpRequest(String url, List<DataPart> data) {
47+
this.url = requireNonEmpty(url, "url must be specified");
48+
this.data = data;
49+
}
50+
51+
public void execute() throws IOException {
52+
MultipartBody.Builder body = new MultipartBody.Builder().setType(MultipartBody.FORM);
53+
for (DataPart dp : data) {
54+
body.addFormDataPart("file", dp.getName(), createPartBody(dp));
55+
}
856

9-
public void execute() {}
57+
Request req = new Request.Builder().url(url).post(body.build()).build();
58+
client.newCall(req).execute();
59+
}
1060

1161
public static Builder newBuilder() {
1262
return new Builder();
1363
}
1464

1565
public static class Builder {
66+
private final List<DataPart> parts = new ArrayList<>();
67+
private String url;
68+
69+
public Builder addFile(ContentResolver resolver, Uri data, String type) {
70+
parts.add(
71+
new DataPart() {
72+
@Override
73+
public InputStream getData() throws IOException {
74+
return resolver.openInputStream(data);
75+
}
76+
77+
@Override
78+
public String getName() {
79+
return data.getLastPathSegment();
80+
}
1681

17-
public Builder addFile(ContentResolver resolver, Uri data) {
82+
@Override
83+
public Option<String> getType() {
84+
return Option.of(type);
85+
}
86+
});
1887
return this;
1988
}
2089

2190
public Builder setUrl(String url) {
91+
this.url = url;
2292
return this;
2393
}
2494

2595
public HttpRequest build() {
26-
return new HttpRequest();
96+
return new HttpRequest(url, parts);
2797
}
2898
}
99+
100+
public interface DataPart {
101+
InputStream getData() throws IOException;
102+
103+
String getName();
104+
105+
Option<String> getType();
106+
}
107+
108+
private static RequestBody createPartBody(DataPart part) {
109+
final String octetStream = "application/octet-stream";
110+
return new RequestBody() {
111+
@Override
112+
public MediaType contentType() {
113+
final MediaType mt = MediaType.parse(part.getType().orElse(octetStream));
114+
return mt != null ? mt : MediaType.get(octetStream);
115+
}
116+
117+
@Override
118+
public void writeTo(@NonNull BufferedSink sink) throws IOException {
119+
try (InputStream in = part.getData();
120+
Source source = Okio.source(in)) {
121+
sink.writeAll(source);
122+
}
123+
}
124+
};
125+
}
29126
}

0 commit comments

Comments
 (0)