commit
						342ea3cd51
					
				| @ -0,0 +1 @@ | |||||||
|  | ## 用于网络备份的WebDav | ||||||
| @ -0,0 +1,297 @@ | |||||||
|  | package io.legado.app.lib.webdav | ||||||
|  | 
 | ||||||
|  | import io.legado.app.lib.webdav.http.Handler | ||||||
|  | import io.legado.app.lib.webdav.http.HttpAuth | ||||||
|  | import io.legado.app.lib.webdav.http.OkHttp | ||||||
|  | 
 | ||||||
|  | import org.jsoup.Jsoup | ||||||
|  | import org.jsoup.nodes.Document | ||||||
|  | import org.jsoup.nodes.Element | ||||||
|  | import org.jsoup.select.Elements | ||||||
|  | 
 | ||||||
|  | import java.io.File | ||||||
|  | import java.io.FileOutputStream | ||||||
|  | import java.io.IOException | ||||||
|  | import java.io.InputStream | ||||||
|  | import java.io.UnsupportedEncodingException | ||||||
|  | import java.lang.reflect.Field | ||||||
|  | import java.net.MalformedURLException | ||||||
|  | import java.net.URL | ||||||
|  | import java.net.URLEncoder | ||||||
|  | import java.util.ArrayList | ||||||
|  | 
 | ||||||
|  | import okhttp3.Credentials | ||||||
|  | import okhttp3.MediaType | ||||||
|  | import okhttp3.OkHttpClient | ||||||
|  | import okhttp3.Request | ||||||
|  | import okhttp3.RequestBody | ||||||
|  | import okhttp3.Response | ||||||
|  | 
 | ||||||
|  | class WebDav @Throws(MalformedURLException::class) | ||||||
|  | constructor(url: String) { | ||||||
|  |     private val url: URL = URL(null, url, Handler.HANDLER) | ||||||
|  |     private var httpUrl: String? = null | ||||||
|  | 
 | ||||||
|  |     var displayName: String? = null | ||||||
|  |     var size: Long = 0 | ||||||
|  |     private var exists = false | ||||||
|  |     var parent = "" | ||||||
|  |     var urlName = "" | ||||||
|  |         get() { | ||||||
|  |             if (field.isEmpty()) { | ||||||
|  |                 this.urlName = (if (parent.isEmpty()) url.file else url.toString().replace(parent, "")).replace("/", "") | ||||||
|  |             } | ||||||
|  |             return field | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |     private val okHttpClient: OkHttpClient = OkHttp.SingletonHelper.okHttpClient | ||||||
|  | 
 | ||||||
|  |     val path: String | ||||||
|  |         get() = url.toString() | ||||||
|  | 
 | ||||||
|  |     private val inputStream: InputStream? | ||||||
|  |         get() = getUrl()?.let {url -> | ||||||
|  |             val request = Request.Builder().url(url) | ||||||
|  |             HttpAuth.auth?.let {request.header("Authorization", Credentials.basic(it.user, it.pass)) } | ||||||
|  | 
 | ||||||
|  |             try { | ||||||
|  |                 return okHttpClient.newCall(request.build()).execute().body()?.byteStream() | ||||||
|  |             } catch (e: IOException) { | ||||||
|  |                 e.printStackTrace() | ||||||
|  |             } catch (e: IllegalArgumentException) { | ||||||
|  |                 e.printStackTrace() | ||||||
|  |             } | ||||||
|  |             return null | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |     val host: String | ||||||
|  |         get() = url.host | ||||||
|  | 
 | ||||||
|  |     private fun getUrl(): String? { | ||||||
|  |         if (httpUrl == null) { | ||||||
|  |             val raw = url.toString().replace("davs://", "https://").replace("dav://", "http://") | ||||||
|  |             try { | ||||||
|  |                 httpUrl = URLEncoder.encode(raw, "UTF-8") | ||||||
|  |                     .replace("\\+".toRegex(), "%20") | ||||||
|  |                     .replace("%3A".toRegex(), ":") | ||||||
|  |                     .replace("%2F".toRegex(), "/") | ||||||
|  |             } catch (e: UnsupportedEncodingException) { | ||||||
|  |                 e.printStackTrace() | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |         } | ||||||
|  |         return httpUrl | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * 填充文件信息。实例化WebDAVFile对象时,并没有将远程文件的信息填充到实例中。需要手动填充! | ||||||
|  |      * | ||||||
|  |      * @return 远程文件是否存在 | ||||||
|  |      */ | ||||||
|  |     @Throws(IOException::class) | ||||||
|  |     fun indexFileInfo(): Boolean { | ||||||
|  |         propFindResponse(ArrayList())?.let { response -> | ||||||
|  |             if (!response.isSuccessful) { | ||||||
|  |                 this.exists = false | ||||||
|  |                 return false | ||||||
|  |             } | ||||||
|  |             response.body()?.let { | ||||||
|  |                 if (it.string().isNotEmpty()) { | ||||||
|  |                     return true | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |         return false | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * 列出当前路径下的文件 | ||||||
|  |      * | ||||||
|  |      * @param propsList 指定列出文件的哪些属性 | ||||||
|  |      * @return 文件列表 | ||||||
|  |      */ | ||||||
|  |     @Throws(IOException::class) | ||||||
|  |     @JvmOverloads | ||||||
|  |     fun listFiles(propsList: ArrayList<String> = ArrayList()): List<WebDav> { | ||||||
|  |             propFindResponse(propsList)?.let {response-> | ||||||
|  |             if (response.isSuccessful) { | ||||||
|  |                 response.body()?.let {body-> | ||||||
|  |                     return parseDir(body.string()) | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |         } | ||||||
|  |         return ArrayList() | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     @Throws(IOException::class) | ||||||
|  |     private fun propFindResponse(propsList: ArrayList<String>, depth: Int = 1): Response? { | ||||||
|  |         val requestProps = StringBuilder() | ||||||
|  |         for (p in propsList) { | ||||||
|  |             requestProps.append("<a:").append(p).append("/>\n") | ||||||
|  |         } | ||||||
|  |         val requestPropsStr: String | ||||||
|  |         requestPropsStr = if (requestProps.toString().isEmpty()) { | ||||||
|  |             DIR.replace("%s", "") | ||||||
|  |         } else { | ||||||
|  |             String.format(DIR, requestProps.toString() + "\n") | ||||||
|  |         } | ||||||
|  |         getUrl()?.let {url-> | ||||||
|  |             val request = Request.Builder() | ||||||
|  |                 .url(url) | ||||||
|  |                 // 添加RequestBody对象,可以只返回的属性。如果设为null,则会返回全部属性 | ||||||
|  |                 // 注意:尽量手动指定需要返回的属性。若返回全部属性,可能后由于Prop.java里没有该属性名,而崩溃。 | ||||||
|  |                 .method("PROPFIND", RequestBody.create(MediaType.parse("text/plain"), requestPropsStr)) | ||||||
|  | 
 | ||||||
|  |             HttpAuth.auth?.let {request.header("Authorization", Credentials.basic(it.user, it.pass)) } | ||||||
|  | 
 | ||||||
|  |             request.header("Depth", if (depth < 0) "infinity" else Integer.toString(depth)) | ||||||
|  | 
 | ||||||
|  |             return okHttpClient.newCall(request.build()).execute() | ||||||
|  |         } | ||||||
|  |         return null | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     private fun parseDir(s: String): List<WebDav> { | ||||||
|  |         val list = ArrayList<WebDav>() | ||||||
|  |         val document = Jsoup.parse(s) | ||||||
|  |         val elements = document.getElementsByTag("d:response") | ||||||
|  |         getUrl()?.let { url-> | ||||||
|  |             val baseUrl = if (url.endsWith("/")) url else "$url/" | ||||||
|  |             for (element in elements) { | ||||||
|  |                 val href = element.getElementsByTag("d:href")[0].text() | ||||||
|  |                 if (!href.endsWith("/")) { | ||||||
|  |                     val fileName = href.substring(href.lastIndexOf("/") + 1) | ||||||
|  |                     val webDavFile: WebDav | ||||||
|  |                     try { | ||||||
|  |                         webDavFile = WebDav(baseUrl + fileName) | ||||||
|  |                         webDavFile.displayName = fileName | ||||||
|  |                         webDavFile.urlName = href | ||||||
|  |                         list.add(webDavFile) | ||||||
|  |                     } catch (e: MalformedURLException) { | ||||||
|  |                         e.printStackTrace() | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |         return list | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * 根据自己的URL,在远程处创建对应的文件夹 | ||||||
|  |      * | ||||||
|  |      * @return 是否创建成功 | ||||||
|  |      */ | ||||||
|  |     @Throws(IOException::class) | ||||||
|  |     fun makeAsDir(): Boolean { | ||||||
|  |         getUrl()?.let { url-> | ||||||
|  |             val request = Request.Builder() | ||||||
|  |                 .url(url) | ||||||
|  |                 .method("MKCOL", null) | ||||||
|  |             return execRequest(request) | ||||||
|  |         } | ||||||
|  |         return false | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * 下载到本地 | ||||||
|  |      * | ||||||
|  |      * @param savedPath       本地的完整路径,包括最后的文件名 | ||||||
|  |      * @param replaceExisting 是否替换本地的同名文件 | ||||||
|  |      * @return 下载是否成功 | ||||||
|  |      */ | ||||||
|  |     fun downloadTo(savedPath: String, replaceExisting: Boolean): Boolean { | ||||||
|  |         if (File(savedPath).exists()) { | ||||||
|  |             if (!replaceExisting) { | ||||||
|  |                 return false | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |         val inputS = inputStream ?: return false | ||||||
|  |         File(savedPath).writeBytes(inputS.readBytes()) | ||||||
|  |         return true | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * 上传文件 | ||||||
|  |      */ | ||||||
|  |     @Throws(IOException::class) | ||||||
|  |     @JvmOverloads | ||||||
|  |     fun upload(localPath: String, contentType: String? = null): Boolean { | ||||||
|  |         val file = File(localPath) | ||||||
|  |         if (!file.exists()) return false | ||||||
|  |         val mediaType = if (contentType == null) null else MediaType.parse(contentType) | ||||||
|  |         // 务必注意RequestBody不要嵌套,不然上传时内容可能会被追加多余的文件信息 | ||||||
|  |         val fileBody = RequestBody.create(mediaType, file) | ||||||
|  |         getUrl()?.let { | ||||||
|  |             val request = Request.Builder() | ||||||
|  |                 .url(it) | ||||||
|  |                 .put(fileBody) | ||||||
|  |             return execRequest(request) | ||||||
|  |         } | ||||||
|  |         return false | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * 执行请求,获取响应结果 | ||||||
|  |      * | ||||||
|  |      * @param requestBuilder 因为还需要追加验证信息,所以此处传递Request.Builder的对象,而不是Request的对象 | ||||||
|  |      * @return 请求执行的结果 | ||||||
|  |      */ | ||||||
|  |     @Throws(IOException::class) | ||||||
|  |     private fun execRequest(requestBuilder: Request.Builder): Boolean { | ||||||
|  |         HttpAuth.auth?.let { requestBuilder.header("Authorization", Credentials.basic(it.user, it.pass)) } | ||||||
|  | 
 | ||||||
|  |         val response = okHttpClient.newCall(requestBuilder.build()).execute() | ||||||
|  |         return response.isSuccessful | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     fun canRead(): Boolean { | ||||||
|  |         return true | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     fun canWrite(): Boolean { | ||||||
|  |         return false | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     fun exists(): Boolean { | ||||||
|  |         return exists | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     fun exists(exists: Boolean) { | ||||||
|  |         this.exists = exists | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     companion object { | ||||||
|  |         val TAG = WebDav::class.java.simpleName | ||||||
|  |         val OBJECT_NOT_EXISTS_TAG = "ObjectNotFound" | ||||||
|  |         // 指定返回哪些属性 | ||||||
|  |         private val DIR = "<?xml version=\"1.0\"?>\n" + | ||||||
|  |                 "<a:propfind xmlns:a=\"DAV:\">\n" + | ||||||
|  |                 "<a:prop>\n" + | ||||||
|  |                 "<a:displayname/>\n<a:resourcetype/>\n<a:getcontentlength/>\n<a:creationdate/>\n<a:getlastmodified/>\n%s" + | ||||||
|  |                 "</a:prop>\n" + | ||||||
|  |                 "</a:propfind>" | ||||||
|  | 
 | ||||||
|  |         /** | ||||||
|  |          * 打印对象内的所有属性 | ||||||
|  |          */ | ||||||
|  |         fun <T> printAllAttrs(className: String, o: Any): T? { | ||||||
|  |             try { | ||||||
|  |                 val c = Class.forName(className) | ||||||
|  |                 val fields = c.declaredFields | ||||||
|  |                 for (f in fields) { | ||||||
|  |                     f.isAccessible = true | ||||||
|  |                 } | ||||||
|  |                 println("=============$className===============") | ||||||
|  |                 for (f in fields) { | ||||||
|  |                     val field = f.toString().substring(f.toString().lastIndexOf(".") + 1) //取出属性名称 | ||||||
|  |                     println(field + " --> " + f.get(o)) | ||||||
|  |                 } | ||||||
|  |             } catch (e: Exception) { | ||||||
|  |                 e.printStackTrace() | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             return null | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
| @ -0,0 +1,25 @@ | |||||||
|  | package io.legado.app.lib.webdav.http | ||||||
|  | 
 | ||||||
|  | import java.net.URL | ||||||
|  | import java.net.URLConnection | ||||||
|  | import java.net.URLStreamHandler | ||||||
|  | 
 | ||||||
|  | class Handler : URLStreamHandler() { | ||||||
|  | 
 | ||||||
|  |     override fun getDefaultPort(): Int { | ||||||
|  |         return 80 | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public override fun openConnection(u: URL): URLConnection? { | ||||||
|  |         return null | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     override fun parseURL(url: URL, spec: String, start: Int, end: Int) { | ||||||
|  |         super.parseURL(url, spec, start, end) | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     companion object { | ||||||
|  | 
 | ||||||
|  |         val HANDLER: URLStreamHandler = Handler() | ||||||
|  |     } | ||||||
|  | } | ||||||
| @ -0,0 +1,14 @@ | |||||||
|  | package io.legado.app.lib.webdav.http | ||||||
|  | 
 | ||||||
|  | object HttpAuth { | ||||||
|  | 
 | ||||||
|  |     var auth: Auth? = null | ||||||
|  |         private set | ||||||
|  | 
 | ||||||
|  |     fun setAuth(user: String, password: String) { | ||||||
|  |         auth = Auth(user, password) | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     class Auth internal constructor(val user: String, val pass: String) | ||||||
|  | 
 | ||||||
|  | } | ||||||
| @ -0,0 +1,11 @@ | |||||||
|  | package io.legado.app.lib.webdav.http | ||||||
|  | 
 | ||||||
|  | import okhttp3.OkHttpClient | ||||||
|  | 
 | ||||||
|  | class OkHttp private constructor() { | ||||||
|  | 
 | ||||||
|  |     object SingletonHelper { | ||||||
|  |         val okHttpClient: OkHttpClient = OkHttpClient.Builder().build() | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  | } | ||||||
					Loading…
					
					
				
		Reference in new issue