FTP(File Transfer Protocol)를 활용해서 파일 업로드 및 다운로드 예제를 정리하려고 합니다.
1. FTP 사전 준비
AndroidManifest 권한 추가
(파일 읽고/쓰기 권한은 MainActivity 에서도 따로 권한 요청을 해주시기 바랍니다.)
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<application
android:requestLegacyExternalStorage="true"
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<application
android:requestLegacyExternalStorage="true"
...
</application>
</application>
Gradle 라이브러리 추가
implementation("commons-net:commons-net:3.6")
2. FTPUtil 클래스
FTPUtil 이름을 가진 object class를 생성하였습니다.
코틀린 코루틴을 활용하여 파일 업로드 및 다운로드 동작을 구현하였습니다.
사용하실 때 FTP 설정을 빠트리면 안됩니다! (경험담..)
object FTPUtil {
private val coroutineScope = CoroutineScope(Dispatchers.Default)
private var clientFTP = FTPClient()
// Input your default setting
private var host = ""
private var user = ""
private var password = ""
private var port = 21
fun changeFTPSetting(host: String, user: String, password: String, port: Int) {
this.host = host
this.user = user
this.password = password
this.port = port
}
suspend fun downloadFile(srcFilePath: String, desFilePath: String): Boolean {
return coroutineScope.async {
var result = false
try {
if (connect()) {
val fileOutputStream = FileOutputStream(desFilePath)
result = clientFTP.retrieveFile(srcFilePath, fileOutputStream)
fileOutputStream.close()
}
} catch (e: Exception) {
println("[FTPUtil] e $e")
e.printStackTrace()
}
disconnect()
println("[FTPUtil] downloadFile result : $result")
result
}.await()
}
suspend fun uploadFile(srcFilePath: String, desFileName: String, desDirectory: String): Boolean {
return coroutineScope.async {
var result = false
try {
if (connect()) {
val fileInputStream = FileInputStream(srcFilePath)
if (changeDirectory(desDirectory)) {
result = clientFTP.storeFile(desFileName, fileInputStream)
}
fileInputStream.close()
}
} catch (e: Exception) {
println("[FTPUtil] e $e")
e.printStackTrace()
}
disconnect()
println("[FTPUtil] uploadFile $result")
result
}.await()
}
suspend fun getFiles(directory: String): ArrayList<FTPFile> {
return coroutineScope.async {
val fileList = ArrayList<FTPFile>()
try {
if (connect()) {
val ftpFiles = clientFTP.listFiles(directory)
for (file in ftpFiles) fileList.add(file)
}
} catch (e: Exception) {
println("[FTPUtil] e $e")
e.printStackTrace()
}
disconnect()
println("[FTPUtil] getFiles size ${fileList.size}")
fileList
}.await()
}
private fun connect(): Boolean {
clientFTP.controlEncoding = "euc-kr"
clientFTP.connect(host, port)
if (!FTPReply.isPositiveCompletion(clientFTP.replyCode)) {
println("[FTPUtil] $host, $port connect fail")
return false
}
if (!clientFTP.login(user, password)) {
println("[FTPUtil] $user, $password login fail")
return false
}
clientFTP.enterLocalPassiveMode()
println("[FTPUtil] FTP connected : host($host), user($user), password($password), port($port)")
return true
}
private fun disconnect(): Boolean {
try {
clientFTP.logout()
clientFTP.disconnect()
} catch (e: Exception) {
e.printStackTrace()
println("[FTPUtil] e $e")
}
return true
}
private fun changeDirectory(directory: String): Boolean {
try {
clientFTP.changeWorkingDirectory(directory)
return true
} catch (e: Exception) {
e.printStackTrace()
println("[FTPUtil] e $e")
}
return false
}
private fun getCurrentDirectory(): String {
return try {
clientFTP.printWorkingDirectory()
} catch (e: Exception) {
""
}
}
}
private val coroutineScope = CoroutineScope(Dispatchers.Default)
private var clientFTP = FTPClient()
// Input your default setting
private var host = ""
private var user = ""
private var password = ""
private var port = 21
fun changeFTPSetting(host: String, user: String, password: String, port: Int) {
this.host = host
this.user = user
this.password = password
this.port = port
}
suspend fun downloadFile(srcFilePath: String, desFilePath: String): Boolean {
return coroutineScope.async {
var result = false
try {
if (connect()) {
val fileOutputStream = FileOutputStream(desFilePath)
result = clientFTP.retrieveFile(srcFilePath, fileOutputStream)
fileOutputStream.close()
}
} catch (e: Exception) {
println("[FTPUtil] e $e")
e.printStackTrace()
}
disconnect()
println("[FTPUtil] downloadFile result : $result")
result
}.await()
}
suspend fun uploadFile(srcFilePath: String, desFileName: String, desDirectory: String): Boolean {
return coroutineScope.async {
var result = false
try {
if (connect()) {
val fileInputStream = FileInputStream(srcFilePath)
if (changeDirectory(desDirectory)) {
result = clientFTP.storeFile(desFileName, fileInputStream)
}
fileInputStream.close()
}
} catch (e: Exception) {
println("[FTPUtil] e $e")
e.printStackTrace()
}
disconnect()
println("[FTPUtil] uploadFile $result")
result
}.await()
}
suspend fun getFiles(directory: String): ArrayList<FTPFile> {
return coroutineScope.async {
val fileList = ArrayList<FTPFile>()
try {
if (connect()) {
val ftpFiles = clientFTP.listFiles(directory)
for (file in ftpFiles) fileList.add(file)
}
} catch (e: Exception) {
println("[FTPUtil] e $e")
e.printStackTrace()
}
disconnect()
println("[FTPUtil] getFiles size ${fileList.size}")
fileList
}.await()
}
private fun connect(): Boolean {
clientFTP.controlEncoding = "euc-kr"
clientFTP.connect(host, port)
if (!FTPReply.isPositiveCompletion(clientFTP.replyCode)) {
println("[FTPUtil] $host, $port connect fail")
return false
}
if (!clientFTP.login(user, password)) {
println("[FTPUtil] $user, $password login fail")
return false
}
clientFTP.enterLocalPassiveMode()
println("[FTPUtil] FTP connected : host($host), user($user), password($password), port($port)")
return true
}
private fun disconnect(): Boolean {
try {
clientFTP.logout()
clientFTP.disconnect()
} catch (e: Exception) {
e.printStackTrace()
println("[FTPUtil] e $e")
}
return true
}
private fun changeDirectory(directory: String): Boolean {
try {
clientFTP.changeWorkingDirectory(directory)
return true
} catch (e: Exception) {
e.printStackTrace()
println("[FTPUtil] e $e")
}
return false
}
private fun getCurrentDirectory(): String {
return try {
clientFTP.printWorkingDirectory()
} catch (e: Exception) {
""
}
}
}
3. FTP 업로드 / 다운로드 사용 예시
Upload, Download, GetFiles 버튼을 생성하여 아래와 같이 동작시켜 확인할 수 있었습니다.
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
private val coroutineScope = CoroutineScope(Dispatchers.Default)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = DataBindingUtil.setContentView(this, R.layout.activity_main)
permissionCheck()
binding.btnUpload.setOnClickListener {
coroutineScope.launch {
val srcFilePath = "sdcard/Download/test.txt"
val desFileName = "test.txt"
val desDirectory = "/user/tempDir"
FTPUtil.uploadFile(srcFilePath, desFileName, desDirectory)
}
}
binding.btnDownload.setOnClickListener {
coroutineScope.launch {
val srcFilePath = "/user/tempDir/test.txt"
val desFilePath = "sdcard/Download/test.txt"
FTPUtil.downloadFile(srcFilePath, desFilePath)
}
}
binding.btnGetFiles.setOnClickListener {
coroutineScope.launch {
val directory = "/user/tempDir"
val files = FTPUtil.getFiles(directory)
for (file in files) {
println("getFiles : ${file.name}")
}
}
}
}
...
private lateinit var binding: ActivityMainBinding
private val coroutineScope = CoroutineScope(Dispatchers.Default)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = DataBindingUtil.setContentView(this, R.layout.activity_main)
permissionCheck()
binding.btnUpload.setOnClickListener {
coroutineScope.launch {
val srcFilePath = "sdcard/Download/test.txt"
val desFileName = "test.txt"
val desDirectory = "/user/tempDir"
FTPUtil.uploadFile(srcFilePath, desFileName, desDirectory)
}
}
binding.btnDownload.setOnClickListener {
coroutineScope.launch {
val srcFilePath = "/user/tempDir/test.txt"
val desFilePath = "sdcard/Download/test.txt"
FTPUtil.downloadFile(srcFilePath, desFilePath)
}
}
binding.btnGetFiles.setOnClickListener {
coroutineScope.launch {
val directory = "/user/tempDir"
val files = FTPUtil.getFiles(directory)
for (file in files) {
println("getFiles : ${file.name}")
}
}
}
}
...
0 comments: