Skip to content

Commit

Permalink
Added untracked files
Browse files Browse the repository at this point in the history
  • Loading branch information
Sheikh-Abdul-Aziz committed Aug 14, 2024
1 parent 91b4271 commit 45d047a
Show file tree
Hide file tree
Showing 20 changed files with 550 additions and 0 deletions.
Empty file.
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
/**
* Copyright 2024 Zyron Official.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.zyron.filetree.callback

import androidx.recyclerview.widget.DiffUtil
import com.zyron.filetree.datamodel.FileTreeNode

/**
* A DiffUtil.ItemCallback implementation for comparing FileTreeNode objects.
*/
class FileTreeDiffCallback : DiffUtil.ItemCallback<FileTreeNode>() {

/**
* Determines if two FileTreeNode items represent the same file by comparing their absolute paths.
*
* @param oldItem The previous FileTreeNode item.
* @param newItem The new FileTreeNode item.
* @return True if the two items represent the same file, false otherwise.
*/
override fun areItemsTheSame(oldItem: FileTreeNode, newItem: FileTreeNode): Boolean {
return oldItem.file.absolutePath == newItem.file.absolutePath
}

/**
* Determines if the contents of two FileTreeNode items are the same.
*
* @param oldItem The previous FileTreeNode item.
* @param newItem The new FileTreeNode item.
* @return True if the contents of the two items are the same, false otherwise.
*/
override fun areContentsTheSame(oldItem: FileTreeNode, newItem: FileTreeNode): Boolean {
return oldItem == newItem
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
/**
* Copyright 2024 Zyron Official.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.zyron.filetree.datamodel

import java.io.File

/**
* Represents a node in a file tree, corresponding to a file or directory.
*
* @property file The file or directory represented by this node.
* @property parent The parent node of this node in the file tree.
* @property level The depth level of this node in the file tree.
*/
data class FileTreeNode(var file: File, var parent: FileTreeNode? = null, var level: Int = 0) {
var isExpanded: Boolean = false
var childrenStartIndex: Int = 0
var childrenEndIndex: Int = 0
var childrenLoaded: Boolean = false

/**
* Sorts the children of this node, separating directories from files and sorting them alphabetically.
*
* @return A list of FileTreeNode objects representing the sorted children.
*/
fun sortNode(): List<FileTreeNode> {
val children = file.listFiles()
?.asSequence()
?.partition { it.isDirectory }
?.let { (directories, files) ->
(directories.sortedBy { it.name.lowercase() } + files.sortedBy { it.name.lowercase() })
} ?: emptyList()
return children.map { childFile ->
FileTreeNode(file = childFile, parent = this, level = level + 1)
}
}

override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other == null || javaClass != other.javaClass) return false
other as FileTreeNode
return file.absolutePath == other.file.absolutePath
}

override fun hashCode(): Int {
return file.absolutePath.hashCode()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
/**
* Copyright 2024 Zyron Official.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.zyron.filetree.events

import java.io.File

/**
* Interface to listen for events within a file tree, including clicks and updates.
*/
interface FileTreeEventListener {

/**
* Called when a file is clicked.
*
* @param file The file that was clicked.
*/
fun onFileClick(file: File)

/**
* Called when a folder is clicked.
*
* @param folder The folder that was clicked.
*/
fun onFolderClick(folder: File)

/**
* Called when a file is long-clicked.
*
* @param file The file that was long-clicked.
* @return True if the long-click event was handled, false otherwise.
*/
fun onFileLongClick(file: File): Boolean

/**
* Called when a folder is long-clicked.
*
* @param folder The folder that was long-clicked.
* @return True if the long-click event was handled, false otherwise.
*/
fun onFolderLongClick(folder: File): Boolean

/**
* Called when the file tree view is updated.
*
* @param startPosition The starting position of the update.
* @param itemCount The number of items updated.
*/
fun onFileTreeViewUpdated(startPosition: Int, itemCount: Int)
}
127 changes: 127 additions & 0 deletions filetree/src/main/kotlin/com/zyron/filetree/map/ConcurrentFileMap.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
/**
* Copyright 2024 Zyron Official.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.zyron.filetree.map

import com.zyron.filetree.datamodel.FileTreeNode
import java.util.concurrent.ConcurrentHashMap
import java.util.concurrent.Executors
import java.util.concurrent.Future
import java.util.concurrent.ScheduledExecutorService
import java.util.concurrent.TimeUnit

/**
* Manages a cache of file nodes for efficient access and manipulation using concurrency.
*
* @param nodes A list of root nodes to be processed and cached.
* @param maxSize The maximum size of the cache before trimming.
*/
class ConcurrentFileMap(private val nodes: List<FileTreeNode>, private val maxSize: Int = 100) : Runnable {

companion object {
val concurrentFileMap: MutableMap<FileTreeNode, List<FileTreeNode>> = ConcurrentHashMap()
}

private val cache = ConcurrentHashMap<FileTreeNode, List<FileTreeNode>>(maxSize)
private val executor: ScheduledExecutorService = Executors.newScheduledThreadPool(Runtime.getRuntime().availableProcessors())

init {
// Schedule cache trimming at fixed intervals
executor.scheduleAtFixedRate(::trimCache, 10, 10, TimeUnit.SECONDS)
}

/**
* Retrieves a list of child nodes for a given file node from the cache.
*
* @param node The parent file node.
* @return A list of child file nodes, or null if the node is not in the cache.
*/
fun get(node: FileTreeNode): List<FileTreeNode>? {
return cache[node]
}

/**
* Adds a file node and its children to the cache.
*
* @param node The parent file node.
* @param result The list of child file nodes.
*/
fun put(node: FileTreeNode, result: List<FileTreeNode>) {
cache[node] = result
if (cache.size > maxSize) {
trimCache()
}
}

/**
* Clears the entire cache.
*/
fun clear() {
cache.clear()
}

/**
* Processes a list of file nodes asynchronously to populate the cache.
*
* @param nodes The list of file nodes to process.
*/
private fun processNodes(nodes: List<FileTreeNode>) {
val futures = mutableListOf<Future<*>>()
for (node in nodes) {
val future = executor.submit {
val nodeList = node.sortNode()
put(node, nodeList)
processNodes(nodeList)
}
futures.add(future)
}
futures.forEach { it.get() }
}

/**
* Trims the cache to the specified maximum size by removing excess entries.
*/
private fun trimCache() {
if (cache.size > maxSize) {
val keysToRemove = cache.keys.take(cache.size - maxSize)
for (key in keysToRemove) {
cache.remove(key)
}
}
}

/**
* Entry point for processing nodes when this Runnable is executed.
*/
override fun run() {
processNodes(nodes)
}

/**
* Shuts down the executor service and cancels all running tasks.
*/
fun shutdown() {
executor.shutdown()
try {
if (!executor.awaitTermination(60, TimeUnit.SECONDS)) {
executor.shutdownNow()
}
} catch (ex: InterruptedException) {
executor.shutdownNow()
Thread.currentThread().interrupt()
}
}
}
65 changes: 65 additions & 0 deletions filetree/src/main/kotlin/com/zyron/filetree/map/FileMapManager.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
/**
* Copyright 2024 Zyron Official.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.zyron.filetree.map

import com.zyron.filetree.datamodel.FileTreeNode
import java.util.concurrent.Executors
import java.util.concurrent.Future
import android.util.Log

/**
* Manages the mapping of file nodes using a single-threaded executor.
*/
object FileMapManager {
private var fileCacheFuture: Future<*>? = null
private val executor = Executors.newSingleThreadExecutor()

/**
* Stops the current file mapping task, if one is running.
*/
fun stopFileMapping() {
fileCacheFuture?.cancel(true)
fileCacheFuture = null
Log.i(this::class.java.simpleName, "FileMapping stopped")
}

/**
* Starts the file mapping process on a separate thread.
*
* @param nodes The list of root nodes to process.
* @param priority Optional priority setting for the mapping thread.
*/
fun startFileMapping(nodes: List<FileTreeNode>, priority: Int? = null) {
if (fileCacheFuture == null) {
fileCacheFuture = executor.submit {
try {
val fileCache = ConcurrentFileMap(nodes)
fileCache.run()
} catch (e: InterruptedException) {
Thread.currentThread().interrupt()
} catch (e: Exception) {
Log.e(this::class.java.simpleName, "Error in FileMapping", e)
}
}.apply {
priority?.let { (this as Thread).priority = it }
}
Log.i(this::class.java.simpleName, "FileMapping started")
} else {
Log.e(this::class.java.simpleName, "FileMapping is already running; this might cause issues")
}
}
}
5 changes: 5 additions & 0 deletions filetree/src/main/res/anim/default_anim.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:interpolator="@android:interpolator/accelerate_decelerate"
android:duration="500">
</set>
5 changes: 5 additions & 0 deletions filetree/src/main/res/anim/default_animation.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<layoutAnimation xmlns:android="http://schemas.android.com/apk/res/android"
android:animationOrder="normal"
android:delay="10%"
android:animation="@anim/default_anim" >
</layoutAnimation>
Loading

0 comments on commit 45d047a

Please sign in to comment.