Skip to content

Commit 9836352

Browse files
committed
Skip duplicates on upload
Sends the file to the server with a flag to skip if a duplicate.
1 parent 7bb7971 commit 9836352

File tree

6 files changed

+188
-158
lines changed

6 files changed

+188
-158
lines changed

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
import org.docspell.docspellshare.R;
2020
import org.docspell.docspellshare.data.Option;
2121
import org.docspell.docspellshare.data.UrlItem;
22-
import org.docspell.docspellshare.http.HttpRequest;
22+
import org.docspell.docspellshare.http.UploadRequest;
2323
import org.docspell.docspellshare.http.ProgressListener;
2424
import org.docspell.docspellshare.http.UploadManager;
2525
import org.docspell.docspellshare.util.DataStore;
@@ -129,7 +129,7 @@ private List<Uri> findFiles(Intent intent) {
129129
}
130130

131131
void handleFiles(List<Uri> uris, String url) {
132-
HttpRequest.Builder req = HttpRequest.newBuilder().setUrl(url);
132+
UploadRequest.Builder req = UploadRequest.newBuilder().setUrl(url);
133133
ContentResolver resolver = getContentResolver();
134134
for (Uri uri : uris) {
135135
req.addFile(resolver, uri, parseFilenameFromUri(uri));
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
package org.docspell.docspellshare.http;
2+
3+
import androidx.annotation.NonNull;
4+
5+
import org.docspell.docspellshare.data.Option;
6+
7+
import java.io.IOException;
8+
import java.io.InputStream;
9+
import java.util.Arrays;
10+
import java.util.concurrent.TimeUnit;
11+
12+
import okhttp3.ConnectionSpec;
13+
import okhttp3.MediaType;
14+
import okhttp3.OkHttpClient;
15+
import okhttp3.RequestBody;
16+
import okio.BufferedSink;
17+
import okio.Okio;
18+
import okio.Source;
19+
20+
public final class Client {
21+
private static final int CHUNK_SIZE = 16 * 1024;
22+
private static final OkHttpClient client =
23+
new OkHttpClient.Builder()
24+
.connectionSpecs(
25+
Arrays.asList(
26+
ConnectionSpec.MODERN_TLS,
27+
ConnectionSpec.COMPATIBLE_TLS,
28+
ConnectionSpec.CLEARTEXT))
29+
.readTimeout(5, TimeUnit.MINUTES)
30+
.writeTimeout(5, TimeUnit.MINUTES)
31+
.addInterceptor(new UserAgentInterceptor())
32+
.socketFactory(new RestrictedSocketFactory(CHUNK_SIZE))
33+
.followRedirects(true)
34+
.followSslRedirects(true)
35+
.build();
36+
37+
public static OkHttpClient get() {
38+
return client;
39+
}
40+
41+
public interface DataPart {
42+
InputStream getData() throws IOException;
43+
44+
String getName();
45+
46+
Option<String> getType();
47+
48+
/** Return -1, if unknown. */
49+
long getTotalSize();
50+
}
51+
52+
static RequestBody createPartBody(DataPart part, ProgressListener listener) {
53+
final String octetStream = "application/octet-stream";
54+
return new RequestBody() {
55+
@Override
56+
public MediaType contentType() {
57+
final MediaType mt = MediaType.parse(part.getType().orElse(octetStream));
58+
return mt != null ? mt : MediaType.get(octetStream);
59+
}
60+
61+
@Override
62+
public long contentLength() {
63+
return part.getTotalSize();
64+
}
65+
66+
@Override
67+
public void writeTo(@NonNull BufferedSink sink) throws IOException {
68+
try (InputStream in = part.getData();
69+
Source source = Okio.source(in)) {
70+
long total = 0;
71+
long read;
72+
while ((read = source.read(sink.getBuffer(), CHUNK_SIZE)) != -1) {
73+
total += read;
74+
sink.flush();
75+
listener.onProgress(part.getName(), total, part.getTotalSize());
76+
}
77+
}
78+
}
79+
};
80+
}
81+
}

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

Lines changed: 0 additions & 152 deletions
This file was deleted.

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

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@
22

33
import android.os.Process;
44
import android.util.Log;
5+
6+
import org.json.JSONException;
7+
58
import java.io.IOException;
69
import java.util.concurrent.ExecutorService;
710
import java.util.concurrent.Executors;
@@ -30,16 +33,16 @@ public void setProgress(ProgressListener listener) {
3033
}
3134
}
3235

33-
public void submit(HttpRequest request) {
36+
public void submit(UploadRequest request) {
3437
executorService.submit(new UploadWorker(request, progress.get()));
3538
}
3639

3740
static class UploadWorker implements Runnable {
3841

39-
private final HttpRequest request;
42+
private final UploadRequest request;
4043
private final ProgressListener listener;
4144

42-
UploadWorker(HttpRequest request, ProgressListener listener) {
45+
UploadWorker(UploadRequest request, ProgressListener listener) {
4346
this.request = request;
4447
this.listener = listener;
4548
}
@@ -55,7 +58,7 @@ public void run() {
5558
} finally {
5659
resp.close();
5760
}
58-
} catch (IOException e) {
61+
} catch (IOException | JSONException e) {
5962
Log.e("upload", "Error uploading!", e);
6063
listener.onException(e);
6164
}
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
package org.docspell.docspellshare.http;
2+
3+
import static org.docspell.docspellshare.util.Strings.requireNonEmpty;
4+
5+
import android.content.ContentResolver;
6+
import android.net.Uri;
7+
import java.io.IOException;
8+
import java.io.InputStream;
9+
import java.util.ArrayList;
10+
import java.util.List;
11+
import okhttp3.MediaType;
12+
import okhttp3.MultipartBody;
13+
import okhttp3.Request;
14+
import okhttp3.RequestBody;
15+
import okhttp3.Response;
16+
import org.docspell.docspellshare.data.Option;
17+
import org.docspell.docspellshare.util.Strings;
18+
import org.docspell.docspellshare.util.Uris;
19+
import org.json.JSONException;
20+
import org.json.JSONObject;
21+
22+
public final class UploadRequest {
23+
private static final MediaType json = MediaType.Companion.get("application/json");
24+
25+
private final String url;
26+
private final List<Client.DataPart> data;
27+
28+
private UploadRequest(String url, List<Client.DataPart> data) {
29+
this.url = requireNonEmpty(url, "url must be specified");
30+
this.data = data;
31+
}
32+
33+
public Response execute(ProgressListener progressListener) throws IOException, JSONException {
34+
MultipartBody.Builder body = new MultipartBody.Builder().setType(MultipartBody.FORM);
35+
// see https://docspell.org/docs/api/upload/#metadata
36+
JSONObject meta = new JSONObject();
37+
meta.put("multiple", true);
38+
meta.put("skipDuplicates", true);
39+
body.addFormDataPart("meta", "meta.json", RequestBody.create(meta.toString(), json));
40+
41+
// adding all files
42+
for (Client.DataPart dp : data) {
43+
body.addFormDataPart("file", dp.getName(), Client.createPartBody(dp, progressListener));
44+
}
45+
46+
Request req = new Request.Builder().url(url).post(body.build()).build();
47+
return Client.get().newCall(req).execute();
48+
}
49+
50+
public static Builder newBuilder() {
51+
return new Builder();
52+
}
53+
54+
public static class Builder {
55+
private final List<Client.DataPart> parts = new ArrayList<>();
56+
private String url;
57+
58+
public Builder addFile(ContentResolver resolver, Uri data, String fileName) {
59+
parts.add(
60+
new Client.DataPart() {
61+
@Override
62+
public InputStream getData() throws IOException {
63+
return resolver.openInputStream(data);
64+
}
65+
66+
@Override
67+
public String getName() {
68+
if (Strings.isNullOrBlank(fileName)) {
69+
return data.getLastPathSegment();
70+
} else {
71+
return fileName;
72+
}
73+
}
74+
75+
@Override
76+
public Option<String> getType() {
77+
return Option.ofNullable(resolver.getType(data));
78+
}
79+
80+
@Override
81+
public long getTotalSize() {
82+
return Uris.getFileSize(data, resolver);
83+
}
84+
});
85+
return this;
86+
}
87+
88+
public Builder setUrl(String url) {
89+
this.url = url;
90+
return this;
91+
}
92+
93+
public UploadRequest build() {
94+
return new UploadRequest(url, parts);
95+
}
96+
}
97+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
- Skip duplicate files on upload

0 commit comments

Comments
 (0)