Skip to content

Commit 79ed7e6

Browse files
author
Andrey
committed
Initial SMB 1.0 support via jcifs-ng: allows connecting and listing folders
1 parent 45a1262 commit 79ed7e6

File tree

9 files changed

+462
-12
lines changed

9 files changed

+462
-12
lines changed

app/build.gradle.kts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ android {
6464
}
6565

6666
dependencies {
67+
implementation(libs.androidx.material3)
6768
"baselineProfile"(project(":baselineprofile"))
6869
implementation(libs.androidx.profileinstaller)
6970
coreLibraryDesugaring(libs.desugar.jdk.libs)
@@ -122,9 +123,11 @@ dependencies {
122123
implementation(libs.storage)
123124
implementation(libs.zip4j)
124125

125-
//SMB Support
126+
// SMB 2/3 support
126127
implementation(libs.smbj)
127128
implementation(libs.dcerpc) {
128129
exclude(group = "com.google.code.findbugs", module = "jsr305")
129130
}
131+
// SMB 1 support (JCIFS-NG)
132+
implementation(libs.jcifs.ng)
130133
}

app/src/main/java/com/raival/compose/file/explorer/screen/main/MainActivityManager.kt

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import com.raival.compose.file.explorer.screen.main.tab.Tab
2121
import com.raival.compose.file.explorer.screen.main.tab.apps.AppsTab
2222
import com.raival.compose.file.explorer.screen.main.tab.files.FilesTab
2323
import com.raival.compose.file.explorer.screen.main.tab.files.holder.LocalFileHolder
24+
import com.raival.compose.file.explorer.screen.main.tab.files.holder.SMB1FileHolder
2425
import com.raival.compose.file.explorer.screen.main.tab.files.holder.SMBFileHolder
2526
import com.raival.compose.file.explorer.screen.main.tab.files.provider.StorageProvider
2627
import com.raival.compose.file.explorer.screen.main.tab.home.HomeTab
@@ -241,6 +242,22 @@ class MainActivityManager {
241242
}
242243
}
243244

245+
fun addSmb1Drive(
246+
host: String,
247+
port: Int,
248+
username: String,
249+
password: String,
250+
anonymous: Boolean,
251+
domain: String,
252+
context: Context
253+
): Boolean {
254+
return try {
255+
openSMB1File(SMB1FileHolder(host, port, username, password, anonymous, domain, ""), context)
256+
} catch (e: Exception) {
257+
false
258+
}
259+
}
260+
244261
private fun openFile(file: LocalFileHolder, context: Context) {
245262
if (file.exists()) {
246263
addTabAndSelect(FilesTab(file, context))
@@ -255,6 +272,14 @@ class MainActivityManager {
255272
false
256273
}
257274

275+
private fun openSMB1File(file: SMB1FileHolder, context: Context) : Boolean {
276+
return if (file.exists()) {
277+
addTabAndSelect(FilesTab(file, context))
278+
true
279+
}else
280+
false
281+
}
282+
258283
fun resumeActiveTab() {
259284
getActiveTab()?.onTabResumed()
260285
}
Lines changed: 264 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,264 @@
1+
package com.raival.compose.file.explorer.screen.main.tab.files.holder
2+
3+
import android.content.Context
4+
import android.webkit.MimeTypeMap
5+
import com.raival.compose.file.explorer.screen.main.tab.files.misc.ContentCount
6+
import com.raival.compose.file.explorer.screen.main.tab.files.misc.FileMimeType.anyFileType
7+
import kotlinx.coroutines.Dispatchers
8+
import kotlinx.coroutines.withContext
9+
import jcifs.smb.SmbFile
10+
import com.raival.compose.file.explorer.screen.main.tab.files.smb.SMB1ConnectionManager
11+
import kotlinx.coroutines.runBlocking
12+
13+
class SMB1FileHolder(
14+
val host: String,
15+
val port: Int = 139,
16+
val username: String? = null,
17+
val password: String? = null,
18+
val anonymous: Boolean = false,
19+
val domain: String? = null,
20+
val shareName: String = "",
21+
val pathInsideShare: String = "",
22+
private val _isFolder: Boolean = true
23+
) : ContentHolder() {
24+
25+
private var folderCount = 0
26+
private var fileCount = 0
27+
var details = ""
28+
29+
override val displayName: String
30+
get() = when {
31+
shareName.isEmpty() -> host // raíz del host
32+
pathInsideShare.isEmpty() -> shareName // share
33+
else -> {
34+
val file = getSmbFile()
35+
file.name.trimEnd('/').substringAfterLast('/') // nombre real del archivo/carpeta
36+
}
37+
}
38+
39+
override val isFolder: Boolean
40+
get() = _isFolder
41+
42+
override val lastModified: Long
43+
get() = System.currentTimeMillis() // jcifs-ng no expone lastModified fácilmente
44+
45+
override val size: Long
46+
get() = if (isFolder) 0L else try { getSmbFile().length() } catch (e: Exception) { 0L }
47+
48+
override val uniquePath: String
49+
get() = if (pathInsideShare.isEmpty()) "smb://$host/$shareName" else "smb://$host/$shareName/$pathInsideShare"
50+
51+
override val extension: String by lazy {
52+
if (isFolder) ""
53+
else displayName.substringAfterLast('.', "").lowercase()
54+
}
55+
56+
override val canAddNewContent: Boolean = true
57+
override val canRead: Boolean get() = true
58+
override val canWrite: Boolean get() = true
59+
60+
val mimeType: String by lazy {
61+
if (isFolder) anyFileType
62+
else MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension) ?: anyFileType
63+
}
64+
65+
private fun getSmbFile(): SmbFile = SMB1ConnectionManager.getFile(
66+
host = host,
67+
port = port,
68+
share = shareName,
69+
path = pathInsideShare,
70+
username = username,
71+
password = password,
72+
domain = domain,
73+
anonymous = anonymous
74+
)
75+
76+
override suspend fun getDetails(): String {
77+
if (details.isNotEmpty()) return details
78+
details = buildString {
79+
append("SMB1 Host: $host")
80+
if (!anonymous) append(" | User: $username")
81+
}
82+
return details
83+
}
84+
85+
override suspend fun isValid(): Boolean = withContext(Dispatchers.IO) {
86+
try {
87+
getSmbFile().exists()
88+
} catch (e: Exception) {
89+
false
90+
}
91+
}
92+
93+
override suspend fun listContent(): ArrayList<SMB1FileHolder> = withContext(Dispatchers.IO) {
94+
folderCount = 0
95+
fileCount = 0
96+
val result = arrayListOf<SMB1FileHolder>()
97+
try {
98+
val folder = getSmbFile()
99+
100+
if (shareName.isEmpty()) {
101+
folder.listFiles()?.forEach { s ->
102+
val shareNameClean = s.name.trimEnd('/')
103+
if (!shareNameClean.endsWith("$")) {
104+
result.add(
105+
SMB1FileHolder(
106+
host = host,
107+
port = port,
108+
username = username,
109+
password = password,
110+
anonymous = anonymous,
111+
domain = domain,
112+
shareName = shareNameClean,
113+
_isFolder = true
114+
)
115+
)
116+
folderCount++
117+
}
118+
}
119+
} else if (folder.isDirectory) {
120+
folder.listFiles()?.forEach { f ->
121+
if (f.name == "." || f.name == "..") return@forEach
122+
val isDir = f.isDirectory
123+
var rawName = f.name.trimEnd('/')
124+
125+
if (rawName.startsWith(shareName)) {
126+
rawName = rawName.removePrefix(shareName)
127+
}
128+
129+
val childPath = if (pathInsideShare.isEmpty()) rawName else "$pathInsideShare/$rawName"
130+
131+
result.add(
132+
SMB1FileHolder(
133+
host = host,
134+
port = port,
135+
username = username,
136+
password = password,
137+
anonymous = anonymous,
138+
domain = domain,
139+
shareName = shareName,
140+
pathInsideShare = childPath,
141+
_isFolder = isDir
142+
)
143+
)
144+
145+
if (isDir) folderCount++ else fileCount++
146+
}
147+
}
148+
} catch (_: Exception) {}
149+
result
150+
}
151+
152+
override suspend fun getParent(): SMB1FileHolder? {
153+
return when {
154+
shareName.isEmpty() -> null
155+
pathInsideShare.isBlank() -> SMB1FileHolder(
156+
host = host,
157+
port = port,
158+
username = username,
159+
password = password,
160+
anonymous = anonymous,
161+
domain = domain,
162+
shareName = "",
163+
_isFolder = true
164+
)
165+
else -> {
166+
val parentPath = pathInsideShare.substringBeforeLast("/", "")
167+
SMB1FileHolder(
168+
host = host,
169+
port = port,
170+
username = username,
171+
password = password,
172+
anonymous = anonymous,
173+
domain = domain,
174+
shareName = shareName,
175+
pathInsideShare = parentPath,
176+
_isFolder = true
177+
)
178+
}
179+
}
180+
}
181+
182+
override fun open(context: Context, anonymous: Boolean, skipSupportedExtensions: Boolean, customMimeType: String?) {
183+
// abrir archivos no implementado todavía
184+
}
185+
186+
override suspend fun getContentCount(): ContentCount = ContentCount(fileCount, folderCount)
187+
188+
override suspend fun createSubFile(name: String, onCreated: (ContentHolder?) -> Unit) = withContext(Dispatchers.IO) {
189+
try {
190+
val parent = getSmbFile()
191+
if (parent.isDirectory) {
192+
val newFile = SmbFile("${parent.url}$name", SMB1ConnectionManager.getContext(host, port, shareName, username, password, domain, anonymous))
193+
newFile.createNewFile()
194+
onCreated(
195+
SMB1FileHolder(
196+
host = host,
197+
port = port,
198+
username = username,
199+
password = password,
200+
anonymous = anonymous,
201+
domain = domain,
202+
shareName = shareName,
203+
pathInsideShare = if (pathInsideShare.isEmpty()) name else "$pathInsideShare/$name",
204+
_isFolder = false
205+
)
206+
)
207+
} else onCreated(null)
208+
} catch (_: Exception) {
209+
onCreated(null)
210+
}
211+
}
212+
213+
override suspend fun createSubFolder(name: String, onCreated: (ContentHolder?) -> Unit) = withContext(Dispatchers.IO) {
214+
try {
215+
val parent = getSmbFile()
216+
if (parent.isDirectory) {
217+
val newFolder = SmbFile("${parent.url}$name/", SMB1ConnectionManager.getContext(host, port,shareName, username, password, domain, anonymous))
218+
newFolder.mkdir()
219+
onCreated(
220+
SMB1FileHolder(
221+
host = host,
222+
port = port,
223+
username = username,
224+
password = password,
225+
anonymous = anonymous,
226+
domain = domain,
227+
shareName = shareName,
228+
pathInsideShare = if (pathInsideShare.isEmpty()) name else "$pathInsideShare/$name",
229+
_isFolder = true
230+
)
231+
)
232+
} else onCreated(null)
233+
} catch (_: Exception) {
234+
onCreated(null)
235+
}
236+
}
237+
238+
override suspend fun findFile(name: String): SMB1FileHolder? = withContext(Dispatchers.IO) {
239+
try {
240+
val folder = getSmbFile()
241+
if (folder.isDirectory) {
242+
folder.listFiles()?.forEach { f ->
243+
if (f.name == name) {
244+
return@withContext SMB1FileHolder(
245+
host = host,
246+
port = port,
247+
username = username,
248+
password = password,
249+
anonymous = anonymous,
250+
domain = domain,
251+
shareName = shareName,
252+
pathInsideShare = if (pathInsideShare.isEmpty()) name else "$pathInsideShare/$name",
253+
_isFolder = f.isDirectory
254+
)
255+
}
256+
}
257+
}
258+
} catch (_: Exception) {}
259+
null
260+
}
261+
262+
fun exists() = runBlocking { isValid() }
263+
}
264+

app/src/main/java/com/raival/compose/file/explorer/screen/main/tab/files/holder/SMBFileHolder.kt

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -336,8 +336,4 @@ class SMBFileHolder(
336336
}
337337

338338
fun exists() = runBlocking { isValid() }
339-
}
340-
341-
342-
343-
339+
}

0 commit comments

Comments
 (0)