NekoX/TMessagesProj/src/main/java/org/telegram/messenger/FileLoadOperation.java

533 lines
21 KiB
Java
Raw Normal View History

2013-10-25 17:19:00 +02:00
/*
2015-10-29 18:10:07 +01:00
* This is the source code of Telegram for Android v. 3.x.x.
2013-10-25 17:19:00 +02:00
* It is licensed under GNU GPL v. 2 or later.
* You should have received a copy of the license in this archive (see LICENSE).
*
* Copyright Nikolai Kudashov, 2013-2016.
2013-10-25 17:19:00 +02:00
*/
package org.telegram.messenger;
2015-09-24 22:52:02 +02:00
import org.telegram.tgnet.ConnectionsManager;
import org.telegram.tgnet.RequestDelegate;
import org.telegram.tgnet.TLObject;
import org.telegram.tgnet.TLRPC;
2013-10-25 17:19:00 +02:00
import java.io.RandomAccessFile;
import java.io.File;
import java.nio.channels.FileChannel;
import java.util.ArrayList;
2013-10-25 17:19:00 +02:00
import java.util.Scanner;
public class FileLoadOperation {
private static class RequestInfo {
private int requestToken;
private int offset;
private TLRPC.TL_upload_file response;
}
private final static int stateIdle = 0;
private final static int stateDownloading = 1;
private final static int stateFailed = 2;
private final static int stateFinished = 3;
private final static int downloadChunkSize = 1024 * 32;
2015-07-22 20:56:37 +02:00
private final static int downloadChunkSizeBig = 1024 * 128;
2015-09-24 22:52:02 +02:00
private final static int maxDownloadRequests = 4;
private final static int maxDownloadRequestsBig = 2;
private final static int bigFileSizeFrom = 1024 * 1024;
2013-10-25 17:19:00 +02:00
private int datacenter_id;
private TLRPC.InputFileLocation location;
private volatile int state = stateIdle;
2013-10-25 17:19:00 +02:00
private int downloadedBytes;
private int totalBytesCount;
private FileLoadOperationDelegate delegate;
2013-10-25 17:19:00 +02:00
private byte[] key;
private byte[] iv;
2015-07-22 20:56:37 +02:00
private int currentDownloadChunkSize;
2015-09-24 22:52:02 +02:00
private int currentMaxDownloadRequests;
private int requestsCount;
2015-12-09 19:27:52 +01:00
private int renameRetryCount;
private int nextDownloadOffset;
2015-09-24 22:52:02 +02:00
private ArrayList<RequestInfo> requestInfos;
private ArrayList<RequestInfo> delayedRequestInfos;
2013-10-25 17:19:00 +02:00
private File cacheFileTemp;
private File cacheFileFinal;
2013-12-20 20:25:49 +01:00
private File cacheIvTemp;
2013-10-25 17:19:00 +02:00
2013-12-26 12:43:37 +01:00
private String ext;
2013-10-25 17:19:00 +02:00
private RandomAccessFile fileOutputStream;
private RandomAccessFile fiv;
private File storePath;
private File tempPath;
private boolean isForceRequest;
2013-10-25 17:19:00 +02:00
public interface FileLoadOperationDelegate {
void didFinishLoadingFile(FileLoadOperation operation, File finalFile);
void didFailedLoadingFile(FileLoadOperation operation, int state);
void didChangedLoadProgress(FileLoadOperation operation, float progress);
2013-10-25 17:19:00 +02:00
}
2015-05-21 23:27:27 +02:00
public FileLoadOperation(TLRPC.FileLocation photoLocation, String extension, int size) {
if (photoLocation instanceof TLRPC.TL_fileEncryptedLocation) {
2013-10-25 17:19:00 +02:00
location = new TLRPC.TL_inputEncryptedFileLocation();
location.id = photoLocation.volume_id;
location.volume_id = photoLocation.volume_id;
location.access_hash = photoLocation.secret;
location.local_id = photoLocation.local_id;
2013-10-25 17:19:00 +02:00
iv = new byte[32];
System.arraycopy(photoLocation.iv, 0, iv, 0, iv.length);
key = photoLocation.key;
datacenter_id = photoLocation.dc_id;
} else if (photoLocation instanceof TLRPC.TL_fileLocation) {
2013-10-25 17:19:00 +02:00
location = new TLRPC.TL_inputFileLocation();
location.volume_id = photoLocation.volume_id;
location.secret = photoLocation.secret;
location.local_id = photoLocation.local_id;
datacenter_id = photoLocation.dc_id;
2013-10-25 17:19:00 +02:00
}
totalBytesCount = size;
2015-05-21 23:27:27 +02:00
ext = extension != null ? extension : "jpg";
2013-10-25 17:19:00 +02:00
}
2013-12-26 12:43:37 +01:00
public FileLoadOperation(TLRPC.Document documentLocation) {
2016-03-06 02:49:31 +01:00
try {
if (documentLocation instanceof TLRPC.TL_documentEncrypted) {
location = new TLRPC.TL_inputEncryptedFileLocation();
location.id = documentLocation.id;
location.access_hash = documentLocation.access_hash;
datacenter_id = documentLocation.dc_id;
iv = new byte[32];
System.arraycopy(documentLocation.iv, 0, iv, 0, iv.length);
key = documentLocation.key;
} else if (documentLocation instanceof TLRPC.TL_document) {
location = new TLRPC.TL_inputDocumentFileLocation();
location.id = documentLocation.id;
location.access_hash = documentLocation.access_hash;
datacenter_id = documentLocation.dc_id;
}
if (totalBytesCount <= 0) {
totalBytesCount = documentLocation.size;
}
ext = FileLoader.getDocumentFileName(documentLocation);
int idx;
if (ext == null || (idx = ext.lastIndexOf(".")) == -1) {
2013-12-26 12:43:37 +01:00
ext = "";
} else {
ext = ext.substring(idx);
2016-03-06 02:49:31 +01:00
}
if (ext.length() <= 1) {
if (documentLocation.mime_type != null) {
switch (documentLocation.mime_type) {
case "video/mp4":
ext = ".mp4";
break;
case "audio/ogg":
ext = ".ogg";
break;
default:
ext = "";
break;
}
} else {
ext = "";
}
2013-12-26 12:43:37 +01:00
}
2016-03-06 02:49:31 +01:00
} catch (Exception e) {
FileLog.e("tmessages", e);
state = stateFailed;
cleanup();
Utilities.stageQueue.postRunnable(new Runnable() {
@Override
public void run() {
delegate.didFailedLoadingFile(FileLoadOperation.this, 0);
}
});
2013-12-26 12:43:37 +01:00
}
2013-10-25 17:19:00 +02:00
}
public void setForceRequest(boolean forceRequest) {
isForceRequest = forceRequest;
}
public boolean isForceRequest() {
return isForceRequest;
}
public void setPaths(File store, File temp) {
storePath = store;
tempPath = temp;
}
2013-10-25 17:19:00 +02:00
public void start() {
if (state != stateIdle) {
2013-10-25 17:19:00 +02:00
return;
}
2015-09-24 22:52:02 +02:00
currentDownloadChunkSize = totalBytesCount >= bigFileSizeFrom ? downloadChunkSizeBig : downloadChunkSize;
currentMaxDownloadRequests = totalBytesCount >= bigFileSizeFrom ? maxDownloadRequestsBig : maxDownloadRequests;
requestInfos = new ArrayList<>(currentMaxDownloadRequests);
delayedRequestInfos = new ArrayList<>(currentMaxDownloadRequests - 1);
state = stateDownloading;
if (location == null) {
2016-03-06 02:49:31 +01:00
cleanup();
2013-12-20 20:25:49 +01:00
Utilities.stageQueue.postRunnable(new Runnable() {
@Override
public void run() {
delegate.didFailedLoadingFile(FileLoadOperation.this, 0);
2013-12-20 20:25:49 +01:00
}
});
return;
}
2015-05-21 23:27:27 +02:00
String fileNameFinal;
String fileNameTemp;
2013-12-20 20:25:49 +01:00
String fileNameIv = null;
if (location.volume_id != 0 && location.local_id != 0) {
2015-12-09 19:27:52 +01:00
fileNameTemp = location.volume_id + "_" + location.local_id + ".temp";
2015-01-02 23:15:07 +01:00
fileNameFinal = location.volume_id + "_" + location.local_id + "." + ext;
2013-12-20 20:25:49 +01:00
if (key != null) {
fileNameIv = location.volume_id + "_" + location.local_id + ".iv";
}
2015-01-02 23:15:07 +01:00
if (datacenter_id == Integer.MIN_VALUE || location.volume_id == Integer.MIN_VALUE || datacenter_id == 0) {
cleanup();
Utilities.stageQueue.postRunnable(new Runnable() {
@Override
public void run() {
delegate.didFailedLoadingFile(FileLoadOperation.this, 0);
}
});
return;
2013-10-25 17:19:00 +02:00
}
} else {
2015-12-09 19:27:52 +01:00
fileNameTemp = datacenter_id + "_" + location.id + ".temp";
2013-12-26 12:43:37 +01:00
fileNameFinal = datacenter_id + "_" + location.id + ext;
2013-12-20 20:25:49 +01:00
if (key != null) {
fileNameIv = datacenter_id + "_" + location.id + ".iv";
}
2015-01-02 23:15:07 +01:00
if (datacenter_id == 0 || location.id == 0) {
cleanup();
Utilities.stageQueue.postRunnable(new Runnable() {
@Override
public void run() {
delegate.didFailedLoadingFile(FileLoadOperation.this, 0);
}
});
return;
}
2013-10-25 17:19:00 +02:00
}
cacheFileFinal = new File(storePath, fileNameFinal);
boolean exist = cacheFileFinal.exists();
if (exist && totalBytesCount != 0 && totalBytesCount != cacheFileFinal.length()) {
cacheFileFinal.delete();
2013-12-26 12:43:37 +01:00
}
2013-10-25 17:19:00 +02:00
if (!cacheFileFinal.exists()) {
cacheFileTemp = new File(tempPath, fileNameTemp);
2013-10-25 17:19:00 +02:00
if (cacheFileTemp.exists()) {
2015-12-09 19:27:52 +01:00
downloadedBytes = (int) cacheFileTemp.length();
2015-07-22 20:56:37 +02:00
nextDownloadOffset = downloadedBytes = downloadedBytes / currentDownloadChunkSize * currentDownloadChunkSize;
2013-10-25 17:19:00 +02:00
}
2015-08-13 11:23:31 +02:00
if (BuildVars.DEBUG_VERSION) {
FileLog.d("tmessages", "start loading file to temp = " + cacheFileTemp + " final = " + cacheFileFinal);
}
2013-12-20 20:25:49 +01:00
if (fileNameIv != null) {
cacheIvTemp = new File(tempPath, fileNameIv);
2013-12-20 20:25:49 +01:00
try {
fiv = new RandomAccessFile(cacheIvTemp, "rws");
long len = cacheIvTemp.length();
if (len > 0 && len % 32 == 0) {
fiv.read(iv, 0, 32);
} else {
downloadedBytes = 0;
}
} catch (Exception e) {
FileLog.e("tmessages", e);
downloadedBytes = 0;
}
}
2013-10-25 17:19:00 +02:00
try {
fileOutputStream = new RandomAccessFile(cacheFileTemp, "rws");
if (downloadedBytes != 0) {
fileOutputStream.seek(downloadedBytes);
}
} catch (Exception e) {
2013-12-20 20:25:49 +01:00
FileLog.e("tmessages", e);
2013-10-25 17:19:00 +02:00
}
if (fileOutputStream == null) {
2013-12-20 20:25:49 +01:00
cleanup();
2013-10-25 17:19:00 +02:00
Utilities.stageQueue.postRunnable(new Runnable() {
@Override
public void run() {
delegate.didFailedLoadingFile(FileLoadOperation.this, 0);
2013-10-25 17:19:00 +02:00
}
});
2013-12-20 20:25:49 +01:00
return;
2013-10-25 17:19:00 +02:00
}
Utilities.stageQueue.postRunnable(new Runnable() {
@Override
public void run() {
if (totalBytesCount != 0 && downloadedBytes == totalBytesCount) {
try {
onFinishLoadingFile();
} catch (Exception e) {
delegate.didFailedLoadingFile(FileLoadOperation.this, 0);
}
} else {
startDownloadRequest();
}
}
});
} else {
try {
onFinishLoadingFile();
} catch (Exception e) {
delegate.didFailedLoadingFile(FileLoadOperation.this, 0);
2013-10-25 17:19:00 +02:00
}
}
}
public void cancel() {
Utilities.stageQueue.postRunnable(new Runnable() {
@Override
public void run() {
if (state == stateFinished || state == stateFailed) {
return;
}
state = stateFailed;
cleanup();
2015-09-24 22:52:02 +02:00
if (requestInfos != null) {
2016-03-06 02:49:31 +01:00
for (int a = 0; a < requestInfos.size(); a++) {
RequestInfo requestInfo = requestInfos.get(a);
2015-09-24 22:52:02 +02:00
if (requestInfo.requestToken != 0) {
ConnectionsManager.getInstance().cancelRequest(requestInfo.requestToken, true);
}
}
}
delegate.didFailedLoadingFile(FileLoadOperation.this, 1);
}
});
2013-12-20 20:25:49 +01:00
}
private void cleanup() {
try {
if (fileOutputStream != null) {
2015-12-09 19:27:52 +01:00
try {
fileOutputStream.getChannel().close();
} catch (Exception e) {
FileLog.e("tmessages", e);
}
fileOutputStream.close();
fileOutputStream = null;
2013-10-25 17:19:00 +02:00
}
} catch (Exception e) {
FileLog.e("tmessages", e);
}
2013-12-20 20:25:49 +01:00
try {
if (fiv != null) {
fiv.close();
fiv = null;
2013-10-25 17:19:00 +02:00
}
} catch (Exception e) {
FileLog.e("tmessages", e);
}
2015-09-24 22:52:02 +02:00
if (delayedRequestInfos != null) {
2015-12-09 19:27:52 +01:00
for (int a = 0; a < delayedRequestInfos.size(); a++) {
RequestInfo requestInfo = delayedRequestInfos.get(a);
2015-09-24 22:52:02 +02:00
if (requestInfo.response != null) {
requestInfo.response.disableFree = false;
requestInfo.response.freeResources();
}
2014-06-20 21:34:16 +02:00
}
2015-09-24 22:52:02 +02:00
delayedRequestInfos.clear();
2013-10-25 17:19:00 +02:00
}
}
private void onFinishLoadingFile() throws Exception {
if (state != stateDownloading) {
2013-10-25 17:19:00 +02:00
return;
}
state = stateFinished;
2013-12-20 20:25:49 +01:00
cleanup();
if (cacheIvTemp != null) {
cacheIvTemp.delete();
2015-12-09 19:27:52 +01:00
cacheIvTemp = null;
2013-12-20 20:25:49 +01:00
}
if (cacheFileTemp != null) {
2015-12-09 19:27:52 +01:00
boolean renameResult = cacheFileTemp.renameTo(cacheFileFinal);
if (!renameResult) {
2015-08-13 11:23:31 +02:00
if (BuildVars.DEBUG_VERSION) {
2015-12-09 19:27:52 +01:00
FileLog.e("tmessages", "unable to rename temp = " + cacheFileTemp + " to final = " + cacheFileFinal + " retry = " + renameRetryCount);
}
renameRetryCount++;
if (renameRetryCount < 3) {
state = stateDownloading;
Utilities.stageQueue.postRunnable(new Runnable() {
@Override
public void run() {
try {
onFinishLoadingFile();
} catch (Exception e) {
delegate.didFailedLoadingFile(FileLoadOperation.this, 0);
}
}
}, 200);
return;
2015-08-13 11:23:31 +02:00
}
2015-02-01 19:51:02 +01:00
cacheFileFinal = cacheFileTemp;
}
2014-08-06 01:17:40 +02:00
}
2015-08-13 11:23:31 +02:00
if (BuildVars.DEBUG_VERSION) {
FileLog.e("tmessages", "finished downloading file to " + cacheFileFinal);
}
2015-02-01 19:51:02 +01:00
delegate.didFinishLoadingFile(FileLoadOperation.this, cacheFileFinal);
2013-10-25 17:19:00 +02:00
}
private void processRequestResult(RequestInfo requestInfo, TLRPC.TL_error error) {
requestInfos.remove(requestInfo);
2014-06-20 21:34:16 +02:00
if (error == null) {
try {
if (downloadedBytes != requestInfo.offset) {
if (state == stateDownloading) {
2014-07-04 00:41:59 +02:00
delayedRequestInfos.add(requestInfo);
requestInfo.response.disableFree = true;
}
2014-06-20 21:34:16 +02:00
return;
}
2014-07-04 00:41:59 +02:00
if (requestInfo.response.bytes == null || requestInfo.response.bytes.limit() == 0) {
2014-06-20 21:34:16 +02:00
onFinishLoadingFile();
return;
}
if (key != null) {
Utilities.aesIgeEncryption(requestInfo.response.bytes.buffer, key, iv, false, true, 0, requestInfo.response.bytes.limit());
2014-06-20 21:34:16 +02:00
}
if (fileOutputStream != null) {
FileChannel channel = fileOutputStream.getChannel();
channel.write(requestInfo.response.bytes.buffer);
2014-06-20 21:34:16 +02:00
}
if (fiv != null) {
fiv.seek(0);
fiv.write(iv);
}
2015-02-01 19:51:02 +01:00
int currentBytesSize = requestInfo.response.bytes.limit();
downloadedBytes += currentBytesSize;
if (totalBytesCount > 0 && state == stateDownloading) {
2014-06-20 21:34:16 +02:00
delegate.didChangedLoadProgress(FileLoadOperation.this, Math.min(1.0f, (float)downloadedBytes / (float)totalBytesCount));
}
for (int a = 0; a < delayedRequestInfos.size(); a++) {
RequestInfo delayedRequestInfo = delayedRequestInfos.get(a);
if (downloadedBytes == delayedRequestInfo.offset) {
delayedRequestInfos.remove(a);
processRequestResult(delayedRequestInfo, null);
delayedRequestInfo.response.disableFree = false;
delayedRequestInfo.response.freeResources();
break;
2014-06-20 21:34:16 +02:00
}
}
2015-07-22 20:56:37 +02:00
if (currentBytesSize != currentDownloadChunkSize) {
2014-06-20 21:34:16 +02:00
onFinishLoadingFile();
2015-02-01 19:51:02 +01:00
} else {
2015-07-22 20:56:37 +02:00
if (totalBytesCount != downloadedBytes && downloadedBytes % currentDownloadChunkSize == 0 || totalBytesCount > 0 && totalBytesCount > downloadedBytes) {
2015-02-01 19:51:02 +01:00
startDownloadRequest();
} else {
onFinishLoadingFile();
}
2014-06-20 21:34:16 +02:00
}
} catch (Exception e) {
cleanup();
delegate.didFailedLoadingFile(FileLoadOperation.this, 0);
2014-06-20 21:34:16 +02:00
FileLog.e("tmessages", e);
}
} else {
if (error.text.contains("FILE_MIGRATE_")) {
String errorMsg = error.text.replace("FILE_MIGRATE_", "");
Scanner scanner = new Scanner(errorMsg);
scanner.useDelimiter("");
Integer val;
try {
val = scanner.nextInt();
} catch (Exception e) {
val = null;
}
if (val == null) {
cleanup();
delegate.didFailedLoadingFile(FileLoadOperation.this, 0);
2014-06-20 21:34:16 +02:00
} else {
datacenter_id = val;
nextDownloadOffset = 0;
startDownloadRequest();
}
} else if (error.text.contains("OFFSET_INVALID")) {
2015-07-22 20:56:37 +02:00
if (downloadedBytes % currentDownloadChunkSize == 0) {
2013-10-25 17:19:00 +02:00
try {
2014-06-20 21:34:16 +02:00
onFinishLoadingFile();
2013-10-25 17:19:00 +02:00
} catch (Exception e) {
2014-06-20 21:34:16 +02:00
FileLog.e("tmessages", e);
2013-12-20 20:25:49 +01:00
cleanup();
delegate.didFailedLoadingFile(FileLoadOperation.this, 0);
2013-10-25 17:19:00 +02:00
}
} else {
2014-06-20 21:34:16 +02:00
cleanup();
delegate.didFailedLoadingFile(FileLoadOperation.this, 0);
2014-06-20 21:34:16 +02:00
}
} else if (error.text.contains("RETRY_LIMIT")) {
cleanup();
delegate.didFailedLoadingFile(FileLoadOperation.this, 2);
2014-06-20 21:34:16 +02:00
} else {
if (location != null) {
2015-11-26 22:04:02 +01:00
FileLog.e("tmessages", "" + location + " id = " + location.id + " local_id = " + location.local_id + " access_hash = " + location.access_hash + " volume_id = " + location.volume_id + " secret = " + location.secret);
2014-06-20 21:34:16 +02:00
}
cleanup();
delegate.didFailedLoadingFile(FileLoadOperation.this, 0);
2014-06-20 21:34:16 +02:00
}
}
}
private void startDownloadRequest() {
2015-09-24 22:52:02 +02:00
if (state != stateDownloading || totalBytesCount > 0 && nextDownloadOffset >= totalBytesCount || requestInfos.size() + delayedRequestInfos.size() >= currentMaxDownloadRequests) {
2014-06-20 21:34:16 +02:00
return;
}
int count = 1;
if (totalBytesCount > 0) {
2015-09-24 22:52:02 +02:00
count = Math.max(0, currentMaxDownloadRequests - requestInfos.size() - delayedRequestInfos.size());
2014-06-20 21:34:16 +02:00
}
for (int a = 0; a < count; a++) {
2014-06-20 21:34:16 +02:00
if (totalBytesCount > 0 && nextDownloadOffset >= totalBytesCount) {
break;
2013-10-25 17:19:00 +02:00
}
2015-07-22 20:56:37 +02:00
boolean isLast = totalBytesCount <= 0 || a == count - 1 || totalBytesCount > 0 && nextDownloadOffset + currentDownloadChunkSize >= totalBytesCount;
TLRPC.TL_upload_getFile req = new TLRPC.TL_upload_getFile();
2014-06-20 21:34:16 +02:00
req.location = location;
req.offset = nextDownloadOffset;
2015-07-22 20:56:37 +02:00
req.limit = currentDownloadChunkSize;
nextDownloadOffset += currentDownloadChunkSize;
final RequestInfo requestInfo = new RequestInfo();
requestInfos.add(requestInfo);
requestInfo.offset = req.offset;
2015-09-24 22:52:02 +02:00
requestInfo.requestToken = ConnectionsManager.getInstance().sendRequest(req, new RequestDelegate() {
2014-06-20 21:34:16 +02:00
@Override
public void run(TLObject response, TLRPC.TL_error error) {
requestInfo.response = (TLRPC.TL_upload_file) response;
processRequestResult(requestInfo, error);
2014-06-20 21:34:16 +02:00
}
2015-09-24 22:52:02 +02:00
}, null, (isForceRequest ? ConnectionsManager.RequestFlagForceDownload : 0) | ConnectionsManager.RequestFlagFailOnServerErrors, datacenter_id, requestsCount % 2 == 0 ? ConnectionsManager.ConnectionTypeDownload : ConnectionsManager.ConnectionTypeDownload2, isLast);
requestsCount++;
2014-06-20 21:34:16 +02:00
}
2013-10-25 17:19:00 +02:00
}
public void setDelegate(FileLoadOperationDelegate delegate) {
this.delegate = delegate;
}
2013-10-25 17:19:00 +02:00
}