2022 Nov. 23.
2021 Sep. 05.
2021 Sep. 04.
android バージョン 10
compileSdkVersion 30
minSdkVersion 24
targetSdkVersion 30
android studioのプロジェクトのapp/libsにjcifs-ngライブラリを配置
jcifs-ng-2.1.6.jar
https://mvnrepository.com/artifact/eu.agno3.jcifs/jcifs-ng/2.1.6 のFilesのbundleをクリックしてダウンロード
bcprov-jdk15to18-1.69.jar
https://mvnrepository.com/artifact/org.bouncycastle/bcprov-jdk15to18/1.69 のFilesのjarをクリックしてダウンロード
build.gradle(Module:app)
次の依存関係設定を記述する
implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.4.0-alpha03"
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.4.0-alpha03"
implementation files('libs/jcifs-ng-2.1.6.jar') <- app/libsに配置したバージョンに合わせる
implementation group: 'org.bouncycastle', name: 'bcprov-jdk15to18', version: '1.69' <- app/libsに配置したバージョンに合わせる
implementation group: 'org.slf4j', name: 'slf4j-api', version: '1.7.32'
implementation group: 'org.slf4j', name: 'slf4j-simple', version: '1.7.32'
全体
plugins {
id 'com.android.application'
id 'kotlin-android'
}
android {
compileSdkVersion 30
buildToolsVersion "30.0.3"
defaultConfig {
applicationId "net.sytes.rokkosan.smbclient2"
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 "androidx.lifecycle:lifecycle-livedata-ktx:2.4.0-alpha03"
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.4.0-alpha03"
implementation files('libs/jcifs-ng-2.1.6.jar')
implementation group: 'org.bouncycastle', name: 'bcprov-jdk15to18', version: '1.69'
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.kotlin:kotlin-stdlib:$kotlin_version"
implementation 'androidx.core:core-ktx:1.6.0'
implementation 'androidx.appcompat:appcompat:1.3.1'
implementation 'com.google.android.material:material:1.4.0'
implementation 'androidx.constraintlayout:constraintlayout:2.1.0'
testImplementation 'junit:junit:4.+'
androidTestImplementation 'androidx.test.ext:junit:1.1.3'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
}
権限許可
<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 xmlnsandroid="http://schemas.android.com/apk/res/android"
package="net.sytes.rokkosan.smbclient2">
<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.SmbClient2">
<activity androidname=".MainActivity">
<intent-filter>
<action androidname="android.intent.action.MAIN" />
<category androidname="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
MainActivity.kt
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.widget.ProgressBar
import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProvider
import net.sytes.rokkosan.smbclient2.databinding.ActivityMainBinding
class MainActivity : AppCompatActivity() {
val DOMAIN: String = "192.168.1.1"
val SMBROOT: String = "/SMB/SERVER/FILE/"
val USER: String = ""
val PASSWORD: String = ""
lateinit var androidPath: String
lateinit var smbViewModel: SmbViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
var isSmb: Boolean = false
binding.progBarHori.max = 100
var smbUrl = "smb://" + DOMAIN + SMBROOT
if (smbUrl.takeLast(1) != "/") {
smbUrl += "/"
}
androidPath = externalCacheDir!!.absolutePath
val smbViewModelFactory = SmbViewModelFactory(USER, PASSWORD, DOMAIN, smbUrl, androidPath)
smbViewModel = ViewModelProvider(this, smbViewModelFactory).get(SmbViewModel::class.java)
val pathObserver = Observer<String> { sPath ->
binding.tvSmbPath.text = sPath
}
val sizeObserver = Observer<Long> { sSize ->
binding.tvSmbSize.text = (sSize / 1000L).toString() + "KB"
}
val downloadedRatioObserver = Observer<Int> { dRatio ->
binding.progBarHori.progress = dRatio
}
val isConnectedObserver = Observer<Boolean> { isConnected ->
if (!isConnected) {
isSmb = false
binding.tvInfo01.setText(R.string.failedConnectSmb)
} else {
isSmb = true
smbViewModel.smbPath.observe(this, pathObserver)
smbViewModel.smbSize.observe(this, sizeObserver)
smbViewModel.downloadedRatio.observe(this, downloadedRatioObserver)
binding.progBarHori.visibility = ProgressBar.VISIBLE
}
}
binding.tvInfo01.setText(R.string.wait)
smbViewModel.downloadSmbFile()
smbViewModel.isConnectedSmb.observe(this,isConnectedObserver)
if (!isSmb) {
return
}
}
}
SmbViewModel.kt
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import jcifs.config.PropertyConfiguration
import jcifs.context.BaseContext
import jcifs.smb.NtlmPasswordAuthenticator
import jcifs.smb.SmbFile
import kotlinx.coroutines.*
import java.io.File
import java.io.FileOutputStream
import java.util.*
class SmbViewModel(val user: String, val password: String, val domain: String, val smbUrl: String, val androidPath: String): ViewModel() {
private val _smbPath: MutableLiveData<String> by lazy {
MutableLiveData<String>()
}
val smbPath: LiveData<String>
get() = _smbPath
private val _smbSize: MutableLiveData<Long> by lazy {
MutableLiveData<Long>()
}
val smbSize: LiveData<Long>
get() = _smbSize
private val _isConnectedSmb = MutableLiveData<Boolean>()
val isConnectedSmb: LiveData<Boolean>
get() = _isConnectedSmb
private var _downloadedRatio: MutableLiveData<Int> = MutableLiveData<Int>().also {
mutableLiveData -> mutableLiveData.value = -1
}
public val downloadedRatio: LiveData<Int> get() = _downloadedRatio
fun downloadSmbFile() {
viewModelScope.launch(Dispatchers.IO) {
lateinit var smb: SmbFile
try {
val prop = Properties()
prop.setProperty("jcifs.smb.client.minVersion", "SMB202")
prop.setProperty("jcifs.smb.client.maxVersion", "SMB300")
val baseCxt = BaseContext(PropertyConfiguration(prop))
val auth =
baseCxt.withCredentials(NtlmPasswordAuthenticator(domain, user, password))
smb = SmbFile(smbUrl, auth)
if (!smb.exists()) {
_isConnectedSmb.postValue(false)
return@launch
}
} catch (e: Exception) {
_isConnectedSmb.postValue(false)
return@launch
}
_isConnectedSmb.postValue(true)
_smbPath.postValue(smb.path)
_smbSize.postValue(smb.length())
val fileName = smb.name
val fileSize = smb.length()
val exterCacheFile = File(androidPath + "/" + fileName)
val inStream = smb.openInputStream()
val fileOutStream = FileOutputStream(exterCacheFile)
val buf = ByteArray(1024)
var len = 0
var downloadedSize = 0L
while (true) {
len = inStream.read(buf)
if (len < 0) {
break
}
downloadedSize += len.toLong()
_downloadedRatio.postValue((downloadedSize * 100L / fileSize).toInt())
fileOutStream.write(buf)
}
fileOutStream.flush();
fileOutStream.close();
inStream.close()
smb.close()
}
}
override fun onCleared() {
super.onCleared()
viewModelScope.cancel()
}
}
SmbViewModelFactory.kt
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
class SmbViewModelFactory(val user: String, val password: String, val domain: String, val smbUrl: String): ViewModelProvider.Factory {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
return SmbViewModel(user, password, domain, smbUrl) as T
}
}