From 07c94f268b8ad2fbfd9ffd1f57c7dcb06f2dd0ed Mon Sep 17 00:00:00 2001 From: maxiaohong Date: Sat, 17 Mar 2018 10:36:31 +0800 Subject: [PATCH] replace upload file network libs fix crash when upload large file --- app/build.gradle | 1 + .../com/seafile/seadroid2/SeafConnection.java | 324 +++---------- .../seafile/seadroid2/data/DataManager.java | 7 +- .../seadroid2/httputils/NoSSLv3Factory.java | 436 ++++++++++++++++++ .../seadroid2/httputils/RequestManager.java | 123 +++++ .../seadroid2/provider/SeafileProvider.java | 6 +- .../ui/activity/BrowserActivity.java | 63 +-- .../seafile/seadroid2/util/UriFilePath.java | 117 +++++ 8 files changed, 767 insertions(+), 310 deletions(-) create mode 100644 app/src/main/java/com/seafile/seadroid2/httputils/NoSSLv3Factory.java create mode 100644 app/src/main/java/com/seafile/seadroid2/httputils/RequestManager.java create mode 100644 app/src/main/java/com/seafile/seadroid2/util/UriFilePath.java diff --git a/app/build.gradle b/app/build.gradle index fc1b4bf2b..6f19c0d21 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -109,6 +109,7 @@ android { compile 'com.madgag.spongycastle:prov:1.54.0.0' compile 'com.shuyu:gsyVideoPlayer-java:2.1.0' compile 'com.shuyu:gsyVideoPlayer-ex_so:2.1.0' + compile 'com.squareup.okhttp3:okhttp:3.9.1' } } diff --git a/app/src/main/java/com/seafile/seadroid2/SeafConnection.java b/app/src/main/java/com/seafile/seadroid2/SeafConnection.java index ea5ae908c..6f64d609e 100644 --- a/app/src/main/java/com/seafile/seadroid2/SeafConnection.java +++ b/app/src/main/java/com/seafile/seadroid2/SeafConnection.java @@ -17,6 +17,7 @@ import com.seafile.seadroid2.data.DataManager; import com.seafile.seadroid2.data.FileBlocks; import com.seafile.seadroid2.data.ProgressMonitor; +import com.seafile.seadroid2.httputils.RequestManager; import com.seafile.seadroid2.ssl.SSLTrustManager; import com.seafile.seadroid2.util.Utils; @@ -38,6 +39,11 @@ import javax.net.ssl.HttpsURLConnection; import javax.net.ssl.SSLHandshakeException; +import okhttp3.MultipartBody; +import okhttp3.Request; +import okhttp3.RequestBody; +import okhttp3.Response; + /** * SeafConnection encapsulates Seafile Web API * @author plt @@ -538,16 +544,9 @@ public Pair getBlock(String repoID, } public String uploadByBlocks(String repoID, String dir, String filePath, List blocks, boolean update, ProgressMonitor monitor) throws IOException, SeafException { - try { String url = getUploadLink(repoID, update, true); Log.d(DEBUG_TAG, "UploadLink " + url); return uploadBlocksCommon(url, repoID, dir, filePath, blocks, monitor, update); - } catch (SeafException e) { - // do again - String url = getUploadLink(repoID, update, true); - Log.d(DEBUG_TAG, "do again UploadLink " + url); - return uploadBlocksCommon(url, repoID, dir, filePath, blocks, monitor, update); - } } private File getFileFromLink(String dlink, String path, String localPath, @@ -787,288 +786,69 @@ private String getUploadLink(String repoID, boolean update, boolean byblock) thr * @throws SeafException */ public String uploadFile(String repoID, String dir, String filePath, ProgressMonitor monitor, boolean update) - throws SeafException { - try { - String url = getUploadLink(repoID, update); - return uploadFileCommon(url, repoID, dir, filePath, monitor, update); - } catch (SeafException e) { - // do again + throws SeafException, IOException { String url = getUploadLink(repoID, update); return uploadFileCommon(url, repoID, dir, filePath, monitor, update); - } } - private static final String CRLF = "\r\n"; - private static final String TWO_HYPENS = "--"; - private static final String BOUNDARY = "----SeafileAndroidBound$_$"; /** * Upload a file to seafile httpserver */ private String uploadFileCommon(String link, String repoID, String dir, String filePath, ProgressMonitor monitor, boolean update) - throws SeafException { - - try { - File file = new File(filePath); - if (!file.exists()) { - throw new SeafException(SeafException.OTHER_EXCEPTION, "File not exists"); - } - - - HttpRequest req = HttpRequest.post(link, null, false).followRedirects(true).connectTimeout(CONNECTION_TIMEOUT); - - prepareHttpsCheck(req); - - /** - * We have to set the content-length header, otherwise the whole - * request would be buffered by android. So we have to format the - * multipart form-data request ourselves in order to calculate the - * content length. - */ - int totalLen = 0; - byte[] dirParam = {}; - byte[] targetFileParam = {}; - StringBuilder builder; - - if (update) { - // the "target_file" param is for update file api - builder = new StringBuilder(); - // line 1, ------SeafileAndroidBound$_$ - builder.append(TWO_HYPENS + BOUNDARY + CRLF); - // line 2 - builder.append("Content-Disposition: form-data; name=\"target_file\"" + CRLF); - // line 3, an empty line - builder.append(CRLF); - String targetFilePath = Utils.pathJoin(dir, file.getName()); - // line 4 - builder.append(targetFilePath + CRLF); - targetFileParam = builder.toString().getBytes("UTF-8"); - totalLen += targetFileParam.length; - } else { - // the "parent_dir" param is for upload file api - builder = new StringBuilder(); - // line 1, ------SeafileAndroidBound$_$ - builder.append(TWO_HYPENS + BOUNDARY + CRLF); - // line 2 - builder.append("Content-Disposition: form-data; name=\"parent_dir\"" + CRLF); - // line 3, an empty line - builder.append(CRLF); - // line 4 - builder.append(dir + CRLF); - dirParam = builder.toString().getBytes("UTF-8"); - totalLen += dirParam.length; - } - - // line 1 - String l1 = TWO_HYPENS + BOUNDARY + CRLF; - // line 2, - String contentDisposition = "Content-Disposition: form-data; name=\"file\";filename=\"" + file.getName() + "\"" + CRLF; - byte[] l2 = contentDisposition.getBytes("UTF-8"); - // line 3 - String l3 = "Content-Type: text/plain" + CRLF; - // line 4 - String l4 = CRLF; - totalLen += l1.length() + l2.length + l3.length() + l4.length() + file.length() + 2; - - String end = TWO_HYPENS + BOUNDARY + TWO_HYPENS + CRLF; - totalLen += end.length(); - - if (totalLen >= 0) - req.contentLength(totalLen); - req.header("Connection", "close"); - req.header("Cache-Control", "no-cache"); - req.header("Content-Type", "multipart/form-data;boundary=" + BOUNDARY); - - if (update) { - req.send(targetFileParam); - } else { - req.send(dirParam); - } - req.send(l1); - req.send(l2); - req.send(l3); - req.send(l4); - - if (monitor != null) { - req.bufferSize(MonitoredFileInputStream.BUFFER_SIZE); - req.send(new MonitoredFileInputStream(file, monitor)); - } else { - req.send(new FileInputStream(file)); - } - - req.send(CRLF); - req.send(end); - - checkRequestResponseStatus(req, HttpURLConnection.HTTP_OK); - - return new String(req.bytes(), "UTF-8"); - } catch (IOException e) { - throw SeafException.networkException; - } catch (HttpRequestException e) { - if (e.getCause() instanceof MonitorCancelledException) { - Log.d(DEBUG_TAG, "upload is cancelled"); - throw SeafException.userCancelledException; - } else { - throw getSeafExceptionFromHttpRequestException(e); - } + throws SeafException, IOException { + File file = new File(filePath); + if (!file.exists()) { + throw new SeafException(SeafException.OTHER_EXCEPTION, "File not exists"); + } + MultipartBody.Builder builder = new MultipartBody.Builder(); + //set type + builder.setType(MultipartBody.FORM); + if (update) { + builder.addFormDataPart("target_file", dir + "/"); + } else { + builder.addFormDataPart("parent_dir", dir + "/"); } + + builder.addFormDataPart("file", file.getName(), RequestManager.getInstance().createProgressRequestBody(monitor, file)); + //create RequestBody + RequestBody body = builder.build(); + //create Request + final Request request = new Request.Builder().url(link).post(body).header("Authorization", "Token " + account.token).build(); + Response execute = RequestManager.getInstance().getClient().newCall(request).execute(); + return execute.body().string(); } /** * Upload file blocks to server */ - private String uploadBlocksCommon(String link, String repoID, String dir, - String filePath, List blocks, - ProgressMonitor monitor, boolean update) - throws SeafException { - - try { - File file = new File(filePath); - if (!file.exists()) { - throw new SeafException(SeafException.OTHER_EXCEPTION, "File not exists"); - } - - HttpRequest req = HttpRequest.post(link, null, false).followRedirects(true).connectTimeout(CONNECTION_TIMEOUT); - - prepareHttpsCheck(req); - - /** - * We have to set the content-length header, otherwise the whole - * request would be buffered by android. So we have to format the - * multipart form-data request ourselves in order to calculate the - * content length. - */ - int totalLen = 0; - byte[] dirParam = {}; - StringBuilder updateBuilder = new StringBuilder(); - if (update) { - // line 1, ------SeafileAndroidBound$_$ - updateBuilder.append(TWO_HYPENS + BOUNDARY + CRLF); - // line 2 - updateBuilder.append("Content-Disposition: form-data; name=\"replace\"" + CRLF); - // line 3 - updateBuilder.append(CRLF); - // line 4 - updateBuilder.append("1" + CRLF); - dirParam = updateBuilder.toString().getBytes("UTF-8"); - totalLen += dirParam.length; - } - - StringBuilder parentDirBuilder = new StringBuilder(); - // line 1, ------SeafileAndroidBound$_$ - parentDirBuilder.append(TWO_HYPENS + BOUNDARY + CRLF); - // line 2 - parentDirBuilder.append("Content-Disposition: form-data; name=\"parent_dir\"" + CRLF); - // line 3 - parentDirBuilder.append(CRLF); - // line 4 - parentDirBuilder.append(dir + CRLF); - totalLen += parentDirBuilder.toString().getBytes("UTF-8").length; - - StringBuilder fileNameBuilder = new StringBuilder(); - // line 1, ------SeafileAndroidBound$_$ - fileNameBuilder.append(TWO_HYPENS + BOUNDARY + CRLF); - // line 2 - fileNameBuilder.append("Content-Disposition: form-data; name=\"file_name\"" + CRLF); - // line 3 - fileNameBuilder.append(CRLF); - // line 4 - fileNameBuilder.append(file.getName() + CRLF); - totalLen += fileNameBuilder.toString().getBytes("UTF-8").length; - - StringBuilder fileSizeBuilder = new StringBuilder(); - // line 1, ------SeafileAndroidBound$_$ - fileSizeBuilder.append(TWO_HYPENS + BOUNDARY + CRLF); - // line 2 - fileSizeBuilder.append("Content-Disposition: form-data; name=\"file_size\"" + CRLF); - // line 3 - fileSizeBuilder.append(CRLF); - // line 4 - fileSizeBuilder.append(file.length() + CRLF); - totalLen += fileSizeBuilder.toString().getBytes("UTF-8").length; - - for (Block block : blocks) { - // line 1 - String l1 = TWO_HYPENS + BOUNDARY + CRLF; - - File blk = new File(block.path); - - // line 2 - String contentDisposition = "Content-Disposition: form-data; name=\"file\";filename=\"" + blk.getName() + "\"" + CRLF; - byte[] l2 = contentDisposition.getBytes("UTF-8"); - - // line 3 - String l3 = "Content-Type: text/plain" + CRLF; - - // line 4 - String l4 = CRLF; - totalLen += l1.length() + l2.length + l3.length() + l4.length() + blk.length() + 2; - } - - String end = TWO_HYPENS + BOUNDARY + TWO_HYPENS + CRLF; - totalLen += end.getBytes().length; - - req.contentLength(totalLen); - req.header("Connection", "Keep-Alive"); - req.header("Cache-Control", "no-cache"); - req.header("Content-Type", "multipart/form-data;boundary=" + BOUNDARY); - - if (update) { - req.send(updateBuilder); - Log.d(DEBUG_TAG, updateBuilder.toString()); - } - - req.send(parentDirBuilder); - req.send(fileNameBuilder); - req.send(fileSizeBuilder); - - for (Block block : blocks) { - // line 1 - String l1 = TWO_HYPENS + BOUNDARY + CRLF; - - File blk = new File(block.path); - - // line 2 - String contentDisposition = "Content-Disposition: form-data; name=\"file\";filename=\"" + blk.getName() + "\"" + CRLF; - byte[] l2 = contentDisposition.getBytes("UTF-8"); - - // line 3 - String l3 = "Content-Type: text/plain" + CRLF; - - // line 4 - String l4 = CRLF; - totalLen += l1.length() + l2.length + l3.length() + l4.length() + blk.length() + 2; - - StringBuilder chunkReq = new StringBuilder(); - chunkReq.append(l1).append(contentDisposition).append(l3).append(l4); - req.send(chunkReq); - - if (monitor != null) { - req.bufferSize(MonitoredFileInputStream.BUFFER_SIZE); - req.send(new MonitoredFileInputStream(blk, monitor)); - } else { - req.send(new FileInputStream(blk)); - } - req.send(CRLF); - } - - req.send(end); - - checkRequestResponseStatus(req, HttpURLConnection.HTTP_OK); - - //result file_id "3f0da9a0709c5fb9f23957608dabef01becc3a8c" - return new String(req.bytes(), "UTF-8").replaceAll("\"", ""); - } catch (IOException e) { - throw SeafException.networkException; - } catch (HttpRequestException e) { - if (e.getCause() instanceof MonitorCancelledException) { - Log.d(DEBUG_TAG, "upload is cancelled"); - throw SeafException.userCancelledException; - } else { - throw getSeafExceptionFromHttpRequestException(e); - } + private String uploadBlocksCommon(String link, String repoID, String dir, String filePath, List blocks, ProgressMonitor + monitor, boolean update) + throws SeafException, IOException { + File file = new File(filePath); + if (!file.exists()) { + throw new SeafException(SeafException.OTHER_EXCEPTION, "File not exists"); + } + MultipartBody.Builder builder = new MultipartBody.Builder(); + //set type + builder.setType(MultipartBody.FORM); + //set header ,to replace file + if (update) { + builder.addFormDataPart("replace", "1"); } + builder.addFormDataPart("parent_dir", dir); + builder.addFormDataPart("file_name", file.getName()); + builder.addFormDataPart("file_size", file.length() + ""); + for (Block block : blocks) { + File blk = new File(block.path); + builder.addFormDataPart("file", blk.getName(), RequestManager.getInstance().createProgressRequestBody(monitor, blk)); + } + + RequestBody body = builder.build(); + final Request request = new Request.Builder().url(link).post(body).header("Authorization", "Token " + account.token).build(); + Response execute = RequestManager.getInstance().getClient().newCall(request).execute(); + return execute.body().string(); } public void createNewRepo(String repoName, String description, String password) throws SeafException { diff --git a/app/src/main/java/com/seafile/seadroid2/data/DataManager.java b/app/src/main/java/com/seafile/seadroid2/data/DataManager.java index ffadaa1b0..80b00ce6a 100644 --- a/app/src/main/java/com/seafile/seadroid2/data/DataManager.java +++ b/app/src/main/java/com/seafile/seadroid2/data/DataManager.java @@ -628,15 +628,14 @@ public void setPassword(String repoID, String passwd) throws SeafException { } public void uploadFile(String repoName, String repoID, String dir, String filePath, - ProgressMonitor monitor, boolean isUpdate, boolean isCopyToLocal) throws SeafException { + ProgressMonitor monitor, boolean isUpdate, boolean isCopyToLocal) throws SeafException, IOException { uploadFileCommon(repoName, repoID, dir, filePath, monitor, isUpdate, isCopyToLocal); } private void uploadFileCommon(String repoName, String repoID, String dir, String filePath, ProgressMonitor monitor, - boolean isUpdate, boolean isCopyToLocal) throws SeafException { - String newFileID = sc.uploadFile(repoID, dir, filePath, monitor,isUpdate); - + boolean isUpdate, boolean isCopyToLocal) throws SeafException, IOException { + String newFileID = sc.uploadFile(repoID, dir, filePath, monitor,isUpdate); if (newFileID == null || newFileID.length() == 0) { return; } diff --git a/app/src/main/java/com/seafile/seadroid2/httputils/NoSSLv3Factory.java b/app/src/main/java/com/seafile/seadroid2/httputils/NoSSLv3Factory.java new file mode 100644 index 000000000..ac68c1e74 --- /dev/null +++ b/app/src/main/java/com/seafile/seadroid2/httputils/NoSSLv3Factory.java @@ -0,0 +1,436 @@ +package com.seafile.seadroid2.httputils; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.net.InetAddress; +import java.net.Socket; +import java.net.SocketAddress; +import java.net.SocketException; +import java.nio.channels.SocketChannel; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import javax.net.ssl.HandshakeCompletedListener; +import javax.net.ssl.HttpsURLConnection; +import javax.net.ssl.SSLParameters; +import javax.net.ssl.SSLSession; +import javax.net.ssl.SSLSocket; +import javax.net.ssl.SSLSocketFactory; + +/** + * code by https://stackoverflow.com/questions/26633349/disable-ssl-as-a-protocol-in-httpsurlconnection + * {@link javax.net.ssl.SSLSocketFactory} that doesn't allow {@code SSLv3} only connections + *

fixes https://github.com/koush/ion/issues/386

+ *

+ *

see https://code.google.com/p/android/issues/detail?id=78187

+ */ +public class NoSSLv3Factory extends SSLSocketFactory { + private final SSLSocketFactory delegate; + + public NoSSLv3Factory() { + this.delegate = HttpsURLConnection.getDefaultSSLSocketFactory(); + } + + @Override + public String[] getDefaultCipherSuites() { + return delegate.getDefaultCipherSuites(); + } + + @Override + public String[] getSupportedCipherSuites() { + return delegate.getSupportedCipherSuites(); + } + + private static Socket makeSocketSafe(Socket socket) { + if (socket instanceof SSLSocket) { + socket = new NoSSLv3SSLSocket((SSLSocket) socket); + } + return socket; + } + + @Override + public Socket createSocket(Socket s, String host, int port, boolean autoClose) throws IOException { + return makeSocketSafe(delegate.createSocket(s, host, port, autoClose)); + } + + @Override + public Socket createSocket(String host, int port) throws IOException { + return makeSocketSafe(delegate.createSocket(host, port)); + } + + @Override + public Socket createSocket(String host, int port, InetAddress localHost, int localPort) throws IOException { + return makeSocketSafe(delegate.createSocket(host, port, localHost, localPort)); + } + + @Override + public Socket createSocket(InetAddress host, int port) throws IOException { + return makeSocketSafe(delegate.createSocket(host, port)); + } + + @Override + public Socket createSocket(InetAddress address, int port, InetAddress localAddress, int localPort) throws IOException { + return makeSocketSafe(delegate.createSocket(address, port, localAddress, localPort)); + } + + /** + * Created by robUx4 on 25/10/2014. + */ + private static class DelegateSSLSocket extends SSLSocket { + + protected final SSLSocket delegate; + + DelegateSSLSocket(SSLSocket delegate) { + this.delegate = delegate; + } + + @Override + public String[] getSupportedCipherSuites() { + return delegate.getSupportedCipherSuites(); + } + + @Override + public String[] getEnabledCipherSuites() { + return delegate.getEnabledCipherSuites(); + } + + @Override + public void setEnabledCipherSuites(String[] suites) { + delegate.setEnabledCipherSuites(suites); + } + + @Override + public String[] getSupportedProtocols() { + return delegate.getSupportedProtocols(); + } + + @Override + public String[] getEnabledProtocols() { + return delegate.getEnabledProtocols(); + } + + @Override + public void setEnabledProtocols(String[] protocols) { + delegate.setEnabledProtocols(protocols); + } + + @Override + public SSLSession getSession() { + return delegate.getSession(); + } + + @Override + public void addHandshakeCompletedListener(HandshakeCompletedListener listener) { + delegate.addHandshakeCompletedListener(listener); + } + + @Override + public void removeHandshakeCompletedListener(HandshakeCompletedListener listener) { + delegate.removeHandshakeCompletedListener(listener); + } + + @Override + public void startHandshake() throws IOException { + delegate.startHandshake(); + } + + @Override + public void setUseClientMode(boolean mode) { + delegate.setUseClientMode(mode); + } + + @Override + public boolean getUseClientMode() { + return delegate.getUseClientMode(); + } + + @Override + public void setNeedClientAuth(boolean need) { + delegate.setNeedClientAuth(need); + } + + @Override + public void setWantClientAuth(boolean want) { + delegate.setWantClientAuth(want); + } + + @Override + public boolean getNeedClientAuth() { + return delegate.getNeedClientAuth(); + } + + @Override + public boolean getWantClientAuth() { + return delegate.getWantClientAuth(); + } + + @Override + public void setEnableSessionCreation(boolean flag) { + delegate.setEnableSessionCreation(flag); + } + + @Override + public boolean getEnableSessionCreation() { + return delegate.getEnableSessionCreation(); + } + + @Override + public void bind(SocketAddress localAddr) throws IOException { + delegate.bind(localAddr); + } + + @Override + public synchronized void close() throws IOException { + delegate.close(); + } + + @Override + public void connect(SocketAddress remoteAddr) throws IOException { + delegate.connect(remoteAddr); + } + + @Override + public void connect(SocketAddress remoteAddr, int timeout) throws IOException { + delegate.connect(remoteAddr, timeout); + } + + @Override + public SocketChannel getChannel() { + return delegate.getChannel(); + } + + @Override + public InetAddress getInetAddress() { + return delegate.getInetAddress(); + } + + @Override + public InputStream getInputStream() throws IOException { + return delegate.getInputStream(); + } + + @Override + public boolean getKeepAlive() throws SocketException { + return delegate.getKeepAlive(); + } + + @Override + public InetAddress getLocalAddress() { + return delegate.getLocalAddress(); + } + + @Override + public int getLocalPort() { + return delegate.getLocalPort(); + } + + @Override + public SocketAddress getLocalSocketAddress() { + return delegate.getLocalSocketAddress(); + } + + @Override + public boolean getOOBInline() throws SocketException { + return delegate.getOOBInline(); + } + + @Override + public OutputStream getOutputStream() throws IOException { + return delegate.getOutputStream(); + } + + @Override + public int getPort() { + return delegate.getPort(); + } + + @Override + public synchronized int getReceiveBufferSize() throws SocketException { + return delegate.getReceiveBufferSize(); + } + + @Override + public SocketAddress getRemoteSocketAddress() { + return delegate.getRemoteSocketAddress(); + } + + @Override + public boolean getReuseAddress() throws SocketException { + return delegate.getReuseAddress(); + } + + @Override + public synchronized int getSendBufferSize() throws SocketException { + return delegate.getSendBufferSize(); + } + + @Override + public int getSoLinger() throws SocketException { + return delegate.getSoLinger(); + } + + @Override + public synchronized int getSoTimeout() throws SocketException { + return delegate.getSoTimeout(); + } + + @Override + public boolean getTcpNoDelay() throws SocketException { + return delegate.getTcpNoDelay(); + } + + @Override + public int getTrafficClass() throws SocketException { + return delegate.getTrafficClass(); + } + + @Override + public boolean isBound() { + return delegate.isBound(); + } + + @Override + public boolean isClosed() { + return delegate.isClosed(); + } + + @Override + public boolean isConnected() { + return delegate.isConnected(); + } + + @Override + public boolean isInputShutdown() { + return delegate.isInputShutdown(); + } + + @Override + public boolean isOutputShutdown() { + return delegate.isOutputShutdown(); + } + + @Override + public void sendUrgentData(int value) throws IOException { + delegate.sendUrgentData(value); + } + + @Override + public void setKeepAlive(boolean keepAlive) throws SocketException { + delegate.setKeepAlive(keepAlive); + } + + @Override + public void setOOBInline(boolean oobinline) throws SocketException { + delegate.setOOBInline(oobinline); + } + + @Override + public void setPerformancePreferences(int connectionTime, int latency, int bandwidth) { + delegate.setPerformancePreferences(connectionTime, latency, bandwidth); + } + + @Override + public synchronized void setReceiveBufferSize(int size) throws SocketException { + delegate.setReceiveBufferSize(size); + } + + @Override + public void setReuseAddress(boolean reuse) throws SocketException { + delegate.setReuseAddress(reuse); + } + + @Override + public synchronized void setSendBufferSize(int size) throws SocketException { + delegate.setSendBufferSize(size); + } + + @Override + public void setSoLinger(boolean on, int timeout) throws SocketException { + delegate.setSoLinger(on, timeout); + } + + @Override + public synchronized void setSoTimeout(int timeout) throws SocketException { + delegate.setSoTimeout(timeout); + } + + @Override + public void setSSLParameters(SSLParameters p) { + delegate.setSSLParameters(p); + } + + @Override + public void setTcpNoDelay(boolean on) throws SocketException { + delegate.setTcpNoDelay(on); + } + + @Override + public void setTrafficClass(int value) throws SocketException { + delegate.setTrafficClass(value); + } + + @Override + public void shutdownInput() throws IOException { + delegate.shutdownInput(); + } + + @Override + public void shutdownOutput() throws IOException { + delegate.shutdownOutput(); + } + + @Override + public String toString() { + return delegate.toString(); + } + + @Override + public boolean equals(Object o) { + return delegate.equals(o); + } + } + + /** + * An {@link javax.net.ssl.SSLSocket} that doesn't allow {@code SSLv3} only connections + *

fixes https://github.com/koush/ion/issues/386

+ */ + private static class NoSSLv3SSLSocket extends DelegateSSLSocket { + + private NoSSLv3SSLSocket(SSLSocket delegate) { + super(delegate); + + String canonicalName = delegate.getClass().getCanonicalName(); + if (!canonicalName.equals("org.apache.harmony.xnet.provider.jsse.OpenSSLSocketImpl")) { + // try replicate the code from HttpConnection.setupSecureSocket() + try { + Method msetUseSessionTickets = delegate.getClass().getMethod("setUseSessionTickets", boolean.class); + if (null != msetUseSessionTickets) { + msetUseSessionTickets.invoke(delegate, true); + } + } catch (NoSuchMethodException ignored) { + } catch (InvocationTargetException ignored) { + } catch (IllegalAccessException ignored) { + } + } + } + + @Override + public void setEnabledProtocols(String[] protocols) { + if (protocols != null && protocols.length == 1 && "SSLv3".equals(protocols[0])) { + // no way jose + // see issue https://code.google.com/p/android/issues/detail?id=78187 + List enabledProtocols = new ArrayList(Arrays.asList(delegate.getEnabledProtocols())); + if (enabledProtocols.size() > 1) { + enabledProtocols.remove("SSLv3"); + } + protocols = enabledProtocols.toArray(new String[enabledProtocols.size()]); + } + super.setEnabledProtocols(protocols); + } + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/seafile/seadroid2/httputils/RequestManager.java b/app/src/main/java/com/seafile/seadroid2/httputils/RequestManager.java new file mode 100644 index 000000000..bd905af6f --- /dev/null +++ b/app/src/main/java/com/seafile/seadroid2/httputils/RequestManager.java @@ -0,0 +1,123 @@ +package com.seafile.seadroid2.httputils; + +import android.util.Log; + +import com.seafile.seadroid2.data.ProgressMonitor; + +import java.io.File; +import java.io.IOException; +import java.util.concurrent.TimeUnit; + +import javax.net.ssl.HttpsURLConnection; + +import okhttp3.Interceptor; +import okhttp3.MediaType; +import okhttp3.OkHttpClient; +import okhttp3.RequestBody; +import okhttp3.Response; +import okio.Buffer; +import okio.BufferedSink; +import okio.Okio; +import okio.Source; + + +public class RequestManager { + + private OkHttpClient client; + private static final long TIMEOUT_COUNT = 5; + + static { + HttpsURLConnection.setDefaultSSLSocketFactory(new NoSSLv3Factory()); + } + + private RequestManager() { + OkHttpClient.Builder builder = new OkHttpClient.Builder(); + builder.addInterceptor(new LoggingInterceptor()); //add okhttp log + client = builder.hostnameVerifier((hostname, session) -> true) + .retryOnConnectionFailure(true) + .connectTimeout(TIMEOUT_COUNT, TimeUnit.MINUTES) + .readTimeout(TIMEOUT_COUNT, TimeUnit.MINUTES) + .writeTimeout(TIMEOUT_COUNT, TimeUnit.MINUTES) + .build(); + } + + public OkHttpClient getClient() { + return client; + } + + + private static class SingletonFactory { + private static RequestManager mRequestManager = new RequestManager(); + } + + public static RequestManager getInstance() { + return SingletonFactory.mRequestManager; + } + + + /** + * output log interceptor + */ + private class LoggingInterceptor implements Interceptor { + + @Override + public Response intercept(Chain chain) throws IOException { + Response response = chain.proceed(chain.request()); + String content = response.body().string(); + Log.i("LoggingInterceptor", content); + return response.newBuilder() + .body(okhttp3.ResponseBody.create(response.body().contentType(), content)) + .build(); + } + } + + + /** + * Create progress RequestBody + * + * @param contentType MediaType + * @param file update file + * @param callBack + * @param + * @return + */ + public RequestBody createProgressRequestBody(ProgressMonitor monitor, final File file) { + return new RequestBody() { + + public long temp = System.currentTimeMillis(); + + @Override + public MediaType contentType() { + return MediaType.parse("application/octet-stream"); + } + + @Override + public long contentLength() { + return file.length(); + } + + @Override + public void writeTo(BufferedSink sink) throws IOException { + Source source; + try { + source = Okio.source(file); + Buffer buf = new Buffer(); + // long remaining = contentLength(); + long current = 0; + for (long readCount; (readCount = source.read(buf, 2048)) != -1; ) { + sink.write(buf, readCount); + current += readCount; + long nowt = System.currentTimeMillis(); + // 1s refresh progress + if (nowt - temp >= 1000) { + temp = nowt; + monitor.onProgressNotify(current, false); + } + } + } catch (Exception e) { + e.printStackTrace(); + } + } + }; + } +} diff --git a/app/src/main/java/com/seafile/seadroid2/provider/SeafileProvider.java b/app/src/main/java/com/seafile/seadroid2/provider/SeafileProvider.java index d95487a37..2caa6b507 100644 --- a/app/src/main/java/com/seafile/seadroid2/provider/SeafileProvider.java +++ b/app/src/main/java/com/seafile/seadroid2/provider/SeafileProvider.java @@ -63,15 +63,11 @@ import java.util.ArrayList; import java.util.List; import java.util.Set; -import java.util.concurrent.BlockingQueue; import java.util.concurrent.Callable; import java.util.concurrent.CancellationException; import java.util.concurrent.ConcurrentSkipListSet; import java.util.concurrent.ExecutionException; import java.util.concurrent.Future; -import java.util.concurrent.LinkedBlockingQueue; -import java.util.concurrent.ThreadPoolExecutor; -import java.util.concurrent.TimeUnit; /** * DocumentProvider for the Storage Access Framework. @@ -572,6 +568,8 @@ public void run() { dm.getDirentsFromServer(repoID, parentDir); } catch (SeafException e1) { Log.d(DEBUG_TAG, "could not upload file: ", e1); + } catch (IOException e1) { + e1.printStackTrace(); } } }); diff --git a/app/src/main/java/com/seafile/seadroid2/ui/activity/BrowserActivity.java b/app/src/main/java/com/seafile/seadroid2/ui/activity/BrowserActivity.java index cbf8900eb..0d5171cb8 100644 --- a/app/src/main/java/com/seafile/seadroid2/ui/activity/BrowserActivity.java +++ b/app/src/main/java/com/seafile/seadroid2/ui/activity/BrowserActivity.java @@ -91,18 +91,15 @@ import com.seafile.seadroid2.ui.fragment.ReposFragment; import com.seafile.seadroid2.ui.fragment.StarredFragment; import com.seafile.seadroid2.util.ConcurrentAsyncTask; +import com.seafile.seadroid2.util.UriFilePath; import com.seafile.seadroid2.util.Utils; import com.seafile.seadroid2.util.UtilsJellyBean; import com.viewpagerindicator.IconPagerAdapter; -import org.apache.commons.io.IOUtils; import org.json.JSONException; import java.io.File; -import java.io.FileOutputStream; import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Date; @@ -1395,32 +1392,37 @@ protected File[] doInBackground(Uri... uriList) { List fileList = new ArrayList(); for (Uri uri: uriList) { // Log.d(DEBUG_TAG, "Uploading file from uri: " + uri); - - InputStream in = null; - OutputStream out = null; - - try { - File tempDir = DataManager.createTempDir(); - File tempFile = new File(tempDir, Utils.getFilenamefromUri(BrowserActivity.this, uri)); - - if (!tempFile.createNewFile()) { - throw new RuntimeException("could not create temporary file"); - } - - in = getContentResolver().openInputStream(uri); - out = new FileOutputStream(tempFile); - IOUtils.copy(in, out); - - fileList.add(tempFile); - - } catch (IOException e) { - Log.d(DEBUG_TAG, "Could not open requested document", e); - } catch (RuntimeException e) { - Log.d(DEBUG_TAG, "Could not open requested document", e); - } finally { - IOUtils.closeQuietly(in); - IOUtils.closeQuietly(out); - } +// ============ copy file to seafile dir =================== +// InputStream in = null; +// OutputStream out = null; +// +// try { +// File tempDir = DataManager.createTempDir(); +// File tempFile = new File(tempDir, Utils.getFilenamefromUri(BrowserActivity.this, uri)); +// +// if (!tempFile.createNewFile()) { +// throw new RuntimeException("could not create temporary file"); +// } +// +// in = getContentResolver().openInputStream(uri); +// out = new FileOutputStream(tempFile); +// IOUtils.copy(in, out); +// +// fileList.add(tempFile); +// +// } catch (IOException e) { +// Log.d(DEBUG_TAG, "Could not open requested document", e); +// } catch (RuntimeException e) { +// Log.d(DEBUG_TAG, "Could not open requested document", e); +// } finally { +// IOUtils.closeQuietly(in); +// IOUtils.closeQuietly(out); +// } +// =============== new idea,file upload no copy seafile dir ================ + String path = UriFilePath.getFileAbsolutePath(BrowserActivity.this, uri); + File tempFile = new File(path); + fileList.add(tempFile); +// =============== new idea,file upload no copy seafile dir ================ } return fileList.toArray(new File[]{}); } @@ -1854,6 +1856,7 @@ private void chooseExportApp(final String repoName, final String repoID, final S @Override public void onCustomActionSelected(CustomAction action) { } + @Override public void onAppSelected(ResolveInfo appInfo) { String className = appInfo.activityInfo.name; diff --git a/app/src/main/java/com/seafile/seadroid2/util/UriFilePath.java b/app/src/main/java/com/seafile/seadroid2/util/UriFilePath.java new file mode 100644 index 000000000..c64eceb11 --- /dev/null +++ b/app/src/main/java/com/seafile/seadroid2/util/UriFilePath.java @@ -0,0 +1,117 @@ +package com.seafile.seadroid2.util; + +import android.annotation.TargetApi; +import android.app.Activity; +import android.content.ContentUris; +import android.content.Context; +import android.database.Cursor; +import android.net.Uri; +import android.os.Environment; +import android.provider.DocumentsContract; +import android.provider.MediaStore; + +/** + * @Description: [ get file path wiht Uri ] + * @Author: [ Saud ] + * @CreateDate: [ 2018/1/20 17:06 ] + * @UpDate: [ 2018/1/20 17:06 ] + * @Version: [ v1.0 ] + */ + +public class UriFilePath { + + @TargetApi(19) + public static String getFileAbsolutePath(Activity context, Uri fileUri) { + if (context == null || fileUri == null) + return null; + if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.KITKAT && DocumentsContract.isDocumentUri(context, + fileUri)) { + if (isExternalStorageDocument(fileUri)) { + String docId = DocumentsContract.getDocumentId(fileUri); + String[] split = docId.split(":"); + String type = split[0]; + if ("primary".equalsIgnoreCase(type)) { + return Environment.getExternalStorageDirectory() + "/" + split[1]; + } + } else if (isDownloadsDocument(fileUri)) { + String id = DocumentsContract.getDocumentId(fileUri); + Uri contentUri = ContentUris.withAppendedId(Uri.parse("content://downloads/public_downloads"), Long.valueOf(id)); + return getDataColumn(context, contentUri, null, null); + } else if (isMediaDocument(fileUri)) { + String docId = DocumentsContract.getDocumentId(fileUri); + String[] split = docId.split(":"); + String type = split[0]; + Uri contentUri = null; + if ("image".equals(type)) { + contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI; + } else if ("video".equals(type)) { + contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI; + } else if ("audio".equals(type)) { + contentUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI; + } + String selection = MediaStore.Images.Media._ID + "=?"; + String[] selectionArgs = new String[]{split[1]}; + return getDataColumn(context, contentUri, selection, selectionArgs); + } + } // MediaStore (and general) + else if ("content".equalsIgnoreCase(fileUri.getScheme())) { + // Return the remote address + if (isGooglePhotosUri(fileUri)) + return fileUri.getLastPathSegment(); + return getDataColumn(context, fileUri, null, null); + } + // File + else if ("file".equalsIgnoreCase(fileUri.getScheme())) { + return fileUri.getPath(); + } + return null; + } + + public static String getDataColumn(Context context, Uri uri, String selection, String[] selectionArgs) { + Cursor cursor = null; + String[] projection = {MediaStore.Images.Media.DATA}; + try { + cursor = context.getContentResolver().query(uri, projection, selection, selectionArgs, null); + if (cursor != null && cursor.moveToFirst()) { + int index = cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATA); + return cursor.getString(index); + } + } finally { + if (cursor != null) + cursor.close(); + } + return null; + } + + /** + * @param uri The Uri to check. + * @return Whether the Uri authority is ExternalStorageProvider. + */ + public static boolean isExternalStorageDocument(Uri uri) { + return "com.android.externalstorage.documents".equals(uri.getAuthority()); + } + + /** + * @param uri The Uri to check. + * @return Whether the Uri authority is DownloadsProvider. + */ + public static boolean isDownloadsDocument(Uri uri) { + return "com.android.providers.downloads.documents".equals(uri.getAuthority()); + } + + /** + * @param uri The Uri to check. + * @return Whether the Uri authority is MediaProvider. + */ + public static boolean isMediaDocument(Uri uri) { + return "com.android.providers.media.documents".equals(uri.getAuthority()); + } + + /** + * @param uri The Uri to check. + * @return Whether the Uri authority is Google Photos. + */ + public static boolean isGooglePhotosUri(Uri uri) { + return "com.google.android.apps.photos.content".equals(uri.getAuthority()); + } +}