rokkonet

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

android開発 jcifs-ngで接続したSMBサーバーファイルをandroid端末にコピーする

2021 Aug. 15.

    /*
     * リモートのSMBファイルをandroid端末にコピーする
     *  コピー先は、android端末の外部ストレージのアプリキャッシュ領域
     *  smbFile  コピー元リモートのSMBファイル
     *  返り値 コピー先ファイル
     */
    private fun cpSmbFile2android(smbFile: SmbFile): File? {
        if ( !smbFile.isFile ) {
            return null
        }
        val fileName = smbFile.name
        val exterCacheFile = File(this.externalCacheDir!!.path + "/" + fileName)
        // copy remote smb-file to local
        val inStream = smbFile.openInputStream()
        val fileOutStream = FileOutputStream(exterCacheFile)
        val buf = ByteArray(1024)
        var len: Int = 0
        while (true) {
            len = inStream.read(buf)
            if (len < 0) break
            fileOutStream.write(buf)
        }
        fileOutStream.flush();
        fileOutStream.close();
        inStream.close();
        return exterCacheFile
    }

java/kotlin jcifs-ng SMBサーバーのディレクトリ内の全ファイルを再帰的に取得する

2021 Sep. 06.
2021 Aug. 15.

    /*
     * SMBサーバーのすべてのノーマルファイルを取得する再帰関数
     * パラメータ
     *   givenDir  取得対象のSMBディレクトリパス。
     *   tmpSmbNormalFiles  空のMutableList<SmbFile>型変数。
     *       1つ前の再帰関数実行結果を次の再帰関数に渡すための変数。
     *       ユーザーは最初にこの関数を起動するので、空のMutableListを与える。
     */
    private fun getNormalSmbFiles(givenDir: SmbFile, tmpSmbNormalFiles: MutableList<SmbFile>): MutableList<SmbFile>  {
        val childSmbs = givenDir.listFiles()
        if (childSmbs.size > 0) {
            for (eachChild in childSmbs) {
                if (eachChild.isDirectory) {
                    getNormalSmbFiles(eachChild, tmpSmbNormalFiles)
                } else if (eachChild.isFile) {
                    tmpSmbNormalFiles.add(eachChild)
                } else {
                    continue
                }
            }
        }
        return tmpSmbNormalFiles
    }

android開発 MediaPlayer利用時の後始末

2021 Aug. 15.

{
    private val myPlayer: MediaPlayer = MediaPlayer()

    override fun onDestroy() {
        // 終了時のMediaPlayerの破棄
        if (myPlayer != null) {
            myPlayer.reset()
            myPlayer.release()
        }

    override fun onCreate(savedInstanceState: Bundle?) {
        myPlayer?.setOnCompletionListener { mp -> audioStop() }
    }


    private fun audioStop(){
        myPlayer?.run {
            stop()
            reset()
            release()
            finishAndRemoveTask()
        }
    }
}

kotlin ファイル拡張子が複数の検索拡張子のいずれかに該当するか調べる

2021 Aug. 15.

kotlinで、ファイル拡張子が複数の検索拡張子のいずれかに該当するかを、大文字小文字を区別せずに調べる

    val strTarget: String = "FILENAME.m4a"
    val strExtension: String = "\\.mp3|\\.wav|\\.aac|\\.au|\\.gsm|\\.m4a|\\.ogg|\\.mkv|\\.3gp|\\.flac"
    isMatchTail(strTarget, strExtension)


    /*
     * target文字列の一番後ろのピリオド以降にconditionStr正規表現文字列が含まれていればtrueを返す
     */
    private fun isMatchTail(target: String, conditionStr: String): Boolean {
        // target中の一番後ろのピリオド(ファイル拡張子の区切り)の場所を検出する
        val idxStr = target.lastIndexOf(".")
        if (idxStr < 0) {
            return false
        }
        // targetから、ピリオドから末尾までの文字列を切り出す
        val extenStr = target.substring(idxStr)
        // 切り出した文字列がconditionStrに一致するか調べる
        val regex = (conditionStr+"$").toRegex(RegexOption.IGNORE_CASE)
        return regex.containsMatchIn(extenStr)
    }

kotlin 文字列の一番後ろのドット以降に検索正規表現文字列が含まれていればtrueを返す関数

2021 Aug. 15.

kotlinで、文字列の一番後ろのドット以降に、検索正規表現文字列が(大文字小文字区別なしに)含まれていればtrueを返す関数

    /*
     * target文字列の一番後ろのピリオド以降にconditionStr正規表現文字列が含まれていればtrueを返す
     */
    private fun isMatchTail(target: String, conditionStr: String): Boolean {
        // target中の一番後ろのピリオド(ファイル拡張子の区切り)の場所を検出する
        val idxStr = target.lastIndexOf(".")
        if (idxStr < 0) {
            return false
        }
        // targetから、ピリオドから末尾までの文字列を切り出す
        val extenStr = target.substring(idxStr)
        // 切り出した文字列がconditionStrに一致するか調べる
        val regex = (conditionStr+"$").toRegex(RegexOption.IGNORE_CASE)
        return regex.containsMatchIn(extenStr)
    }

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
    }
}