diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index af0b355..03e819a 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -17,7 +17,6 @@
android:exported="true" >
-
@@ -28,16 +27,17 @@
android:theme="@style/Theme.PartyUP.NoActionBar">
-
-
-
-
+
+
+
+
+
@@ -47,7 +47,6 @@
android:label="@string/app_name" >
-
diff --git a/app/src/main/java/me/ocv/partyup/XferActivity.java b/app/src/main/java/me/ocv/partyup/XferActivity.java
index 0c390ef..1a1f7cf 100644
--- a/app/src/main/java/me/ocv/partyup/XferActivity.java
+++ b/app/src/main/java/me/ocv/partyup/XferActivity.java
@@ -1,5 +1,8 @@
package me.ocv.partyup;
+import static java.lang.String.format;
+
+import android.annotation.SuppressLint;
import android.content.ClipData;
import android.content.ClipboardManager;
import android.content.Context;
@@ -14,6 +17,7 @@
import androidx.appcompat.app.AppCompatActivity;
import android.provider.OpenableColumns;
+import android.util.Log;
import android.view.Gravity;
import android.view.View;
import android.widget.Button;
@@ -31,67 +35,84 @@
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
+import java.util.ArrayList;
import java.util.Base64;
-import java.util.stream.Collectors;
import me.ocv.partyup.databinding.ActivityXferBinding;
-public class XferActivity extends AppCompatActivity {
+class F {
+ public Uri handle;
+ public String name;
+ public long size;
+ public String full_url;
+ public String share_url;
+ public String desc;
+}
+public class XferActivity extends AppCompatActivity {
ActivityXferBinding binding;
SharedPreferences prefs;
Intent the_intent;
String password;
- String the_msg;
- String base_url, full_url, share_url;
- Uri src_uri;
- String src_name;
- long src_size;
- String the_desc;
+ String base_url;
boolean upping;
+ String the_msg;
+ long bytes_done, bytes_total;
+ F[] files;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
+ upping = false;
+ prefs = PreferenceManager.getDefaultSharedPreferences(this);
binding = ActivityXferBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());
-
setSupportActionBar(binding.toolbar);
- prefs = PreferenceManager.getDefaultSharedPreferences(this);
-
the_intent = getIntent();
+ String etype = the_intent.getType();
String action = the_intent.getAction();
- String type = the_intent.getType();
-
- if (!the_intent.ACTION_SEND.equals(action) || type == null) {
- show_msg("cannot share this content");
+ boolean one = Intent.ACTION_SEND.equals(action);
+ boolean many = Intent.ACTION_SEND_MULTIPLE.equals(action);
+ if (etype == null || (!one && !many)) {
+ show_msg("cannot share content;\naction: " + action + "\ntype: " + etype);
return;
}
- upping = false;
- the_msg = null;
- src_name = null;
- src_size = -1;
- src_uri = (Uri)the_intent.getParcelableExtra(Intent.EXTRA_STREAM);
- the_msg = the_intent.getStringExtra(Intent.EXTRA_TEXT);
- if (src_uri != null) {
+ Uri[] handles = null;
+ if (many) {
+ ArrayList x = the_intent.getParcelableArrayListExtra(Intent.EXTRA_STREAM);
+ handles = x.toArray(new Uri[0]);
+ }
+ else if (one) {
+ Uri uri = (Uri) the_intent.getParcelableExtra(Intent.EXTRA_STREAM);
+ if (uri != null)
+ handles = new Uri[]{ uri };
+ else
+ the_msg = the_intent.getStringExtra(Intent.EXTRA_TEXT);
+ }
+ if (handles != null) {
+ files = new F[handles.length];
+ for (int a = 0; a < handles.length; a++) {
+ F f = new F();
+ f.handle = handles[a];
+ f.name = null;
+ f.size = -1;
+ files[a] = f;
+ }
handleSendImage();
} else if (the_msg != null) {
handleSendText();
} else {
- show_msg("cannot decide on what to send for " + type);
+ show_msg("cannot decide on what to send for " + the_intent.getType());
return;
}
final FloatingActionButton fab = (FloatingActionButton) findViewById(R.id.fab);
- fab.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- fab.setVisibility(View.GONE);
- do_up();
- }
+ fab.setOnClickListener(v -> {
+ fab.setVisibility(View.GONE);
+ do_up();
});
}
@@ -101,12 +122,7 @@ private void show_msg(String txt) {
private void tshow_msg(String txt) {
final TextView tv = (TextView)findViewById(R.id.upper_info);
- tv.post(new Runnable() {
- @Override
- public void run() {
- tv.setText(txt);
- }
- });
+ tv.post(() -> tv.setText(txt));
}
String getext(String mime) {
@@ -127,7 +143,7 @@ String getext(String mime) {
if (mime.contains("/")) {
mime = mime.split("/")[1];
- if (!mime.contains(".") && mime.length() < 8)
+ if (mime.matches("^[a-zA-Z0-9]{1,8}$"))
return mime;
}
@@ -140,41 +156,68 @@ private void handleSendText() {
do_up();
}
+ @SuppressLint("DefaultLocale")
private void handleSendImage() {
- // the following code returns the wrong filesize (off by 626 bytes)
- Cursor cur = getContentResolver().query(src_uri, null, null, null, null);
- int iname = cur.getColumnIndex(OpenableColumns.DISPLAY_NAME);
- int isize = cur.getColumnIndex(OpenableColumns.SIZE);
- cur.moveToFirst();
- src_name = cur.getString(iname);
- src_size = cur.getLong(isize);
- cur.close();
-
- if (src_name == null)
- src_name = "mystery-file." + getext(the_intent.getType());
-
- // get correct filesize
- try {
- InputStream ins = getContentResolver().openInputStream(src_uri);
- byte[] buf = new byte[128 * 1024];
- long sz = 0;
- while (true) {
- int n = ins.read(buf);
- if (n <= 0)
- break;
-
- sz += n;
+ for (F f : files) {
+ // contentresolver returns the wrong filesize (off by 626 bytes)
+ // but we want the name so lets go
+ try {
+ Cursor cur = getContentResolver().query(f.handle, null, null, null, null);
+ assert cur != null;
+ int iname = cur.getColumnIndex(OpenableColumns.DISPLAY_NAME);
+ int isize = cur.getColumnIndex(OpenableColumns.SIZE);
+ cur.moveToFirst();
+ f.name = cur.getString(iname);
+ f.size = cur.getLong(isize);
+ cur.close();
+ }
+ catch (Exception ex) {
+ Log.w("me.ocv.partyup", "contentresolver: " + ex.toString());
}
- src_size = sz;
+
+ if (f.name == null)
+ f.name = "mystery-file." + getext(the_intent.getType());
+
+ // get correct filesize
+ try {
+ InputStream ins = getContentResolver().openInputStream(f.handle);
+ assert ins != null;
+ byte[] buf = new byte[128 * 1024];
+ long sz = 0;
+ while (true) {
+ int n = ins.read(buf);
+ if (n <= 0)
+ break;
+
+ sz += n;
+ }
+ f.size = sz;
+ } catch (Exception ex) {
+ show_msg("Error3: " + ex.toString());
+ return;
+ }
+
+ f.desc = format("%s\n\nsize: %,d byte\ntype: %s", f.name, f.size, the_intent.getType());
}
- catch (Exception ex) {
- show_msg("Error3: " + ex.toString());
- return;
+
+ String msg;
+ if (files.length == 1) {
+ msg = "Upload the following file?\n\n" + files[0].desc;
}
+ else {
+ bytes_done = bytes_total = 0;
+ msg = "Upload the following " + files.length + " files?\n\n";
+ for (int a = 0; a < Math.min(10, files.length); a++) {
+ msg += files[a].name + "\n";
+ bytes_total += files[a].size;
+ }
- the_desc = String.format("%s\n\nsize: %,d byte\ntype: %s", src_name, src_size, the_intent.getType());
+ if (files.length > 10)
+ msg += "[...]\n";
- show_msg("Upload the following file?\n\n" + the_desc);
+ msg += format("--- total %,d bytes ---", bytes_total);
+ }
+ show_msg(msg);
if (prefs.getBoolean("autosend", false))
do_up();
}
@@ -184,49 +227,51 @@ private void do_up() {
return;
upping = true;
+ new Thread(this::do_up2).start();
+ }
+ private void do_up2() {
try {
base_url = prefs.getString("server_url", "");
+ if (base_url == null)
+ throw new Exception("server_url config is invalid");
+
if (!base_url.startsWith("http"))
base_url = "http://" + base_url;
- full_url = base_url;
- show_msg("Sending to " + base_url + " ...\n\n" + the_desc);
-
- if (!full_url.endsWith("/"))
- full_url += "/";
-
- if (src_size >= 0 && !src_name.isEmpty())
- full_url += URLEncoder.encode(src_name, "UTF-8");
+ if (!base_url.endsWith("/"))
+ base_url += "/";
password = prefs.getString("server_password", "");
+ password = password == null ? "" : password;
if (password.equals("Default value"))
password = ""; // necessary in the emulator, not on real devices(?)
- Thread t = new Thread() {
- public void run() {
- do_up2();
+ tshow_msg("Sending to " + base_url + " ...");
+
+ int nfiles = files == null ? 1 : files.length;
+ for (int a = 0; a < nfiles; a++) {
+ String full_url = base_url;
+ if (files != null) {
+ F f = files[a];
+ full_url += URLEncoder.encode(f.name, "UTF-8");
+ tshow_msg("Sending to " + base_url + " ...\n\n" + f.desc);
+ f.full_url = full_url;
}
- };
- t.start();
- }
- catch (Exception ex) {
- show_msg("Error1: " + ex.toString());
- }
- }
- private void do_up2() {
- try {
- URL url = new URL(full_url);
- HttpURLConnection conn = (HttpURLConnection) url.openConnection();
- conn.setDoOutput(true);
- if (!password.isEmpty())
- conn.setRequestProperty("Authorization", "Basic " + new String(Base64.getEncoder().encode(password.getBytes())));
-
- if (src_uri != null)
- do_fileput(conn);
- else
- do_textmsg(conn);
+ URL url = new URL(full_url);
+ HttpURLConnection conn = (HttpURLConnection) url.openConnection();
+ conn.setDoOutput(true);
+ if (!password.isEmpty())
+ conn.setRequestProperty("Authorization", "Basic " + new String(Base64.getEncoder().encode(password.getBytes())));
+
+ if (files == null)
+ do_textmsg(conn);
+ else
+ if (!do_fileput(conn, a))
+ return;
+ }
+ findViewById(R.id.upper_info).post(() -> onsuccess());
}
catch (Exception ex) {
tshow_msg("Error2: " + ex.toString() + "\n\nmaybe wrong password?");
@@ -237,7 +282,7 @@ String read_err(HttpURLConnection conn) {
try {
byte[] buf = new byte[1024];
int n = Math.max(0, conn.getErrorStream().read(buf));
- return new String(buf, 0, n, "UTF-8");
+ return new String(buf, 0, n, StandardCharsets.UTF_8);
} catch (Exception ex) {
return ex.toString();
}
@@ -253,32 +298,27 @@ private void do_textmsg(HttpURLConnection conn) throws Exception {
os.write(body);
os.flush();
int rc = conn.getResponseCode();
- share_url = "HTTP " + rc;
if (rc >= 300) {
tshow_msg("Server error " + rc + ":\n" + read_err(conn));
conn.disconnect();
return;
}
conn.disconnect();
- findViewById(R.id.upper_info).post(new Runnable() {
- @Override
- public void run() {
- onsuccess(false);
- }
- });
}
- private void do_fileput(HttpURLConnection conn) throws Exception {
+ @SuppressLint("DefaultLocale")
+ private boolean do_fileput(HttpURLConnection conn, int nfile) throws Exception {
+ F f = files[nfile];
conn.setRequestMethod("PUT");
- conn.setFixedLengthStreamingMode(src_size);
+ conn.setFixedLengthStreamingMode(f.size);
conn.setRequestProperty("Content-Type", "application/octet-stream");
conn.connect();
final TextView tv = (TextView) findViewById(R.id.upper_info);
OutputStream os = conn.getOutputStream();
- InputStream ins = getContentResolver().openInputStream(src_uri);
+ InputStream ins = getContentResolver().openInputStream(f.handle);
MessageDigest md = MessageDigest.getInstance("SHA-512");
byte[] buf = new byte[128 * 1024];
- long bytes_done = 0;
+ assert ins != null;
while (true) {
int n = ins.read(buf);
if (n <= 0)
@@ -288,20 +328,18 @@ private void do_fileput(HttpURLConnection conn) throws Exception {
os.write(buf, 0, n);
md.update(buf, 0, n);
- final long meme = bytes_done;
- tv.post(new Runnable() {
- @Override
- public void run() {
- double perc = ((double) meme * 100) / src_size;
- tv.setText(String.format("Sending to %s ...\n\n%s\n\nbytes done: %,d\nbytes left: %,d\nprogress: %.2f %%",
- base_url,
- the_desc,
- meme,
- src_size - meme,
- perc
- ));
- ((ProgressBar) findViewById(R.id.progbar)).setProgress((int) Math.round(perc));
- }
+ tv.post(() -> {
+ double perc = ((double) bytes_done * 100) / bytes_total;
+ tv.setText(format("Sending %d of %d to %s ...\n\n%s\n\nbytes done: %,d\nbytes left: %,d\nprogress: %.2f %%",
+ nfile + 1,
+ files.length,
+ base_url,
+ f.desc,
+ bytes_done,
+ bytes_total - bytes_done,
+ perc
+ ));
+ ((ProgressBar) findViewById(R.id.progbar)).setProgress((int) Math.round(perc));
});
}
os.flush();
@@ -309,44 +347,46 @@ public void run() {
if (rc >= 300) {
tshow_msg("Server error " + rc + ":\n" + read_err(conn));
conn.disconnect();
- return;
+ return false;
}
String sha = "";
byte[] bsha = md.digest();
for (int a = 0; a < 28; a++)
- sha += String.format("%02x", bsha[a]);
+ sha += format("%02x", bsha[a]);
BufferedReader br = new BufferedReader(new InputStreamReader(conn.getInputStream()));
- String[] lines = br.lines().collect(Collectors.toList()).toArray(new String[0]);
+ String[] lines = br.lines().toArray(String[]::new);
conn.disconnect();
if (lines.length < 3) {
tshow_msg("SERVER ERROR:\n" + lines[0]);
- return;
+ return false;
}
if (lines[2].indexOf(sha) != 0) {
tshow_msg("ERROR:\nFile got corrupted during the upload;\n\n" + lines[2] + " expected\n" + sha + " from server");
- return;
+ return false;
}
if (lines.length > 3 && !lines[3].isEmpty())
- share_url = lines[3];
+ f.share_url = lines[3];
else
- share_url = full_url.split("\\?")[0];
+ f.share_url = f.full_url.split("\\?")[0];
- tv.post(new Runnable() {
- @Override
- public void run() {
- onsuccess(true);
- }
- });
+ return true;
}
- void onsuccess(Boolean upload) {
- show_msg("✅ 👍\n\nCompleted successfully\n\n" + share_url);
+ void onsuccess() {
+ String msg = "✅ 👍\n\nCompleted successfully";
+ if (files != null) {
+ if (files.length == 1)
+ msg += "\n\n" + files[0].share_url;
+ else
+ msg += "\n\n" + files.length + " files OK";
+ }
+ show_msg(msg);
((TextView)findViewById(R.id.upper_info)).setGravity(Gravity.CENTER);
String act = prefs.getString("on_up_ok", "menu");
- if (!act.equals("menu")) {
+ if (act != null && !act.equals("menu")) {
if (act.equals("copy"))
copylink();
else if (act.equals("share"))
@@ -362,52 +402,49 @@ else if (act.equals("share"))
findViewById(R.id.successbuttons).setVisibility(View.VISIBLE);
Button btn = (Button)findViewById(R.id.btnExit);
- btn.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- finishAndRemoveTask();
- }
- });
+ btn.setOnClickListener(v -> finishAndRemoveTask());
- if (!upload) {
- findViewById(R.id.btnCopyLink).setVisibility(View.GONE);
- findViewById(R.id.btnShareLink).setVisibility(View.GONE);
+ Button vcopy = (Button)findViewById(R.id.btnCopyLink);
+ Button vshare = (Button)findViewById(R.id.btnShareLink);
+ if (files == null) {
+ vcopy.setVisibility(View.GONE);
+ vshare.setVisibility(View.GONE);
return;
}
-
- btn = (Button)findViewById(R.id.btnCopyLink);
- btn.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- copylink();
- }
- });
-
- btn = (Button)findViewById(R.id.btnShareLink);
- btn.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- sharelink();
- }
- });
+ vcopy.setOnClickListener(v -> copylink());
+ vshare.setOnClickListener(v -> sharelink());
+ if (files.length > 1)
+ vshare.setVisibility(View.GONE);
}
void copylink() {
+ if (files == null)
+ return;
+
+ String links = "";
+ for (F file : files)
+ links += file.share_url + "\n";
+
ClipboardManager cb = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE);
- ClipData cd = ClipData.newPlainText("copyparty upload", share_url);
+ ClipData cd = ClipData.newPlainText("copyparty upload", links);
+ cb.setPrimaryClip(cd);
Toast.makeText(getApplicationContext(), "Upload OK -- Link copied", Toast.LENGTH_SHORT).show();
}
void sharelink() {
+ if (files == null || files.length > 1)
+ return;
+
+ F f = files[0];
Intent send = new Intent(Intent.ACTION_SEND);
send.setType("text/plain");
send.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
send.putExtra(Intent.EXTRA_SUBJECT, "Uploaded file");
- send.putExtra(Intent.EXTRA_TEXT, share_url);
+ send.putExtra(Intent.EXTRA_TEXT, f.share_url);
//startActivity(Intent.createChooser(send, "Share file link"));
Intent view = new Intent(Intent.ACTION_VIEW);
- view.setData(Uri.parse(share_url));
+ view.setData(Uri.parse(f.share_url));
Intent i = Intent.createChooser(send, "Share file link");
i.putExtra(Intent.EXTRA_INITIAL_INTENTS, new Intent[] { view });
diff --git a/metadata/en-US/changelogs/10600.txt b/metadata/en-US/changelogs/10600.txt
index 0041b61..a9670ab 100644
--- a/metadata/en-US/changelogs/10600.txt
+++ b/metadata/en-US/changelogs/10600.txt
@@ -1,2 +1,4 @@
+• new: upload multiple files
• new: choose what happens when an upload succeeds
• new: add http:// to the server URL if missing
+• bugfix: sharing from some apps in android 10+ could crash this app
\ No newline at end of file