filecache

This commit is contained in:
cansnow
2025-12-27 07:08:30 +08:00
parent 974d149d25
commit 09c7889525
54 changed files with 10485 additions and 164 deletions
@@ -0,0 +1,6 @@
{
"dependencies": [
"com.squareup.okhttp3:okhttp:3.12.12"
],
"minSdkVersion": "19"
}
@@ -0,0 +1,495 @@
import { RequestOptions, RequestSuccess, RequestTask, UploadFileOptions, UploadFile, UploadTask, OnProgressUpdateResult, UploadFileSuccess, UploadFileProgressUpdateCallback, DownloadFile, DownloadTask, DownloadFileOptions, OnProgressDownloadResult, DownloadFileProgressUpdateCallback, DownloadFileSuccess } from '../interface'
import { RequestFailImpl, UploadFileFailImpl, DownloadFileFailImpl, getErrcode } from '../unierror';
import { NetworkManager, NetworkRequestListener, NetworkUploadFileListener, NetworkDownloadFileListener } from './network/NetworkManager.uts'
import Pattern from 'java.util.regex.Pattern';
import Locale from 'java.util.Locale';
import TextUtils from 'android.text.TextUtils';
import { StatusCode } from './network/StatusCode.uts'
import Key from 'kotlinx.coroutines.CoroutineExceptionHandler.Key';
import JSONObject from 'com.alibaba.fastjson.JSONObject';
import ArrayList from 'java.util.ArrayList';
import ProgressListener from 'android.os.RecoverySystem.ProgressListener';
import Handler from 'android.os.Handler';
import Looper from 'android.os.Looper';
import Class from 'java.lang.Class';
import Type from 'java.lang.reflect.Type';
import Gson from "io.dcloud.uts.gson.Gson";
import ByteBuffer from 'java.nio.ByteBuffer';
import { NetworkUtil } from './network/utils/NetworkUtil.uts';
let charsetPattern = Pattern.compile('charset=([a-z0-9-]+)')
class RunnableTask extends Runnable {
private callback : () => void | null;
private looper : Looper | null = null;
constructor(looper : Looper | null, callback : () => void) {
super();
this.looper = looper;
this.callback = callback
}
override run() {
this.callback?.()
}
public execute() {
if (this.looper == null) {
this.run();
} else {
new Handler(this.looper!!).post(this);
}
}
}
class RequestNetworkListener<T> extends NetworkRequestListener {
private param : RequestOptions<T> | null = null;
private headers : UTSJSONObject = {};
private looper : Looper | null = null;
private type : Type | null = null;
private clzName : string | null = null;
constructor(param : RequestOptions<T>, type : Type, clzName : string) {
super();
this.param = param;
this.type = type;
this.clzName = clzName;
this.looper = Looper.myLooper();
}
override onStart() : void {
}
override onHeadersReceived(statusCode : number, headers : MutableMap<String, MutableList<String>>) : void {
let simpleHeaders = {};
if (headers != null) {
let it = headers.iterator();
while (it.hasNext()) {
let entry = it.next();
let key = entry.key;
let value = entry.value;
let tmpKey = '_';
if (key == null) {
key = tmpKey;
}
if (value.size == 0) {
continue;
} else if (value.size == 1) {
simpleHeaders[key] = value.get(0);
} else {
simpleHeaders[key] = value.toString();
}
}
}
this.headers = simpleHeaders;
}
override onProgress(progress : number) : void {
}
override onComplete(option : UTSJSONObject) : void {
let kParam = this.param;
let result = {};
if (kParam != null) {
if (option == null || '-1' == option['statusCode']) {
if (this.headers != null) {
result['header'] = this.headers;
}
let exception = option['cause']! as Exception;
const originalErrMsg = option['errorMsg']! as string;
let cause = exception.cause.toString();
if(exception.cause == null) {
cause = originalErrMsg
}
let errMsg = originalErrMsg
let errCode = (option['errorCode']! as string).toInt();
if (errMsg.indexOf("timeout") != -1) {
errCode = 5;
errMsg = "time out";
} else if (cause.contains("Connection refused")) {
errCode = 1000;
} else if (cause.contains("Network is unreachable")) {
errCode = 600003;
} else if (cause.contains("invalid URL")) {
errCode = 600009;
}
let failResult = new RequestFailImpl(getErrcode(errCode));
failResult.cause = new SourceError(cause);
new RunnableTask(this.looper, () => {
if (kParam != null) {
let fail = kParam.fail;
if (fail != null) {
fail(failResult);
}
let complete = kParam.complete;
if (complete != null) {
complete(failResult);
}
}
}).execute();
} else {
result['statusCode'] = option['statusCode'];
if (option['originalData'] == null) {
if ("java.lang.Object".equals(this.clzName, true)) {
let errMsg = option['errorMsg'];
if (errMsg != null) {
let errMsgJson = JSON.parse((option['errorMsg']! as string));
if (errMsgJson != null) {
result['data'] = errMsgJson;
} else {
result['data'] = errMsg;
}
} else {
result['data'] = "error";
}
} else if("java.lang.String".equals(this.clzName, true)){
result['data'] = option['errorMsg'] ?? "error";
} else if("io.dcloud.uts.ArrayBuffer".equals(this.clzName, true)){
let textDecoder : TextEncoder = new TextEncoder()
let error :string= (option['errorMsg'] ?? "error") as string
let uint8Array = textDecoder.encode(error)
result['data'] = uint8Array.buffer
}else {
let errMsg = option['errorMsg'];
if (errMsg != null) {
let errMsgJson = JSON.parse<T>(errMsg as string, this.type);
if (errMsgJson != null) {
result['data'] = errMsgJson;
} else {
let failResult = new RequestFailImpl(getErrcode(100002));
failResult.data = errMsg
failResult.cause = new SourceError("Error message parsing failed")
new RunnableTask(this.looper, () => {
if (kParam != null) {
let fail = kParam.fail;
if (fail != null) {
fail(failResult);
}
let complete = kParam.complete;
if (complete != null) {
complete(failResult);
}
}
}).execute();
return;
}
}
}
} else {
let charset = "";
let headers = this.headers.toJSONObject() as JSONObject;
if (headers != null) {
for (key in headers.keys) {
if (key.equals("Content-Type", true)) {
charset = headers[key] as string;
}
}
}
let data :any | null;
if("io.dcloud.uts.ArrayBuffer".equals(this.clzName, true)){
let by= option['originalData'] as ByteArray
data =ArrayBuffer.fromByteBuffer(ByteBuffer.wrap(by))
}else{
let strData = this.readAsString(option['originalData'] as ByteArray, charset);
let type = kParam.responseType != null ? kParam.responseType : kParam.dataType;
if (type == null) {
type = charset;
}
if (kParam.method == "HEAD") {
type = "";
}
data = this.parseData(strData, type);
if (data == null) {
let failResult = new RequestFailImpl(getErrcode(100001));
new RunnableTask(this.looper, () => {
if (kParam != null) {
let fail = kParam.fail;
if (fail != null) {
fail(failResult);
}
let complete = kParam.complete;
if (complete != null) {
complete(failResult);
}
}
}).execute();
return;
}
}
result['data'] = data;
}
result['statusText'] = StatusCode.getStatus(option['statusCode'] as string);
if (this.headers != null) {
result['header'] = this.headers;
}
let tmp : RequestSuccess<T> = {
data: result['data'] as T,
statusCode: (result['statusCode'] as string).toInt(),
header: result['header']!,
cookies: NetworkUtil.parseCookie(this.headers)
};
new RunnableTask(this.looper, () => {
if (kParam != null) {
let success = kParam.success;
if (success != null) {
success(tmp);
}
let complete = kParam.complete;
if (complete != null) {
complete(tmp);
}
}
}).execute();
}
}
}
private readAsString(byteArray : ByteArray, type : string) : string {
let charsetType = "utf-8";
if (type != null) {
let matcher = charsetPattern.matcher(type.lowercase(Locale.ENGLISH));
if (matcher.find()) {
charsetType = matcher.group(1);
}
}
try {
return new String(byteArray, charset(charsetType));
} catch (e : Exception) {
return new String(byteArray);
}
}
private parseData(data : string, type : string) : any | null {
if ("java.lang.Object".equals(this.clzName, true)) {
if (type.indexOf("json") != -1) {
return JSON.parse(data);
} else if (type == 'jsonp') {
if (TextUtils.isEmpty(data)) {
return {};
}
let start = data.indexOf('(') + 1;
let end = data.indexOf(')');
if (start == 0 || start >= end) {
return {};
}
let tmp = data.substring(start, end);
return JSON.parse(tmp);
} else {
let jsonData: any | null = null;
try{
const gson = new Gson();
jsonData = gson.fromJson<any>(data, this.type);
}catch(e){
}
return jsonData ?? data;
}
} else if ("java.lang.String".equals(this.clzName, true)){
return data;
} else {
return JSON.parse<T>(data, this.type);
}
}
private parseCookie(header : UTSJSONObject | null) : string[] {
if (header == null) {
return []
}
let cookiesStr = header.getString('Set-Cookie')
if (cookiesStr == null) {
cookiesStr = header.getString('set-cookie')
}
if (cookiesStr == null) {
return []
}
let cookiesArr = new Array<string>()
if (cookiesStr.charAt(0) == "[" && cookiesStr.charAt(cookiesStr.length - 1) == "]") {
cookiesStr = cookiesStr.slice(1, -1)
}
const handleCookiesArr = cookiesStr.split(';')
for (let i = 0; i < handleCookiesArr.length; i++) {
if (handleCookiesArr[i].indexOf('Expires=') != -1 || handleCookiesArr[i].indexOf('expires=') != -1) {
cookiesArr.push(handleCookiesArr[i].replace(',', ''))
} else {
cookiesArr.push(handleCookiesArr[i])
}
}
cookiesArr = cookiesArr.join(';').split(',')
return cookiesArr
}
}
class UploadNetworkListener implements NetworkUploadFileListener {
private param : UploadFileOptions | null = null;
public progressListeners = new ArrayList<UploadFileProgressUpdateCallback>();
private looper : Looper | null = null;
constructor(param : UploadFileOptions) {
this.param = param;
this.looper = Looper.myLooper();
}
onProgress(progressUpdate : OnProgressUpdateResult) {
if (this.progressListeners.size != 0) {
new RunnableTask(this.looper, () => {
for (let i : Int = 0; i < this.progressListeners.size; i++) {
let listener = this.progressListeners.get(i);
listener(progressUpdate);
}
}).execute();
}
}
onComplete(option : UTSJSONObject) {
let kParam = this.param;
if (kParam != null) {
const errorMsg = option["errorMsg"];
if (errorMsg != null) {
let errCode = (option['errorCode']! as string).toInt();
let failResult = new UploadFileFailImpl(getErrcode(errCode));
let cause = option['cause'];
if (cause != null) {
failResult.cause = cause as SourceError;
}
new RunnableTask(this.looper, () => {
if (kParam != null) {
let fail = kParam.fail;
if (fail != null) {
fail(failResult);
}
let complete = kParam.complete;
if (complete != null) {
complete(failResult);
}
}
}).execute();
} else {
let kData = option["data"];
let data = "";
if (kData != null) {
data = kData as string;
}
let statusCode = (option['statusCode']! as string).toInt();
let successResult : UploadFileSuccess = {
data: data,
statusCode: statusCode
}
new RunnableTask(this.looper, () => {
if (kParam != null) {
let success = kParam.success;
if (success != null) {
success(successResult);
}
let complete = kParam.complete;
if (complete != null) {
complete(successResult);
}
}
}).execute();
}
}
}
}
class DownloadNetworkListener implements NetworkDownloadFileListener {
private param : DownloadFileOptions | null = null;
public progressListeners = new ArrayList<DownloadFileProgressUpdateCallback>();
private looper : Looper | null = null;
constructor(param : DownloadFileOptions) {
this.param = param;
this.looper = Looper.myLooper();
}
onProgress(progressUpdate : OnProgressDownloadResult) {
if (this.progressListeners.size != 0) {
new RunnableTask(this.looper, () => {
for (let i : Int = 0; i < this.progressListeners.size; i++) {
let listener = this.progressListeners.get(i);
listener(progressUpdate);
}
}).execute();
}
}
onComplete(option : UTSJSONObject) {
let kParam = this.param;
if (kParam != null) {
let errMsg = option['errorMsg'];
if (errMsg != null) {
let errCode = (option['errorCode']! as string).toInt();
let failResult = new DownloadFileFailImpl(getErrcode(errCode));
failResult.errMsg = errMsg as string;
let cause = option['cause'];
if (cause != null) {
failResult.cause = cause as SourceError;
}
new RunnableTask(this.looper, () => {
if (kParam != null) {
let fail = kParam.fail;
if (fail != null) {
fail(failResult);
}
let complete = kParam.complete;
if (complete != null) {
complete(failResult);
}
}
}).execute();
} else {
let kTempFilePath = option["tempFilePath"];
let tempFilePath = "";
if (kTempFilePath != null) {
tempFilePath = kTempFilePath as string;
}
let statusCode = (option['statusCode']! as string).toInt();
let successResult : DownloadFileSuccess = {
tempFilePath: tempFilePath,
statusCode: statusCode
}
new RunnableTask(this.looper, () => {
if (kParam != null) {
let success = kParam.success;
if (success != null) {
success(successResult);
}
let complete = kParam.complete;
if (complete != null) {
complete(successResult);
}
}
}).execute();
}
}
}
}
@UTSAndroid.keyword("inline")
@UTSAndroid.keyword('reified')
export function request<T>(options : RequestOptions<T>) : RequestTask {
const type = UTSAndroid.getGenericType<T>();
const clzName = UTSAndroid.getGenericClassName<T>();
return NetworkManager.getInstance().request<T>(options, new RequestNetworkListener<T>(options, type, clzName));
}
export const uploadFile : UploadFile = (options : UploadFileOptions) : UploadTask => {
return NetworkManager.getInstance().uploadFile(options, new UploadNetworkListener(options));
}
export const downloadFile : DownloadFile = (options : DownloadFileOptions) : DownloadTask => {
return NetworkManager.getInstance().downloadFile(options, new DownloadNetworkListener(options));
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,32 @@
import Dns from 'okhttp3.Dns';
import UnknownHostException from 'java.net.UnknownHostException';
import InetAddress from 'java.net.InetAddress';
import Inet4Address from 'java.net.Inet4Address';
export class OKDns implements Dns {
public override lookup(hostName: string): kotlin.collections.MutableList<InetAddress> {
if (hostName == null) {
throw UnknownHostException("hostname == null");
} else {
try {
let inetAddressesList: Array<InetAddress> = [];
let inetAddresses = InetAddress.getAllByName(hostName);
for (inetAddress in inetAddresses) {
if (inetAddress instanceof Inet4Address) {
inetAddressesList.unshift(inetAddress)
} else {
inetAddressesList.push(inetAddress);
}
}
return inetAddressesList;
} catch (e: Exception) {
let unknownHostException = new UnknownHostException("error");
unknownHostException.initCause(e);
throw unknownHostException;
}
}
}
}
@@ -0,0 +1,80 @@
class StatusCode {
public static statusCodeMap : Map<string, string> | null = null;
private static initStatusCodeMap() {
let map = new Map<string, string>();
this.statusCodeMap = map;
map.set('100', "Continue");
map.set('101', "Switching Protocol");
map.set('200', "OK");
map.set('201', "Created");
map.set('202', "Accepted");
map.set('203', "Non-Authoritative Information");
map.set('204', "No Content");
map.set('205', "Reset Content");
map.set('206', "Partial Content");
map.set('300', "Multiple Choice");
map.set('301', "Moved Permanently");
map.set('302', "Found");
map.set('303', "See Other");
map.set('304', "Not Modified");
map.set('305', "Use Proxy");
map.set('306', "unused");
map.set('307', "Temporary Redirect");
map.set('308', "Permanent Redirect");
map.set('400', "Bad Request");
map.set('401', "Unauthorized");
map.set('402', "Payment Required");
map.set('403', "Forbidden");
map.set('404', "Not Found");
map.set('405', "Method Not Allowed");
map.set('406', "Not Acceptable");
map.set('407', "Proxy Authentication Required");
map.set('408', "Request Timeout");
map.set('409', "Conflict");
map.set('410', "Gone");
map.set('411', "Length Required");
map.set('412', "Precondition Failed");
map.set('413', "Payload Too Large");
map.set('414', "URI Too Long");
map.set('415', "Unsupported Media Type");
map.set('416', "Requested Range Not Satisfiable");
map.set('417', "Expectation Failed");
map.set('418', "I'm a teapot");
map.set('421', "Misdirected Request");
map.set('426', "Upgrade Required");
map.set('428', "Precondition Required");
map.set('429', "Too Many Requests");
map.set('431', "Request Header Fields Too Large");
map.set('500', "Internal Server Error");
map.set('501', "Not Implemented");
map.set('502', "Bad Gateway");
map.set('503', "Service Unavailable");
map.set('504', "Gateway Timeout");
map.set('505', "HTTP Version Not Supported");
map.set('506', "Variant Also Negotiates");
map.set('507', "Variant Also Negotiates");
map.set('511', "Network Authentication Required");
}
public static getStatus(code : string) : string {
let map = this.statusCodeMap;
if (map == null) {
this.initStatusCodeMap();
}
let tmp = this.statusCodeMap!;
if (!(tmp.has(code))) {
return 'unknown status';
} else {
return tmp.get(code)! as string;
}
}
}
export {
StatusCode
}
@@ -0,0 +1,507 @@
import { DownloadFileOptions, DownloadTask, DownloadFileProgressUpdateCallback, OnProgressDownloadResult } from '../../../interface.uts';
import { NetworkDownloadFileListener } from '../NetworkManager.uts'
import OkHttpClient from 'okhttp3.OkHttpClient';
import TimeUnit from 'java.util.concurrent.TimeUnit';
import ExecutorService from 'java.util.concurrent.ExecutorService';
import Executors from 'java.util.concurrent.Executors';
import Dispatcher from 'okhttp3.Dispatcher';
import Callback from 'okhttp3.Callback';
import Response from 'okhttp3.Response';
import Request from 'okhttp3.Request';
import Call from 'okhttp3.Call';
import IOException from 'java.io.IOException';
import ResponseBody from 'okhttp3.ResponseBody';
import File from 'java.io.File';
import BufferedSink from 'okio.BufferedSink';
import BufferedSource from 'okio.BufferedSource';
import Okio from 'okio.Okio';
import TextUtils from 'android.text.TextUtils';
import StringTokenizer from 'java.util.StringTokenizer';
import MimeTypeMap from 'android.webkit.MimeTypeMap';
import URLDecoder from 'java.net.URLDecoder';
import CookieManager from 'android.webkit.CookieManager';
import KotlinArray from 'kotlin.Array';
import Context from 'android.content.Context';
import Environment from 'android.os.Environment';
import { CookieInterceptor } from '../interceptor/CookieInterceptor.uts'
class NetworkDownloadTaskImpl implements DownloadTask {
private call : Call | null = null;
private listener : NetworkDownloadFileListener | null = null;
constructor(call : Call | null, listener : NetworkDownloadFileListener) {
this.call = call;
this.listener = listener;
}
public abort() {
if (this.call != null) {
this.call?.cancel();
}
}
public onProgressUpdate(option : DownloadFileProgressUpdateCallback) {
const kListener = this.listener;
if (kListener != null) {
kListener.progressListeners.add(option);
}
}
}
export class DownloadController {
private static instance : DownloadController | null = null
/**
* 上传的线程池
*/
private downloadExecutorService : ExecutorService | null = null;
public static getInstance() : DownloadController {
if (this.instance == null) {
this.instance = new DownloadController();
}
return this.instance!;
}
public downloadFile(options : DownloadFileOptions, listener : NetworkDownloadFileListener) : DownloadTask {
const client = this.createDownloadClient(options);
let request = this.createDownloadRequest(options, listener);
if (request == null) {
return new NetworkDownloadTaskImpl(null, listener);
}
let call : Call = client.newCall(request);
call.enqueue(new SimpleDownloadCallback(listener, options.filePath ?? ""));
let task = new NetworkDownloadTaskImpl(call, listener);
return task;
}
private createDownloadClient(option : DownloadFileOptions) : OkHttpClient {
let clientBuilder = OkHttpClient.Builder();
const timeout : Long = option.timeout != null ? option.timeout!.toLong() : 120000;
clientBuilder.connectTimeout(timeout, TimeUnit.MILLISECONDS);
clientBuilder.readTimeout(timeout, TimeUnit.MILLISECONDS);
clientBuilder.writeTimeout(timeout, TimeUnit.MILLISECONDS);
clientBuilder.addInterceptor(new CookieInterceptor());
if (this.downloadExecutorService == null) {
this.downloadExecutorService = Executors.newFixedThreadPool(10);
}
clientBuilder.dispatcher(new Dispatcher(this.downloadExecutorService));
return clientBuilder.build();
}
private createDownloadRequest(options : DownloadFileOptions, listener : NetworkDownloadFileListener) : Request | null {
let requestBilder = new Request.Builder();
try {
requestBilder.url(options.url);
} catch (exception : Exception) {
let option: UTSJSONObject = {};
option['statusCode'] = '-1';
option['errorCode'] = '-1';
option['errorMsg'] = "invalid URL";
let cause = exception.cause.toString();
option['cause'] = new SourceError(cause);
if (listener != null) {
listener.onComplete(option);
}
return null;
}
let ua = UTSAndroid.getWebViewInfo(UTSAndroid.getAppContext()!)["ua"].toString();
requestBilder.header("User-Agent", ua);
const headers = options.header?.toMap();
if (headers != null) {
for (entry in headers) {
const key = entry.key;
const value = entry.value;
if (value != null) {
requestBilder.addHeader(key, "" + value);
} else {
continue;
}
}
}
return requestBilder.build();
}
}
class SimpleDownloadCallback implements Callback {
private downloadFilePath = "/uni-download/";
private listener : NetworkDownloadFileListener | null = null;
private specifyPath = "";
constructor(listener : NetworkDownloadFileListener, specifyPath : string) {
this.listener = listener;
if(specifyPath.startsWith("unifile://")){
this.specifyPath = UTSAndroid.convert2AbsFullPath(specifyPath);
}else{
this.specifyPath = specifyPath
}
}
override onFailure(call : Call, exception : IOException) {
let option: UTSJSONObject = {};
option['statusCode'] = '-1';
option['errorCode'] = '-1';
option['errorMsg'] = exception.message;
let cause = exception.cause.toString();
option['cause'] = new SourceError(cause);
this.listener?.onComplete(option);
}
override onResponse(call : Call, response : Response) {
if (response.isSuccessful()) {
const source = response.body()?.source()
if (source != null) {
let mime_type = response.body()?.contentType();
let extension = "data";
if (mime_type != null) {
let mime_type1 = mime_type.toString(); // 例如: "application/json; charset=utf-8"
extension = mime_type.subtype(); // 子类型,例如: "json"
}
const tempFile = this.getTempFile()
let tempSink : BufferedSink | null = null;
let tempSource : BufferedSource | null = null;
let targetSink : BufferedSink | null = null;
try {
tempSink = Okio.buffer(Okio.sink(tempFile));
let totalBytesRead : Long = 0;
const contentLength = response.body()!!.contentLength();
const bufferSize : Int = 8 * 1024;
const buffer = ByteArray(bufferSize);
do {
let bytesRead = source.read(buffer);
if (bytesRead == -1) {
break;
}
tempSink.write(buffer, 0, bytesRead)
totalBytesRead += bytesRead.toLong();
const progress = (totalBytesRead.toFloat() / contentLength) * 100
let downloadProgressUpdate : OnProgressDownloadResult = {
progress: progress,
totalBytesWritten: totalBytesRead,
totalBytesExpectedToWrite: contentLength
}
this.listener?.onProgress(downloadProgressUpdate);
} while (true)
tempSink.flush()
tempSource = Okio.buffer(Okio.source(tempFile));
this.specifyPath = this.specifyPath.replace('{{ext}}',extension);
let targetFile = this.getFile(response)
targetSink = Okio.buffer(Okio.sink(targetFile));
targetSink.writeAll(tempSource);
targetSink.flush();
let option = {};
option['statusCode'] = response.code() + "";
if (targetFile.exists()) {
option['tempFilePath'] = targetFile.getPath();
}
this.listener?.onComplete(option);
} catch (e: Exception){
let option: UTSJSONObject = {};
const code = "-1"
const errorMsg = e.message
option['statusCode'] = code;
option['errorCode'] = code;
option['errorMsg'] = errorMsg;
const sourceError = new SourceError(errorMsg ?? "");
option['cause'] = sourceError;
this.listener?.onComplete(option);
} finally {
tempSink?.close()
targetSink?.close()
tempSource?.close()
source?.close()
tempFile.delete()
}
}
} else {
let option: UTSJSONObject = {};
const code = response.code() + "";
const errorMsg = response.body()?.string();
option['statusCode'] = code;
option['errorCode'] = code;
option['errorMsg'] = errorMsg;
const sourceError = new SourceError(errorMsg ?? "");
sourceError.code = response.code();
option['cause'] = sourceError;
this.listener?.onComplete(option);
}
}
//ext:string
getTempFile() : File {
return new File(UTSAndroid.getAppContext()!.getExternalCacheDir(), "temp_" + System.currentTimeMillis())
}
getRealPath() : string {
var path = UTSAndroid.getAppTempPath() ?? "";
return path + this.downloadFilePath;
}
getFile(response : Response) : File {
let targetPath = "";
if (this.specifyPath != "") {
const sourcePath = UTSAndroid.convert2AbsFullPath("/");
const sourceFileDir = new File(sourcePath);
if (this.isDescendant(sourceFileDir, new File(this.specifyPath))) {
let option = {};
option['statusCode'] = '-1';
option['errorCode'] = '602001';
option['errorMsg'] = "This path is not supported";
option['cause'] = null;
this.listener?.onComplete(option);
return new File("")
}
const pos = this.specifyPath.lastIndexOf("/")
if (pos == this.specifyPath.length - 1) {
//如果filePath是目录
if (this.isAbsolute(this.specifyPath)) {
targetPath = this.specifyPath;
} else {
targetPath = UTSAndroid.getAppTempPath()!! + "/" + this.specifyPath
}
} else {
let path = "";
if (this.isAbsolute(this.specifyPath)) {
path = this.specifyPath;
} else {
path = UTSAndroid.getAppTempPath()!! + "/" + this.specifyPath;
}
var file = new File(path)
const parentFile = file.getParentFile()
if (parentFile != null) {
if (!parentFile.exists()) {
parentFile.mkdirs()
}
}
if (file.exists() && file.isDirectory()) {
let option = {};
option['statusCode'] = '-1';
option['errorCode'] = '602001';
option['errorMsg'] = "The target file path is already a directory file, and file creation failed.";
option['cause'] = null;
this.listener?.onComplete(option);
}
if (file.exists()) {
const index = path.lastIndexOf(".");
let tFileName = path;
let tFileType = "";
if (index >= 0) {
tFileName = path.substring(0, index)
tFileType = path.substring(index)
}
var number = 1
while (new File(path).exists()) {
path = tFileName + "(" + number + ")" + tFileType;
number++
}
file = new File(path)
}
if (!file.exists()) {
try {
file.createNewFile()
} catch (exception : Exception) {
let option = {};
option['statusCode'] = '-1';
option['errorCode'] = '602001';
option['errorMsg'] = exception.message;
let cause = exception.cause.toString();
option['cause'] = new SourceError(cause);
this.listener?.onComplete(option);
}
}
return file
}
} else {
targetPath = this.getRealPath();
}
let fileName = "";
let remoteFileName = response.header("content-disposition");
if (!TextUtils.isEmpty(remoteFileName)) {
// form-data; name="file"; filename="xxx.pdf"
const segments : KotlinArray<String | null> | null = this.stringSplit(remoteFileName, ";")
if (segments != null) {
for (let i : Int = 0; i < segments.size; i++) {
const segment = segments[i];
if (segment != null) {
if (segment.contains("filename")) {
const pair = this.stringSplit(segment.trim(), "=") //目前认为只存在一个键值对
if (pair != null && pair.size > 1) {
let key = pair[0];
let value = pair[1];
const reg = new RegExp("^\"|\"$","g")
if (key != null) {
key = key.replace(reg, "");
}
if (value != null) {
value = value.replace(reg, "");
}
if (!TextUtils.isEmpty(key) && !TextUtils.isEmpty(value) && key!.equals("filename", true)) {
if (value != null) {
fileName = value;
}
}
}
}
}
}
}
}
if (TextUtils.isEmpty(fileName)) {
let path = response.request().url().encodedPath()
let pos = path.lastIndexOf('/')
if (pos >= 0) {
path = path.substring(pos + 1)
if (path.indexOf('.') >= 0 || path.length > 0) { //存在类型后缀或者没有文件格式后缀的情况,取最后LastPathComponent的名称当做文件名。
if (path.contains("?")) {
path = path.substring(0, path.indexOf("?"))
}
fileName = path
}
}
}
if (TextUtils.isEmpty(fileName)) {
fileName = System.currentTimeMillis().toString()
const contentType = response.header("content-type")
let type = MimeTypeMap.getSingleton().getExtensionFromMimeType(contentType);
if (type != null) {
fileName += "." + type;
}
}
fileName = URLDecoder.decode(fileName, "UTF-8")
fileName = fileName.replace(File.separator.toRegex(), "")
if (fileName.contains("?")) {
fileName = fileName.replace("\\?".toRegex(), "0")
}
if (fileName.length > 80) {
const subFileName : String = fileName.substring(0, 80)
fileName = subFileName + System.currentTimeMillis()
}
if (new File(targetPath + fileName).exists()) {
const index = fileName.lastIndexOf(".");
let tFileName = fileName;
let tFileType = "";
if (index >= 0) {
tFileName = fileName.substring(0, index)
tFileType = fileName.substring(index)
//fileName是 .xxx的情况
if(tFileName == ""){
tFileName = tFileType
tFileType = ""
}
} else {
tFileName = fileName
}
var number = 1
while (new File(targetPath + fileName).exists()) {
fileName = tFileName + "(" + number + ")" + tFileType;
number++
}
}
targetPath += fileName
const file = new File(targetPath)
const parentFile = file.getParentFile()
if (parentFile != null) {
if (!parentFile.exists()) {
parentFile.mkdirs()
}
}
if (!file.exists()) {
try {
file.createNewFile()
} catch (exception : Exception) {
let option: UTSJSONObject = {};
option['statusCode'] = '-1';
option['errorCode'] = '602001';
option['errorMsg'] = exception.message;
let cause = exception.cause.toString();
option['cause'] = new SourceError(cause);
this.listener?.onComplete(option);
}
}
return file
}
isAbsolute(path : string) : boolean {
const context = UTSAndroid.getAppContext()!! as Context;
if (path.startsWith(context.getFilesDir().getParent())) {
return true;
}
const exPath = context.getExternalFilesDir(null)?.getParent();
if (exPath != null && path.startsWith(exPath)) {
return true;
}
return false;
}
/**
* 判断两个文件的上下级关系
*/
isDescendant(parent : File, child : File) : boolean {
//有可能开发者传入的是/sdcard 或者/storage/emulated/ 这样的文件路径, 所以要用软连接的实际文件路径进行对比.
if (child.getCanonicalPath() == parent.getCanonicalPath()) {
return true;
}
let parentFile = child.getParentFile();
if (parentFile == null) {
return false;
}
return this.isDescendant(parent, parentFile);
}
stringSplit(str : String | null, delim : String | null) : KotlinArray<String | null> | null {
if (!TextUtils.isEmpty(str) && !TextUtils.isEmpty(delim)) {
const stringTokenizer = new StringTokenizer(str, delim, false);
const result = arrayOfNulls<String>(stringTokenizer.countTokens())
var index : Int = 0
while (stringTokenizer.hasMoreElements()) {
result[index] = stringTokenizer.nextToken().trim()
index += 1
}
return result
}
return null
}
}
@@ -0,0 +1,82 @@
import Interceptor from 'okhttp3.Interceptor';
import Response from 'okhttp3.Response';
import CookieHandler from 'java.net.CookieHandler';
import TreeMap from 'java.util.TreeMap';
import Headers from 'okhttp3.Headers';
import Request from 'okhttp3.Request';
class CookieInterceptor implements Interceptor {
override intercept(chain : Interceptor.Chain) : Response {
let request = chain.request()
let headerCookie = request.header("cookie")
let uri = request.url().uri()
let cookieHandler = CookieHandler.getDefault()
if (headerCookie == null) {
let requestBuilder = request.newBuilder()
try {
let currentHeaders = this.toMap(request.headers())
let localCookie = cookieHandler.get(uri, currentHeaders)
this.addCookies(requestBuilder, localCookie)
} catch (e : Exception) {
}
request = requestBuilder.build()
}
let response = chain.proceed(request)
try {
cookieHandler.put(uri, this.toMap(response.headers()))
} catch (e : Exception) {
}
return response
}
private toMap(headers : Headers) : MutableMap<String, MutableList<String>> {
let result : MutableMap<String, MutableList<String>> = new TreeMap(String.CASE_INSENSITIVE_ORDER)
let size = headers.size()
for (let i:Int = 0; i < size; i++) {
let name = headers.name(i)
let values = result[name]
if (values == null) {
values = arrayListOf()
result[name] = values
}
values.add(headers.value(i))
}
return result
}
private addCookies(builder : Request.Builder, localCookie : MutableMap<String, MutableList<String>>) : void {
let totalList = mutableListOf<String>()
let flagList = mutableListOf<String>()
for (key in localCookie.keys) {
if (flagList.size == 2) {
break
}
if ("cookie".equals(key, true) || "cookie2".equals(key, true)) {
flagList.add(key)
let cookieList = localCookie[key]
if (!cookieList.isNullOrEmpty()) {
totalList.addAll(cookieList)
}
}
}
let headerStr = new StringBuilder()
for (let str in totalList) {
headerStr.append(str)
headerStr.append("; ")
}
if (headerStr.toString().endsWith("; ")) {
headerStr.deleteRange(headerStr.length - 2, headerStr.length - 1)
}
if (!headerStr.toString().isEmpty()){
builder.addHeader("Cookie", headerStr.toString())
}
}
}
export {
CookieInterceptor
}
@@ -0,0 +1,48 @@
import Arrays from 'java.util.Arrays';
import KotlinArray from 'kotlin.Array'
class SSLConfig {
private keystore ?: string = null;
private storePass ?: string = null;
private ca ?: KotlinArray<String> = null;
public getKeystore() : string | null {
return this.keystore;
}
public setKeystore(ks : string) {
if (ks == null) {
ks = "";
}
this.keystore = ks;
}
public getStorePass() : string | null {
return this.storePass;
}
public setStorePass(sp : string) {
if (sp == null) {
sp = "";
}
this.storePass = sp;
}
public getCa() : KotlinArray<String> | null {
return this.ca;
}
public setCa(ca : KotlinArray<String>) {
if (ca == null) {
ca = emptyArray();
}
this.ca = ca;
}
}
export {
SSLConfig
}
@@ -0,0 +1,65 @@
import SSLSocketFactory from 'javax.net.ssl.SSLSocketFactory';
import { SSLConfig } from './SSLConfig.uts'
import SSLContext from 'javax.net.ssl.SSLContext';
import KeyStore from 'java.security.KeyStore';
import KeyManagerFactory from 'javax.net.ssl.KeyManagerFactory';
import CertificateFactory from 'java.security.cert.CertificateFactory';
import TextUtils from 'android.text.TextUtils';
class SSLFactoryManager {
private static instance?: SSLFactoryManager = null;
private cacheSSLFactory: Map<SSLConfig, SSLSocketFactory> = new Map<SSLConfig, SSLSocketFactory>();
public static getInstance(): SSLFactoryManager {
if (this.instance == null) {
this.instance = SSLFactoryManager();
}
return this.instance!;
}
public getSSLSocketFactory(sslConfig: SSLConfig): SSLSocketFactory | null {
if (sslConfig == null) {
return null;
}
if (this.cacheSSLFactory.has(sslConfig)){
let sslFactory = this.cacheSSLFactory.get(sslConfig);
if (sslConfig != null){
return sslFactory;
}
}
try{
let sslContext = SSLContext.getInstance('TLS');
let keyStore = KeyStore.getInstance('PKCS12');
let keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
if (!TextUtils.isEmpty(sslConfig.getKeystore()) && !TextUtils.isEmpty(sslConfig.getStorePass())){
//todo 1. 这里需要解析keystore
// 2. 如果是文件需要转换一下路径,然后读出来。
// resolve 原生层会提供bundleurl和运行模式的接口。
}else{
keyManagerFactory = null;
}
let certificateFactory = CertificateFactory.getInstance('X.509');
let caKeyStore = KeyStore.getInstance('PKCS12');
}catch(e : Exception){
}
return null;
}
}
export {
SSLFactoryManager
}
@@ -0,0 +1,47 @@
import RequestBody from 'okhttp3.RequestBody';
import MediaType from 'okhttp3.MediaType';
import InputStream from 'java.io.InputStream';
import BufferedSink from 'okio.BufferedSink';
import Source from 'okio.Source';
import Okio from 'okio.Okio';
import Util from 'okhttp3.internal.Util';
export class InputStreamRequestBody extends RequestBody {
private mediaType : MediaType | null = null;
private length : Long = -1;
private inputStream : InputStream | null = null;
constructor(mediaType : MediaType, length : Long, inputStream : InputStream) {
super()
this.mediaType = mediaType;
this.length = length;
this.inputStream = inputStream;
}
override contentLength() : Long {
return this.length;
}
override contentType() : MediaType {
const type = this.mediaType;
if (type == null) {
return MediaType.parse("application/octet-stream")!;
} else {
return type;
}
}
override writeTo(sink : BufferedSink) {
let source : Source | null = null;
try {
source = Okio.source(this.inputStream);
sink.writeAll(source);
} catch (e) {
}
Util.closeQuietly(source);
}
}
@@ -0,0 +1,62 @@
import RequestBody from 'okhttp3.RequestBody';
import MediaType from 'okhttp3.MediaType';
import BufferedSink from 'okio.BufferedSink';
import ForwardingSink from 'okio.ForwardingSink';
import Sink from 'okio.Sink';
import Buffer from 'okio.Buffer';
import Okio from 'okio.Okio';
export interface UploadProgressListener {
onProgress(bytesWritten : number, contentLength : number) : void;
}
class CountingSink extends ForwardingSink {
private listener : UploadProgressListener | null = null;
private bytesWritten : number = 0;
private total : number = 0;
constructor(sink : Sink, total : number, listener : UploadProgressListener) {
super(sink)
this.listener = listener;
this.total = total;
}
override write(source : Buffer, byteCount : Long) {
super.write(source, byteCount);
this.bytesWritten += byteCount;
this.listener?.onProgress(this.bytesWritten, this.total);
}
}
export class ProgressRequestBody extends RequestBody {
private requestBody : RequestBody | null = null;
private listener : UploadProgressListener | null = null;
constructor(requestBody : RequestBody, listener : UploadProgressListener) {
super();
this.requestBody = requestBody;
this.listener = listener;
}
override contentLength() : Long {
return this.requestBody?.contentLength() ?? 0;
}
override contentType() : MediaType {
const body = this.requestBody;
if (body == null) {
return MediaType.parse("application/octet-stream")!;
} else {
return body.contentType()!;
}
}
override writeTo(sink : BufferedSink) {
const countingSink = new CountingSink(sink, this.contentLength(), this.listener!);
const bufferedSink = Okio.buffer(countingSink);
this.requestBody?.writeTo(bufferedSink);
bufferedSink.flush();
}
}
@@ -0,0 +1,430 @@
import { UploadFileOptions, UploadTask, UploadFileProgressUpdateCallback, UploadFileOptionFiles, OnProgressUpdateResult } from '../../../interface.uts';
import { NetworkUploadFileListener } from '../NetworkManager.uts'
import OkHttpClient from 'okhttp3.OkHttpClient';
import TimeUnit from 'java.util.concurrent.TimeUnit';
import ExecutorService from 'java.util.concurrent.ExecutorService';
import Executors from 'java.util.concurrent.Executors';
import RequestBody from 'okhttp3.RequestBody';
import MediaType from 'okhttp3.MediaType';
import MultipartBody from 'okhttp3.MultipartBody';
import Call from 'okhttp3.Call';
import Dispatcher from 'okhttp3.Dispatcher';
import Request from 'okhttp3.Request';
import MimeTypeMap from 'android.webkit.MimeTypeMap';
import TextUtils from 'android.text.TextUtils';
import File from 'java.io.File';
import Uri from 'android.net.Uri';
import InputStream from 'java.io.InputStream';
import MediaStore from 'android.provider.MediaStore';
import FileInputStream from 'java.io.FileInputStream';
import { InputStreamRequestBody } from './InputStreamRequestBody.uts';
import { UploadProgressListener, ProgressRequestBody } from './ProgressRequestBody.uts'
import Callback from 'okhttp3.Callback';
import Response from 'okhttp3.Response';
import IOException from 'java.io.IOException';
import Retention from 'java.lang.annotation.Retention';
import URI from 'java.net.URI';
import Build from 'android.os.Build';
import Environment from 'android.os.Environment';
import UUID from 'java.util.UUID';
import Context from 'android.content.Context';
import FileOutputStream from 'java.io.FileOutputStream';
import { CookieInterceptor } from '../interceptor/CookieInterceptor.uts'
class FileInformation {
public inputStream : InputStream | null = null;
public size : Long = -1;
public mime : string | null = null;
public name : string | null = null;
}
class NetworkUploadTaskImpl implements UploadTask {
private call : Call | null = null;
private listener : NetworkUploadFileListener | null = null;
constructor(call : Call | null, listener : NetworkUploadFileListener) {
this.call = call;
this.listener = listener;
}
public abort() {
if (this.call != null) {
this.call?.cancel();
}
}
public onProgressUpdate(option : UploadFileProgressUpdateCallback) {
const kListener = this.listener;
if (kListener != null) {
kListener.progressListeners.add(option);
}
}
}
class NetworkUploadProgressListener implements UploadProgressListener {
private listener : NetworkUploadFileListener | null = null;
constructor(listener : NetworkUploadFileListener) {
this.listener = listener;
}
onProgress(bytesWritten : number, contentLength : number) {
const progress = (bytesWritten.toFloat() / contentLength) * 100
const progressUpdate : OnProgressUpdateResult = {
progress: progress.toInt(),
totalBytesSent: bytesWritten,
totalBytesExpectedToSend: contentLength
}
this.listener?.onProgress(progressUpdate);
}
}
class UploadController {
private static instance : UploadController | null = null
/**
* 上传的线程池
*/
private uploadExecutorService : ExecutorService | null = null;
public static getInstance() : UploadController {
if (this.instance == null) {
this.instance = new UploadController();
}
return this.instance!;
}
public uploadFile(options : UploadFileOptions, listener : NetworkUploadFileListener) : UploadTask {
const client = this.createUploadClient(options);
let request = this.createUploadRequest(options, listener);
if (request == null) {
return new NetworkUploadTaskImpl(null, listener);;
}
let call : Call = client.newCall(request);
call.enqueue(new SimpleUploadCallback(listener));
let task = new NetworkUploadTaskImpl(call, listener);
return task;
}
private createUploadClient(option : UploadFileOptions) : OkHttpClient {
let clientBuilder = OkHttpClient.Builder();
const timeout : Long = option.timeout != null ? option.timeout!.toLong() : 120000;
clientBuilder.connectTimeout(timeout, TimeUnit.MILLISECONDS);
clientBuilder.readTimeout(timeout, TimeUnit.MILLISECONDS);
clientBuilder.writeTimeout(timeout, TimeUnit.MILLISECONDS);
clientBuilder.addInterceptor(new CookieInterceptor());
if (this.uploadExecutorService == null) {
this.uploadExecutorService = Executors.newFixedThreadPool(10);
}
clientBuilder.dispatcher(new Dispatcher(this.uploadExecutorService));
return clientBuilder.build();
}
private createUploadRequest(options : UploadFileOptions, listener : NetworkUploadFileListener) : Request | null {
let requestBilder = new Request.Builder();
try {
requestBilder.url(options.url);
} catch (exception : Exception) {
let option = {};
option['statusCode'] = '-1';
option['errorCode'] = '600009';
option['errorMsg'] = "invalid URL";
let cause = exception.cause.toString();
option['cause'] = new SourceError(cause);
if (listener != null) {
listener.onComplete(option);
}
return null;
}
let multiPartBody = (new MultipartBody.Builder("----" + UUID.randomUUID().toString())).setType(MultipartBody.FORM);
const formData = options.formData?.toMap();
if (formData != null) {
for (entry in formData) {
const key = entry.key;
const value = entry.value;
if (value != null) {
multiPartBody.addFormDataPart(key, "" + value);
} else {
continue;
}
}
}
const tempFiles = options.files;
if (tempFiles != null && tempFiles!.length > 0) {
const files : UploadFileOptionFiles[] = tempFiles;
for (let i = 0; i < files.length; i++) {
const file = files[i];
const path = file.uri;
const fileInformation = this.getFileInformation(path)
const name = file.name ?? "file";
const inputStream = fileInformation?.inputStream;
if (fileInformation != null && inputStream != null) {
let requestBody = new InputStreamRequestBody(MediaType.parse(fileInformation.mime ?? "*/*")!, fileInformation.size, inputStream);
multiPartBody.addFormDataPart(name, fileInformation.name, requestBody);
} else {
let option = {};
option['statusCode'] = '-1';
option['errorCode'] = '-1';
option['errorMsg'] = "Illegal file";
option['cause'] = null;
if (listener != null) {
listener.onComplete(option);
}
return null;
}
}
} else {
const filePath = options.filePath;
if (filePath == null) {
let option = {};
option['statusCode'] = '-1';
option['errorCode'] = '-1';
option['errorMsg'] = "filePath is null";
option['cause'] = null;
if (listener != null) {
listener.onComplete(option);
}
return null;
}
const fileInformation = this.getFileInformation(filePath);
const name = options.name ?? "file";
const inputStream = fileInformation?.inputStream;
if (fileInformation != null && inputStream != null) {
let requestBody = new InputStreamRequestBody(MediaType.parse(fileInformation.mime ?? "*/*")!, fileInformation.size, inputStream);
multiPartBody.addFormDataPart(name, fileInformation.name, requestBody);
} else {
let option = {};
option['statusCode'] = '-1';
option['errorCode'] = '-1';
option['errorMsg'] = "Illegal file";
option['cause'] = null;
if (listener != null) {
listener.onComplete(option);
}
return null;
}
}
let ua = UTSAndroid.getWebViewInfo(UTSAndroid.getAppContext()!)["ua"].toString();
requestBilder.header("User-Agent", ua);
const headers = options.header?.toMap();
if (headers != null) {
for (entry in headers) {
const key = entry.key;
const value = entry.value;
if (value != null) {
requestBilder.addHeader(key, "" + value);
} else {
continue;
}
}
}
requestBilder.post(new ProgressRequestBody(multiPartBody.build(), new NetworkUploadProgressListener(listener)));
return requestBilder.build();
}
/**
* 获取文件信息对象
*/
private getFileInformation(uri : string) : FileInformation | null {
let result : FileInformation | null = null;
if (uri.startsWith("content://")) {
const contentUri = Uri.parse(uri);
const context = UTSAndroid.getAppContext();
let cursor = context!.getContentResolver().query(contentUri, null, null, null, null);
if (cursor != null) {
cursor.moveToFirst();
let fileInformation = new FileInformation();
fileInformation.inputStream = context.getContentResolver().openInputStream(contentUri);
fileInformation.size = cursor.getInt(cursor.getColumnIndex(MediaStore.Images.Media.SIZE)).toLong();
fileInformation.name = cursor.getString(cursor.getColumnIndex(MediaStore.Images.Media.DISPLAY_NAME));
fileInformation.mime = cursor.getString(cursor.getColumnIndex(MediaStore.Images.Media.MIME_TYPE))
result = fileInformation;
cursor.close()
}
} else {
if (uri.startsWith("file://")) {
uri = uri.substring("file://".length)
} else if(uri.startsWith("unifile://")){
uri = UTSAndroid.convert2AbsFullPath(uri)
}else {
// 如果不是file://开头的,就说明是相对路径。
uri = UTSAndroid.convert2AbsFullPath(uri)
if(uri.startsWith("/android_asset/")){
const filePath = uri.replace("/android_asset/", "")
const context = UTSAndroid.getAppContext();
const apkFile = this.copyAssetFileToPrivateDir(context!!, filePath)
if(apkFile != null){
uri = apkFile.getPath()
}
}
}
let file = new File(uri);
let fileInputStream = new FileInputStream(file);
let size = file.length();
let name = file.getName();
let mime = this.getMimeType(name);
let fileInformation = new FileInformation();
fileInformation.inputStream = fileInputStream;
fileInformation.size = size;
fileInformation.name = name;
fileInformation.mime = mime;
result = fileInformation;
}
return result;
}
private copyAssetFileToPrivateDir(context: Context, fileName: string): File| null {
try {
const destPath = context.getCacheDir().getPath() + "/uploadFiles/" + fileName
const outFile = new File(destPath)
const parentFile = outFile.getParentFile()
if (parentFile != null) {
if (!parentFile.exists()) {
parentFile.mkdirs()
}
}
if(!outFile.exists()){
outFile.createNewFile()
}
const inputStream = context.getAssets().open(fileName)
const outputStream = new FileOutputStream(outFile)
let buffer = new ByteArray(1024);
do {
let len = inputStream.read(buffer);
if (len == -1) {
break;
}
outputStream.write(buffer, 0, len)
} while (true)
inputStream.close()
outputStream.close()
return outFile
} catch (e: Exception) {
e.printStackTrace()
}
return null
}
private checkPrivatePath(path : string) : boolean {
if (Build.VERSION.SDK_INT > 29 && Environment.isExternalStorageManager()) {
return true;
}
if (path.startsWith("file://")) {
path = path.replace("file://", "");
}
const context = UTSAndroid.getAppContext()!;
let cache = context.getExternalCacheDir();
let sPrivateExternalDir = ""
if (cache == null) {
sPrivateExternalDir = Environment.getExternalStorageDirectory().getPath() + "/Android/data/" + context.getPackageName();
} else {
sPrivateExternalDir = cache.getParent();
}
const sPrivateDir = context.getFilesDir().getParent();
if (sPrivateExternalDir.startsWith("/") && !path.startsWith("/")) {
path = "/" + path;
}
if ((path.contains(sPrivateDir) || path.contains(sPrivateExternalDir))//表示应用私有路径
|| this.isAssetFile(path) //表示apk的assets路径文件
|| Build.VERSION.SDK_INT < Build.VERSION_CODES.Q//表示当前手机属于可正常访问路径系统
) {
//文件路径在私有路径下或手机系统版符合非分区存储逻辑
return true;
}
return false;
}
private isAssetFile(filePath : string) : boolean {
let isAsset = false;
if (filePath.startsWith("apps/")) {
isAsset = true;
} else if (filePath.startsWith("/android_asset/") || filePath.startsWith("android_asset/")) {
isAsset = true;
}
return isAsset;
}
/**
* 获取文件mime
*/
private getMimeType(filename : string) : string {
let map = MimeTypeMap.getSingleton()
var extType = MimeTypeMap.getFileExtensionFromUrl(filename)
if (extType == null && filename.lastIndexOf(".") >= 0) {
extType = filename.substring(filename.lastIndexOf(".") + 1)
}
let ret = map.getMimeTypeFromExtension(extType);
if (TextUtils.isEmpty(ret)) {
if (TextUtils.isEmpty(extType)) {
ret = "*/*"
} else {
ret = "application/" + extType
}
}
return ret!;
}
}
class SimpleUploadCallback implements Callback {
private listener : NetworkUploadFileListener | null = null;
constructor(listener : NetworkUploadFileListener) {
this.listener = listener;
}
override onFailure(call : Call, exception : IOException) {
let option = {};
option['statusCode'] = '-1';
option['errorCode'] = '-1';
option['errorMsg'] = exception.message;
let cause = exception.cause.toString();
option['cause'] = new SourceError(cause);
this.listener?.onComplete(option);
}
override onResponse(call : Call, response : Response) {
const result = {};
result["statusCode"] = response.code() + "";
result["data"] = response.body()?.string();
this.listener?.onComplete(result);
}
}
export {
UploadController
}
@@ -0,0 +1,61 @@
export class NetworkUtil {
public static convertHeaders(headers: MutableMap<String, MutableList<String>>): UTSJSONObject {
let simpleHeaders = {};
if (headers != null) {
let it = headers.iterator();
while (it.hasNext()) {
let entry = it.next();
let key = entry.key;
let value = entry.value;
let tmpKey = '_';
if (key == null) {
key = tmpKey;
}
if (value.size == 0) {
continue;
} else if (value.size == 1) {
simpleHeaders[key] = value.get(0);
} else {
simpleHeaders[key] = value.toString();
}
}
}
return simpleHeaders;
}
public static parseCookie(header: UTSJSONObject | null): string[] {
if (header == null) {
return []
}
let cookiesStr = header.getString('Set-Cookie')
if (cookiesStr == null) {
cookiesStr = header.getString('set-cookie')
}
if (cookiesStr == null) {
return []
}
let cookiesArr = new Array<string>()
if (cookiesStr.charAt(0) == "[" && cookiesStr.charAt(cookiesStr.length - 1) == "]") {
cookiesStr = cookiesStr.slice(1, -1)
}
const handleCookiesArr = cookiesStr.split(';')
for (let i = 0; i < handleCookiesArr.length; i++) {
if (handleCookiesArr[i].indexOf('Expires=') != -1 || handleCookiesArr[i].indexOf('expires=') != -1) {
cookiesArr.push(handleCookiesArr[i].replace(',', ''))
} else {
cookiesArr.push(handleCookiesArr[i])
}
}
cookiesArr = cookiesArr.join(';').split(',')
return cookiesArr
}
}