godot/platform/android/java/lib/src/org/godotengine/godot/io/directory/AssetsDirectoryAccess.kt

/**************************************************************************/
/*  AssetsDirectoryAccess.kt                                              */
/**************************************************************************/
/*                         This file is part of:                          */
/*                             GODOT ENGINE                               */
/*                        https://godotengine.org                         */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur.                  */
/*                                                                        */
/* Permission is hereby granted, free of charge, to any person obtaining  */
/* a copy of this software and associated documentation files (the        */
/* "Software"), to deal in the Software without restriction, including    */
/* without limitation the rights to use, copy, modify, merge, publish,    */
/* distribute, sublicense, and/or sell copies of the Software, and to     */
/* permit persons to whom the Software is furnished to do so, subject to  */
/* the following conditions:                                              */
/*                                                                        */
/* The above copyright notice and this permission notice shall be         */
/* included in all copies or substantial portions of the Software.        */
/*                                                                        */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,        */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF     */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY   */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,   */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE      */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.                 */
/**************************************************************************/

package org.godotengine.godot.io.directory

import android.content.Context
import android.util.Log
import android.util.SparseArray
import org.godotengine.godot.io.StorageScope
import org.godotengine.godot.io.directory.DirectoryAccessHandler.Companion.INVALID_DIR_ID
import org.godotengine.godot.io.directory.DirectoryAccessHandler.Companion.STARTING_DIR_ID
import org.godotengine.godot.io.file.AssetData
import java.io.File
import java.io.IOException

/**
 * Handles directories access within the Android assets directory.
 */
internal class AssetsDirectoryAccess(private val context: Context) : DirectoryAccessHandler.DirectoryAccess {

	companion object {
		private val TAG = AssetsDirectoryAccess::class.java.simpleName

		internal fun getAssetsPath(originalPath: String): String {
			if (originalPath.startsWith(File.separator)) {
				return originalPath.substring(File.separator.length)
			}
			if (originalPath.startsWith(StorageScope.Identifier.ASSETS_PREFIX)) {
				return originalPath.substring(StorageScope.Identifier.ASSETS_PREFIX.length)
			}
			return originalPath
		}
	}

	private data class AssetDir(val path: String, val files: Array<String>, var current: Int = 0)

	private val assetManager = context.assets

	private var lastDirId = STARTING_DIR_ID
	private val dirs = SparseArray<AssetDir>()

	override fun hasDirId(dirId: Int) = dirs.indexOfKey(dirId) >= 0

	override fun dirOpen(path: String): Int {
		val assetsPath = getAssetsPath(path)
		try {
			val files = assetManager.list(assetsPath) ?: return INVALID_DIR_ID
			// Empty directories don't get added to the 'assets' directory, so
			// if files.length > 0 ==> path is directory
			// if files.length == 0 ==> path is file
			if (files.isEmpty()) {
				return INVALID_DIR_ID
			}

			val ad = AssetDir(assetsPath, files)

			dirs.put(++lastDirId, ad)
			return lastDirId
		} catch (e: IOException) {
			Log.e(TAG, "Exception on dirOpen", e)
			return INVALID_DIR_ID
		}
	}

	override fun dirExists(path: String): Boolean {
		val assetsPath = getAssetsPath(path)
		try {
			val files = assetManager.list(assetsPath) ?: return false
			// Empty directories don't get added to the 'assets' directory, so
			// if files.length > 0 ==> path is directory
			// if files.length == 0 ==> path is file
			return files.isNotEmpty()
		} catch (e: IOException) {
			Log.e(TAG, "Exception on dirExists", e)
			return false
		}
	}

	override fun fileExists(path: String) = AssetData.fileExists(context, path)

	override fun dirIsDir(dirId: Int): Boolean {
		val ad: AssetDir = dirs[dirId]

		var idx = ad.current
		if (idx > 0) {
			idx--
		}

		if (idx >= ad.files.size) {
			return false
		}

		val fileName = ad.files[idx]
		// List the contents of $fileName. If it's a file, it will be empty, otherwise it'll be a
		// directory
		val filePath = if (ad.path == "") fileName else "${ad.path}/${fileName}"
		val fileContents = assetManager.list(filePath)
		return (fileContents?.size?: 0) > 0
	}

	override fun isCurrentHidden(dirId: Int): Boolean {
		val ad = dirs[dirId]

		var idx = ad.current
		if (idx > 0) {
			idx--
		}

		if (idx >= ad.files.size) {
			return false
		}

		val fileName = ad.files[idx]
		return fileName.startsWith('.')
	}

	override fun dirNext(dirId: Int): String {
		val ad: AssetDir = dirs[dirId]

		if (ad.current >= ad.files.size) {
			ad.current++
			return ""
		}

		return ad.files[ad.current++]
	}

	override fun dirClose(dirId: Int) {
		dirs.remove(dirId)
	}

	override fun getDriveCount() = 0

	override fun getDrive(drive: Int) = ""

	override fun makeDir(dir: String) = false

	override fun getSpaceLeft() = 0L

	override fun rename(from: String, to: String) = AssetData.rename(from, to)

	override fun remove(filename: String) = AssetData.delete(filename)
}