1
1
package xyz.cssxsh.mirai.skia
2
2
3
3
import io.ktor.client.*
4
+ import io.ktor.client.content.*
4
5
import io.ktor.client.engine.okhttp.*
5
6
import io.ktor.client.plugins.*
6
7
import io.ktor.client.plugins.compression.*
@@ -10,6 +11,12 @@ import io.ktor.http.*
10
11
import io.ktor.utils.io.jvm.javaio.*
11
12
import kotlinx.coroutines.*
12
13
import net.mamoe.mirai.utils.*
14
+ import okhttp3.Dns
15
+ import okhttp3.HttpUrl.Companion.toHttpUrl
16
+ import okhttp3.dnsoverhttps.DnsOverHttps
17
+ import org.apache.commons.compress.archivers.sevenz.*
18
+ import org.apache.commons.compress.archivers.tar.*
19
+ import org.apache.commons.compress.compressors.gzip.*
13
20
import org.jetbrains.skiko.*
14
21
import xyz.cssxsh.skia.*
15
22
import java.io.*
@@ -33,34 +40,56 @@ private val http = HttpClient(OkHttp) {
33
40
connectTimeoutMillis = 30_000
34
41
socketTimeoutMillis = 30_000
35
42
}
36
- }
43
+ engine {
44
+ config {
45
+ dns(object : Dns {
46
+ private val url = System .getProperty(" xyz.cssxsh.mirai.skia.doh" , " https://public.dns.iij.jp/dns-query" )
47
+ private val doh = DnsOverHttps .Builder ()
48
+ .client(okhttp3.OkHttpClient ())
49
+ .url(url.toHttpUrl())
50
+ .includeIPv6(true )
51
+ .build()
37
52
38
- internal val sevenZ: String by lazy {
39
- System .getProperty(" xyz.cssxsh.mirai.skia.sevenZ" , " 7za" )
53
+ @Throws(java.net.UnknownHostException ::class )
54
+ override fun lookup (hostname : String ): List <java.net.InetAddress > {
55
+ return try {
56
+ doh.lookup(hostname)
57
+ } catch (_: java.net.UnknownHostException ) {
58
+ Dns .SYSTEM .lookup(hostname)
59
+ }
60
+ }
61
+ })
62
+ }
63
+ }
40
64
}
41
65
42
- internal suspend fun download (urlString : String , folder : File ): File = supervisorScope {
43
- http.prepareGet(urlString).execute { response ->
44
- val relative = response.headers[HttpHeaders .ContentDisposition ]
45
- ?.let { ContentDisposition .parse(it).parameter(ContentDisposition .Parameters .FileName ) }
46
- ? : response.request.url.encodedPath.substringAfterLast(' /' ).decodeURLPart()
66
+ internal var listener: (urlString: String ) -> ProgressListener ? = { null }
47
67
48
- val file = folder.resolve(relative)
68
+ internal suspend fun download (urlString : String , folder : File ): File {
69
+ val name = urlString.substringAfterLast(' /' ).decodeURLPart()
70
+ val listener = listener(" <${name} >下载中" )
49
71
50
- if (file.isFile && response.contentLength() == file.length()) {
51
- logger.info { " 文件 ${file.name} 已存在,跳过下载" }
52
- } else {
53
- file.delete()
54
- logger.info { " 文件 ${file.name} 开始下载" }
55
- file.outputStream().use { output ->
56
- val channel = response.bodyAsChannel()
72
+ val response = http.get(urlString) {
73
+ onDownload(listener)
74
+ }
75
+ val relative = response.headers[HttpHeaders .ContentDisposition ]
76
+ ?.let { ContentDisposition .parse(it).parameter(ContentDisposition .Parameters .FileName ) }
77
+ ? : response.request.url.encodedPath.substringAfterLast(' /' ).decodeURLPart()
57
78
58
- while (! channel.isClosedForRead) channel.copyTo(output)
59
- }
60
- }
79
+ val file = folder.resolve(relative)
80
+
81
+ if (file.isFile && response.contentLength() == file.length()) {
82
+ logger.info { " 文件 ${file.name} 已存在,跳过下载" }
83
+ } else {
84
+ file.delete()
85
+ logger.info { " 文件 ${file.name} 开始下载" }
86
+ file.outputStream().use { output ->
87
+ val channel = response.bodyAsChannel()
61
88
62
- file
89
+ while (! channel.isClosedForRead) channel.copyTo(output)
90
+ }
63
91
}
92
+ return file
64
93
}
65
94
66
95
/* *
@@ -70,7 +99,7 @@ internal suspend fun download(urlString: String, folder: File): File = superviso
70
99
*/
71
100
@JvmSynthetic
72
101
public suspend fun downloadTypeface (folder : File , vararg links : String ) {
73
- val downloaded: MutableList <File > = ArrayList ()
102
+ val downloaded: MutableList <File > = ArrayList (links.size )
74
103
val temp = runInterruptible(Dispatchers .IO ) {
75
104
Files .createTempDirectory(" skia" )
76
105
.toFile()
@@ -89,32 +118,59 @@ public suspend fun downloadTypeface(folder: File, vararg links: String) {
89
118
for (pack in downloaded) {
90
119
when (pack.extension) {
91
120
" 7z" -> runInterruptible(Dispatchers .IO ) {
92
- ProcessBuilder (sevenZ, " x" , pack.absolutePath, " -y" )
93
- .directory(folder)
94
- .start()
95
- // 防止卡顿
96
- .apply { inputStream.transferTo(OutputStream .nullOutputStream()) }
97
- .waitFor()
121
+ SevenZFile (pack).use { sevenZ ->
122
+ for (entry in sevenZ.entries) {
123
+ if (entry.isDirectory) continue
124
+ if (entry.hasStream().not ()) continue
125
+ val target = folder.resolve(entry.name)
126
+ if (target.extension !in FontExtensions ) continue
127
+ target.parentFile.mkdirs()
128
+ target.outputStream().use { output ->
129
+ sevenZ.getInputStream(entry).use { input ->
130
+ input.copyTo(output)
131
+ }
132
+ }
133
+ target.setLastModified(entry.lastModifiedDate.time)
134
+ }
135
+ }
98
136
}
99
137
" zip" -> runInterruptible(Dispatchers .IO ) {
100
138
ZipFile (pack).use { zip ->
101
139
for (entry in zip.entries()) {
102
140
if (entry.isDirectory) continue
103
141
if (entry.name.startsWith(" __MACOSX" )) continue
104
- with (folder.resolve(entry.name)) {
105
- parentFile.mkdirs()
106
- if (exists().not ()) {
107
- outputStream().use { output ->
108
- zip.getInputStream(entry).use { input ->
109
- input.transferTo(output)
110
- }
111
- }
142
+ val target = folder.resolve(entry.name)
143
+ if (target.extension !in FontExtensions ) continue
144
+ target.parentFile.mkdirs()
145
+ target.outputStream().use { output ->
146
+ zip.getInputStream(entry).use { input ->
147
+ input.copyTo(output)
112
148
}
113
- setLastModified(entry.lastModifiedTime.toMillis())
114
149
}
150
+ target.setLastModified(entry.lastModifiedTime.toMillis())
115
151
}
116
152
}
117
153
}
154
+ " gz" -> runInterruptible(Dispatchers .IO ) {
155
+ pack.inputStream()
156
+ .buffered()
157
+ .let (::GzipCompressorInputStream )
158
+ .let (::TarArchiveInputStream )
159
+ .use { input ->
160
+ while (true ) {
161
+ val entry = input.nextTarEntry ? : break
162
+ if (entry.isFile.not ()) continue
163
+ if (input.canReadEntryData(entry).not ()) continue
164
+ val target = folder.resolve(entry.name)
165
+ if (target.extension !in FontExtensions ) continue
166
+ target.parentFile.mkdirs()
167
+ target.outputStream().use { output ->
168
+ input.copyTo(output)
169
+ }
170
+ target.setLastModified(entry.modTime.time)
171
+ }
172
+ }
173
+ }
118
174
else -> runInterruptible(Dispatchers .IO ) {
119
175
Files .move(pack.toPath(), folder.resolve(pack.name).toPath())
120
176
}
@@ -149,16 +205,19 @@ public fun loadTypeface(folder: File) {
149
205
}
150
206
}
151
207
208
+
209
+ public val FontExtensions : Array <String > = arrayOf(" ttf" , " otf" , " eot" , " fon" , " font" , " woff" , " woff2" , " ttc" )
210
+
152
211
/* *
153
212
* 一些免费字体链接
154
213
*/
155
214
public val FreeFontLinks : Array <String > = arrayOf(
156
- " https://raw.fastgit.org /googlefonts/noto-emoji/main/fonts/NotoColorEmoji_WindowsCompatible.ttf" ,
157
- " https://raw.fastgit.org/wordshub/free-font/master/assets/ font/中文/方正字体系列/方正书宋简体.ttf " ,
158
- " https://raw.fastgit.org/wordshub/free-font/master/ assets/font/中文/方正字体系列/方正仿宋简体 .ttf" ,
159
- " https://raw.fastgit.org/wordshub/free-font/master/ assets/font/中文/方正字体系列/方正楷体简体 .ttf" ,
160
- " https://raw.fastgit.org/wordshub/free-font/master/ assets/font/中文/方正字体系列/方正黑体简体 .ttf" ,
161
- " https://cdn.cnbj1.fds.api.mi-img.com/vipmlmodel/font/MiSans/MiSans.zip "
215
+ " https://github.com /googlefonts/noto-emoji/raw /main/fonts/NotoColorEmoji_WindowsCompatible.ttf" ,
216
+ // "https://cdn.cnbj1.fds.api.mi-img.com/vipmlmodel/ font/MiSans/MiSans.zip ",
217
+ " https://mirai.mamoe.net/ assets/uploads/files/1666870589379-方正书宋简体 .ttf" ,
218
+ " https://mirai.mamoe.net/ assets/uploads/files/1666870589357-方正仿宋简体 .ttf" ,
219
+ " https://mirai.mamoe.net/ assets/uploads/files/1666870589334-方正楷体简体 .ttf" ,
220
+ " https://mirai.mamoe.net/assets/uploads/files/1666870589312-方正黑体简体.ttf "
162
221
)
163
222
164
223
private val SKIKO_MAVEN : String by lazy {
0 commit comments