rokkonet

PC・Androidソフトウェア・アプリの開発・使い方に関するメモ

android開発 SMBサーバーに接続し、ディレクトリ内のファイルを再帰的に取得する

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 android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>


AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="PACKAGE.PROJECT">
    <uses-permission android:name="android.permission.INTERNET"/>
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
    <uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/Theme.SmbClient">
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="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 {
    // 認証情報
    //////////////////////////////////////////////
    // Please replace these values to your data //
    val user = "USER"
    val password = "PASSWORD"
    val domain = "192.168.1.1"
    val smbroot = "smb://" + domain + "/SMB/DIR/"
        // smbrootの末尾にはスラッシュ(/)を付ける
    //////////////////////////////////////////////

    val TAG: String = "MySMB"

    // coroutine準備
    private val job = Job()
    override val coroutineContext: CoroutineContext
        get() = Dispatchers.Main + job

    // 終了時のcoroutineのキャンセル設定
    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)

                // get all files recursively in the smb
                var tmpSmbNormalFiles: MutableList<SmbFile> = mutableListOf<SmbFile>()
                val allSmbNormalsFiles = getNormalSmbFiles(smb, tmpSmbNormalFiles)
                for ( eachAllSmbFile in allSmbNormalsFiles) {
                    Log.d(TAG, "eachAllSmbFile" + eachAllSmbFile.path)
                }
            }
        }
    }

    /*
     * SMBサーバーのノーマルファイルを検索する再帰関数
     * パラメータ
     *   givenDir  検索するSMBディレクトリパス。
     *   tmpSmbNormalFiles  空のMutableList<SmbFile>型変数。
     *       1つ前の再帰関数実行結果を次の再帰関数に渡すための変数。
     *       ユーザーは最初にこの関数を起動するので、空のMutableListを与える。
     */
    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
    }

    // SMBサーバーに接続する関数
    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
    }
}