2021 Sep. 06.
2021 Aug. 14.
androidでjcifs-ng・コルーチンを使ってSMBサーバーに接続し、ディレクトリ内のファイルを再帰的に取得する実装例
app/libsに配置するライブラリ
bcprov-jdk15to18-1.69.jar
Maven Repository: org.bouncycastle » bcprov-jdk15to18 » 1.69 のFilesのjarをクリックしてダウンロード
jcifs-ng-2.1.6.jar
Maven Repository: eu.agno3.jcifs » jcifs-ng » 2.1.6 のFilesのbundleをクリックしてダウンロード
build.gradle(Module:app)
追加設定
implementation files('libs/jcifs-ng-2.1.6.jar')
implementation group: 'org.slf4j', name: 'slf4j-api', version: '1.7.32'
implementation group: 'org.slf4j', name: 'slf4j-simple', version: '1.7.32'
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.1'
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.5.1'
implementation group: 'org.bouncycastle', name: 'bcprov-jdk15to18', version: '1.69'
全体
plugins {
id 'com.android.application'
id 'kotlin-android'
}
android {
compileSdkVersion 30
buildToolsVersion "30.0.3"
defaultConfig {
applicationId "PACKAGE.PROJECT"
minSdkVersion 24
targetSdkVersion 30
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = '1.8'
}
buildFeatures {
viewBinding = true
}
}
dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
implementation 'androidx.core:core-ktx:1.6.0'
implementation 'androidx.appcompat:appcompat:1.3.0'
implementation 'com.google.android.material:material:1.4.0'
implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
testImplementation 'junit:junit:4.+'
androidTestImplementation 'androidx.test.ext:junit:1.1.3'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
implementation files('libs/jcifs-ng-2.1.6.jar')
implementation group: 'org.slf4j', name: 'slf4j-api', version: '1.7.32'
implementation group: 'org.slf4j', name: 'slf4j-simple', version: '1.7.32'
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.1'
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.5.1'
implementation group: 'org.bouncycastle', name: 'bcprov-jdk15to18', version: '1.69'
}
<uses-permission androidname="android.permission.INTERNET"/>
<uses-permission androidname="android.permission.ACCESS_NETWORK_STATE"/>
<uses-permission androidname="android.permission.ACCESS_WIFI_STATE"/>
AndroidManifest.xml
xml version="1.0" encoding="utf-8"
<manifest xmlnsandroid="http://schemas.android.com/apk/res/android"
package="PACKAGE.PROJECT">
<uses-permission androidname="android.permission.INTERNET"/>
<uses-permission androidname="android.permission.ACCESS_NETWORK_STATE"/>
<uses-permission androidname="android.permission.ACCESS_WIFI_STATE"/>
<application
androidallowBackup="true"
androidicon="@mipmap/ic_launcher"
androidlabel="@string/app_name"
androidroundIcon="@mipmap/ic_launcher_round"
androidsupportsRtl="true"
androidtheme="@style/Theme.SmbClient">
<activity androidname=".MainActivity">
<intent-filter>
<action androidname="android.intent.action.MAIN" />
<category androidname="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
MainActivity
import android.os.Bundle
import android.util.Log
import androidx.appcompat.app.AppCompatActivity
import jcifs.CIFSContext
import jcifs.config.PropertyConfiguration
import jcifs.context.BaseContext
import jcifs.smb.NtlmPasswordAuthentication
import jcifs.smb.SmbFile
import kotlinx.coroutines.*
import java.util.*
import kotlin.coroutines.CoroutineContext
class MainActivity : AppCompatActivity(), CoroutineScope {
val user = "USER"
val password = "PASSWORD"
val domain = "192.168.1.1"
val smbroot = "smb://" + domain + "/SMB/DIR/"
val TAG: String = "MySMB"
private val job = Job()
override val coroutineContext: CoroutineContext
get() = Dispatchers.Main + job
override fun onDestroy() {
job.cancel()
super.onDestroy()
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
launch {
withContext(Dispatchers.IO) {
val smb = connectSMB(user, password, domain, smbroot)
Log.d(TAG, "Got SMB: " + smb.path)
var tmpSmbNormalFiles: MutableList<SmbFile> = mutableListOf<SmbFile>()
val allSmbNormalsFiles = getNormalSmbFiles(smb, tmpSmbNormalFiles)
for ( eachAllSmbFile in allSmbNormalsFiles) {
Log.d(TAG, "eachAllSmbFile" + eachAllSmbFile.path)
}
}
}
}
private suspend fun getNormalSmbFiles(givenDir: SmbFile, tmpSmbNormalFiles: MutableList<SmbFile>): MutableList<SmbFile> {
val childSmbs = givenDir.listFiles()
if (childSmbs.size > 0) {
for (eachChild in childSmbs) {
Log.d(TAG, "eachChildSmb is " + eachChild.path)
if (eachChild.isDirectory) {
getNormalSmbFiles(eachChild, tmpSmbNormalFiles)
} else if (eachChild.isFile) {
tmpSmbNormalFiles.add(eachChild)
} else {
continue
}
}
}
return tmpSmbNormalFiles
}
private suspend fun connectSMB(user: String, password: String, domain: String, smbroot: String): SmbFile {
lateinit var smb: SmbFile
coroutineScope {
smb = async(Dispatchers.IO) {
val prop = Properties()
prop.setProperty("jcifs.smb.client.minVersion", "SMB202")
prop.setProperty("jcifs.smb.client.maxVersion", "SMB300")
val bc = BaseContext(PropertyConfiguration(prop))
val creds = NtlmPasswordAuthentication(bc, domain, user, password)
val auth: CIFSContext = bc.withCredentials(creds)
SmbFile(smbroot, auth)
}.await()
}
return smb
}
}