@@ -7,154 +7,133 @@ import com.omarea.common.shell.RootFile
77import com.omarea.krscript.FileOwner
88import java.io.File
99import java.io.InputStream
10+ import java.net.URI
1011
1112class PathAnalysis (private var context : Context , private var parentDir : String = " " ) {
12- private val ASSETS_FILE = " file:///android_asset/"
13+ companion object {
14+ private const val ASSETS_FILE = " file:///android_asset/"
15+ }
1316
14- // 解析路径时自动获得
1517 private var currentAbsPath: String = " "
1618
17- fun getCurrentAbsPath (): String {
18- return currentAbsPath
19- }
19+ fun getCurrentAbsPath (): String = currentAbsPath
2020
2121 fun parsePath (filePath : String ): InputStream ? {
22- try {
22+ return try {
2323 if (filePath.startsWith(ASSETS_FILE )) {
2424 currentAbsPath = filePath
25- return context.assets.open(filePath.substring(ASSETS_FILE .length))
25+ context.assets.open(filePath.substring(ASSETS_FILE .length))
2626 } else {
27- return getFileByPath(filePath)
27+ getFileByPath(filePath)
2828 }
2929 } catch (ex: Exception ) {
30- return null
30+ null
3131 }
3232 }
3333
34- // TODO:处理 ../ 、 ./
34+ /* *
35+ * Tối ưu hóa việc nối đường dẫn bằng cách sử dụng java.net.URI
36+ * để tự động xử lý các ký hiệu ../ và ./ một cách chuẩn xác.
37+ */
3538 private fun pathConcat (parent : String , target : String ): String {
36- val isAssets = parent.startsWith(ASSETS_FILE )
37- val parentDir = if (isAssets) parent.substring(ASSETS_FILE .length) else parent
38- val parentSlices = ArrayList (parentDir.split(" /" ))
39- if (target.startsWith(" ../" ) && parentSlices.isNotEmpty()) {
40- val targetSlices = ArrayList (target.split(" /" ))
41- while (true ) {
42- val step = targetSlices.firstOrNull()
43- if (step != null && step == " .." && parentSlices.isNotEmpty()) {
44- parentSlices.removeAt(parentSlices.size - 1 )
45- targetSlices.removeAt(0 )
46- } else {
47- break
48- }
49- }
50- return pathConcat((if (isAssets) ASSETS_FILE else " " )+ parentSlices.joinToString(" /" ), targetSlices.joinToString(" /" ))
39+ return try {
40+ val isAssets = parent.startsWith(ASSETS_FILE )
41+ val base = if (isAssets) parent else " file://$parent "
42+
43+ // Sử dụng URI để normalize đường dẫn (xử lý ../ và ./)
44+ val uri = URI (base).resolve(target).normalize()
45+
46+ val result = uri.toString()
47+ if (isAssets) result else result.removePrefix(" file:" )
48+ } catch (e: Exception ) {
49+ // Fallback nếu URI fail
50+ if (parent.endsWith(" /" )) parent + target else " $parent /$target "
5151 }
52-
53- return (if (isAssets) ASSETS_FILE else " " )+ ( when {
54- ! (parentDir.isEmpty() || parentDir.endsWith(" /" )) -> " $parentDir /"
55- else -> parentDir
56- } + (if (target.startsWith(" ./" )) target.substring(2 ) else target))
5752 }
5853
54+ /* *
55+ * Cải tiến việc mở file bằng Root: sử dụng tên file động để tránh xung đột (Collision)
56+ */
5957 private fun useRootOpenFile (filePath : String ): InputStream ? {
6058 if (RootFile .fileExists(filePath)) {
61- val dir = File (FileWrite .getPrivateFilePath(context, " icons" ))
62- if (! dir.exists()) {
63- dir.mkdirs()
64- }
59+ val cacheDir = File (FileWrite .getPrivateFilePath(context, " icons" ))
60+ if (! cacheDir.exists()) cacheDir.mkdirs()
6561
66- val cachePath = FileWrite .getPrivateFilePath(context, " icons/outside_file.cache" )
62+ // Tạo tên file cache dựa trên hash đường dẫn để tránh ghi đè khi mở nhiều file cùng lúc
63+ val fileName = " cache_${filePath.hashCode()} "
64+ val cachePath = File (cacheDir, fileName).absolutePath
6765 val fileOwner = FileOwner (context).fileOwner
68- KeepShellPublic .doCmdSync(
69- " cp -f \" $filePath \" \" $cachePath \"\n " +
70- " chmod 777 \" $cachePath \"\n " +
71- " chown $fileOwner :$fileOwner \" $cachePath \"\n " )
72- File (cachePath).run {
73- if (exists() && canRead()) {
74- return inputStream()
66+
67+ val command = """
68+ cp -f "$filePath " "$cachePath "
69+ chmod 777 "$cachePath "
70+ chown $fileOwner :$fileOwner "$cachePath "
71+ """ .trimIndent()
72+
73+ KeepShellPublic .doCmdSync(command)
74+
75+ File (cachePath).let {
76+ if (it.exists() && it.canRead()) {
77+ return it.inputStream()
7578 }
7679 }
7780 }
7881 return null
7982 }
8083
81- // 在assets里查找文件
8284 private fun findAssetsResource (filePath : String ): InputStream ? {
83- // 解析成绝对路径
8485 val relativePath = pathConcat(parentDir, filePath)
85- try {
86+ return try {
87+ val simplePath = relativePath.substring(ASSETS_FILE .length)
88+ context.assets.open(simplePath).also { currentAbsPath = relativePath }
89+ } catch (ex: Exception ) {
8690 try {
87- // 首先在assets里查找相对路径
88- val simplePath = relativePath.substring(ASSETS_FILE .length)
89- context.assets.open(simplePath).run {
90- currentAbsPath = relativePath
91- return this
92- }
93- } catch (ex: java.lang.Exception ) {
94- // 然后再尝试再assets里查找绝对路径
95- context.assets.open(filePath).run {
96- currentAbsPath = ASSETS_FILE + filePath
97- return this
98- }
91+ context.assets.open(filePath).also { currentAbsPath = ASSETS_FILE + filePath }
92+ } catch (e: Exception ) {
93+ null
9994 }
100- } catch (ex: java.lang.Exception ) {
101- return null
10295 }
10396 }
10497
105- // 在磁盘上查找文件
10698 private fun findDiskResource (filePath : String ): InputStream ? {
99+ // 1. Tìm tương đối so với parentDir
107100 if (parentDir.isNotEmpty()) {
108- // 解析成绝对路径
109101 val relativePath = pathConcat(parentDir, filePath)
110- // 尝试使用普通权限读取文件
111- File (relativePath).run {
112- if (exists() && canRead()) {
113- currentAbsPath = absolutePath
114- return inputStream()
115- }
116- }
117- useRootOpenFile(relativePath)?.run {
118- return this
102+ val file = File (relativePath)
103+ if (file.exists() && file.canRead()) {
104+ currentAbsPath = file.absolutePath
105+ return file.inputStream()
119106 }
107+ useRootOpenFile(relativePath)?.let { return it }
120108 }
121109
122- // 路径相对于当前配置文件没找到文件的话,继续查找相对于数据文件根目录的文件
123- val privatePath = File ( pathConcat(FileWrite .getPrivateFileDir(context), filePath)).absolutePath
124- File (privatePath).run {
125- if (exists() && canRead()) {
126- currentAbsPath = absolutePath
127- return inputStream()
128- }
129- }
130- useRootOpenFile(privatePath)?.run {
131- return this
110+ // 2. Tìm trong thư mục riêng của ứng dụng (Private Data)
111+ val privateDir = FileWrite .getPrivateFileDir(context)
112+ val privatePath = pathConcat(privateDir, filePath)
113+ val pFile = File (privatePath)
114+ if (pFile.exists() && pFile.canRead()) {
115+ currentAbsPath = pFile.absolutePath
116+ return pFile.inputStream()
132117 }
133-
134- return null
118+
119+ return useRootOpenFile(privatePath)
135120 }
136121
137122 private fun getFileByPath (filePath : String ): InputStream ? {
138- try {
123+ return try {
139124 if (filePath.startsWith(" /" )) {
140125 currentAbsPath = filePath
141- val javaFileInfo = File (filePath)
142- return if (javaFileInfo.exists() && javaFileInfo.canRead()) {
143- javaFileInfo.inputStream()
144- } else {
145- useRootOpenFile(filePath)
146- }
126+ val file = File (filePath)
127+ if (file.exists() && file.canRead()) file.inputStream() else useRootOpenFile(filePath)
147128 } else {
148- // 如果当前配置文件来源于 assets,则查找依赖资源时也只去assets查找
149- return if (parentDir.isNotEmpty() && parentDir.startsWith(ASSETS_FILE )) {
129+ if (parentDir.startsWith(ASSETS_FILE )) {
150130 findAssetsResource(filePath)
151131 } else {
152132 findDiskResource(filePath)
153133 }
154134 }
155- } catch (ex: java.lang. Exception ) {
156- ex.printStackTrace()
135+ } catch (ex: Exception ) {
136+ null
157137 }
158- return null
159138 }
160139}
0 commit comments