package com.qiniu.android.storage;
import com.qiniu.android.collect.Config;
import com.qiniu.android.collect.UploadInfoCollector;
import com.qiniu.android.common.Zone;
import com.qiniu.android.http.Client;
import com.qiniu.android.http.ResponseInfo;
import com.qiniu.android.utils.AsyncRun;
import com.qiniu.android.utils.StringUtils;
import org.json.JSONObject;
import java.io.File;
/**
* 七牛文件上传管理器
* 一般默认可以使用这个类的方法来上传数据和文件。会自动检测文件的大小,
* 只要超过了{@link Configuration#putThreshold} 异步方法会使用分片方片上传。
* 同步上传方法使用表单方式上传,建议只对小文件使用同步方式
*/
public final class UploadManager {
private final Configuration config;
private final Client client;
public UploadManager() {
this(new Configuration.Builder().build());
}
public UploadManager(Configuration config) {
this.config = config;
this.client = new Client(config.proxy, config.connectTimeout, config.responseTimeout,
config.urlConverter, config.dns);
}
public UploadManager(Recorder recorder, KeyGenerator keyGen) {
this(new Configuration.Builder().recorder(recorder, keyGen).build());
}
public UploadManager(Recorder recorder) {
this(recorder, null);
}
private static boolean areInvalidArg(final String key, byte[] data, File f, String token,
UpToken decodedToken, final WarpHandler completionHandler) {
if (completionHandler.complete == null) {
throw new IllegalArgumentException("no UpCompletionHandler");
}
String message = null;
if (f == null && data == null) {
message = "no input data";
} else if (token == null || token.equals("")) {
message = "no token";
}
ResponseInfo info = null;
if (message != null) {
info = ResponseInfo.invalidArgument(message, decodedToken);
} else if (decodedToken == UpToken.NULL || decodedToken == null) {
info = ResponseInfo.invalidToken("invalid token");
} else if ((f != null && f.length() == 0) || (data != null && data.length == 0)) {
info = ResponseInfo.zeroSize(decodedToken);
}
if (info != null) {
completionHandler.complete(key, info, null);
return true;
}
return false;
}
/**
* 上传数据
*
* @param data 上传的数据
* @param key 上传数据保存的文件名
* @param token 上传凭证
* @param complete 上传完成后续处理动作
* @param options 上传数据的可选参数
*/
public void put(final byte[] data, final String key, final String token,
final UpCompletionHandler complete, final UploadOptions options) {
final WarpHandler completionHandler = warpHandler(complete, data != null ? data.length : 0, UpType.form);
final UpToken decodedToken = UpToken.parse(token);
if (areInvalidArg(key, data, null, token, decodedToken, completionHandler)) {
return;
}
Zone z = config.zone;
z.preQuery(token, new Zone.QueryHandler() {
@Override
public void onSuccess() {
FormUploader.upload(client, config, data, key, decodedToken, completionHandler, options);
}
@Override
public void onFailure(int reason) {
final ResponseInfo info = ResponseInfo.invalidToken("invalid token");
completionHandler.complete(key, info, null);
}
});
}
/**
* 上传文件
*
* @param filePath 上传的文件路径
* @param key 上传文件保存的文件名
* @param token 上传凭证
* @param completionHandler 上传完成的后续处理动作
* @param options 上传数据的可选参数
*/
public void put(String filePath, String key, String token, UpCompletionHandler completionHandler,
final UploadOptions options) {
put(new File(filePath), key, token, completionHandler, options);
}
/**
* 上传文件
*
* @param file 上传的文件对象
* @param key 上传文件保存的文件名
* @param token 上传凭证
* @param complete 上传完成的后续处理动作
* @param options 上传数据的可选参数
*/
public void put(final File file, final String key, String token, final UpCompletionHandler complete,
final UploadOptions options) {
final WarpHandler completionHandler = warpHandler(complete, file != null ? file.length() : 0, UpType.form);
final UpToken decodedToken = UpToken.parse(token);
if (areInvalidArg(key, null, file, token, decodedToken, completionHandler)) {
return;
}
Zone z = config.zone;
z.preQuery(token, new Zone.QueryHandler() {
@Override
public void onSuccess() {
long size = file.length();
if (size <= config.putThreshold) {
completionHandler.type = UpType.form;
FormUploader.upload(client, config, file, key, decodedToken, completionHandler, options);
return;
}
String recorderKey = config.keyGen.gen(key, file);
completionHandler.type = UpType.block;
ResumeUploader uploader = new ResumeUploader(client, config, file, key,
decodedToken, completionHandler, options, recorderKey);
AsyncRun.runInMain(uploader);
}
@Override
public void onFailure(int reason) {
final ResponseInfo info = ResponseInfo.invalidToken("invalid token");
completionHandler.complete(key, info, null);
}
});
}
/**
* 同步上传文件。使用 form 表单方式上传,建议只在数据较小情况下使用此方式,如 file.size() < 1024 * 1024。
*
* @param data 上传的数据
* @param key 上传数据保存的文件名
* @param token 上传凭证
* @param options 上传数据的可选参数
* @return 响应信息 ResponseInfo#response 响应体,序列化后 json 格式
*/
public ResponseInfo syncPut(byte[] data, String key, String token, UploadOptions options) {
final UpToken decodedToken = UpToken.parse(token);
ResponseInfo info = areInvalidArg(key, data, null, token, decodedToken);
if (info != null) {
return info;
}
return FormUploader.syncUpload(client, config, data, key, decodedToken, options);
}
/**
* 同步上传文件。使用 form 表单方式上传,建议只在文件较小情况下使用此方式,如 file.size() < 1024 * 1024。
*
* @param file 上传的文件对象
* @param key 上传数据保存的文件名
* @param token 上传凭证
* @param options 上传数据的可选参数
* @return 响应信息 ResponseInfo#response 响应体,序列化后 json 格式
*/
public ResponseInfo syncPut(File file, String key, String token, UploadOptions options) {
final UpToken decodedToken = UpToken.parse(token);
ResponseInfo info = areInvalidArg(key, null, file, token, decodedToken);
if (info != null) {
return info;
}
return FormUploader.syncUpload(client, config, file, key, decodedToken, options);
}
/**
* 同步上传文件。使用 form 表单方式上传,建议只在文件较小情况下使用此方式,如 file.size() < 1024 * 1024。
*
* @param file 上传的文件绝对路径
* @param key 上传数据保存的文件名
* @param token 上传凭证
* @param options 上传数据的可选参数
* @return 响应信息 ResponseInfo#response 响应体,序列化后 json 格式
*/
public ResponseInfo syncPut(String file, String key, String token, UploadOptions options) {
return syncPut(new File(file), key, token, options);
}
private static ResponseInfo areInvalidArg(final String key, byte[] data, File f, String token,
UpToken decodedToken) {
String message = null;
if (f == null && data == null) {
message = "no input data";
} else if (token == null || token.equals("")) {
message = "no token";
}
if (message != null) {
return ResponseInfo.invalidArgument(message, decodedToken);
}
if (decodedToken == UpToken.NULL || decodedToken == null) {
return ResponseInfo.invalidToken("invalid token");
}
if ((f != null && f.length() == 0) || (data != null && data.length == 0)) {
return ResponseInfo.zeroSize(decodedToken);
}
return null;
}
private static enum UpType {
form, block,
}
private static class WarpHandler implements UpCompletionHandler {
final UpCompletionHandler complete;
UpType type;
final long before = System.currentTimeMillis();
final long size;
WarpHandler(UpCompletionHandler complete, long size, UpType type) {
this.complete = complete;
this.type = type;
this.size = size;
}
@Override
public void complete(final String key, final ResponseInfo res, final JSONObject response) {
if (Config.isRecord) {
final long after = System.currentTimeMillis();
UploadInfoCollector.handleUpload(res.upToken,
// 延迟序列化.如果判断不记录,则不执行序列化
new UploadInfoCollector.RecordMsg() {
@Override
public String toRecordMsg() {
// https://jira.qiniu.io/browse/KODO-1468
// ip 形如 /115.231.97.46:80
String remoteIp = (res.ip + "").split(":")[0].replace("/", "");
String[] ss = new String[]{res.statusCode + "", res.reqId, res.host, remoteIp, res.port + "", (after - before) + "",
res.timeStamp + "", size + "", type.toString()};
return StringUtils.join(ss, ",");
}
});
}
AsyncRun.runInMain(new Runnable() {
@Override
public void run() {
try {
complete.complete(key, res, response);
} catch (Throwable t) {
// do nothing
t.printStackTrace();
}
}
});
}
}
private static WarpHandler warpHandler(final UpCompletionHandler complete, final long size, final UpType type) {
return new WarpHandler(complete, size, type);
}
}