Update project structure and impl all domain layer
This commit is contained in:
@@ -3,10 +3,14 @@ package com.megboyzz.devmenu
|
||||
import android.os.Bundle
|
||||
import androidx.activity.ComponentActivity
|
||||
import androidx.activity.compose.setContent
|
||||
import com.megboyzz.devmenu.di.components.AppComponent
|
||||
import dagger.internal.DaggerGenerated
|
||||
|
||||
class MainActivity : ComponentActivity() {
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
|
||||
setContent {}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
package com.megboyzz.devmenu.db
|
||||
|
||||
import androidx.room.Database
|
||||
import androidx.room.RoomDatabase
|
||||
import com.megboyzz.devmenu.db.dao.ReplacementDao
|
||||
import com.megboyzz.devmenu.db.entites.Replacement
|
||||
|
||||
@Database(entities = [Replacement::class], version = 1, exportSchema = false)
|
||||
abstract class AppDatabase: RoomDatabase() {
|
||||
abstract val replacementDao: ReplacementDao
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package com.megboyzz.devmenu.db.dao
|
||||
|
||||
import androidx.room.Dao
|
||||
import androidx.room.Delete
|
||||
import androidx.room.Insert
|
||||
import androidx.room.Query
|
||||
import com.megboyzz.devmenu.db.entites.Replacement
|
||||
|
||||
@Dao
|
||||
interface ReplacementDao {
|
||||
|
||||
@get:Query("SELECT * from replacement")
|
||||
val all: List<Replacement>
|
||||
|
||||
@Insert
|
||||
fun addReplacement(replacement: Replacement)
|
||||
|
||||
@Query("SELECT * FROM replacement WHERE original_file_name=:originalFileName")
|
||||
fun getReplacementByOriginalName(originalFileName: String): Replacement
|
||||
|
||||
@Delete
|
||||
fun removeReplacement(replacement: Replacement)
|
||||
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package com.megboyzz.devmenu.db.entites
|
||||
|
||||
import androidx.room.ColumnInfo
|
||||
import androidx.room.Entity
|
||||
import androidx.room.PrimaryKey
|
||||
|
||||
@Entity
|
||||
data class Replacement(
|
||||
|
||||
@PrimaryKey(autoGenerate = true)
|
||||
val id: Int = 0,
|
||||
|
||||
@ColumnInfo(name = "original_file_name")
|
||||
val originalFileName: String,
|
||||
|
||||
@ColumnInfo(name = "replacement_file_name")
|
||||
val replacementFileName: String
|
||||
)
|
||||
@@ -0,0 +1,50 @@
|
||||
package com.megboyzz.devmenu.di.components
|
||||
|
||||
import android.content.Context
|
||||
import com.megboyzz.devmenu.db.entites.Replacement
|
||||
import com.megboyzz.devmenu.di.components.subcomponents.FilesUseCaseComponent
|
||||
import com.megboyzz.devmenu.di.components.subcomponents.HideFilesUseCaseComponent
|
||||
import com.megboyzz.devmenu.di.components.subcomponents.ReplacementUseCaseComponent
|
||||
import com.megboyzz.devmenu.di.components.subcomponents.SettingsUseCaseComponent
|
||||
import com.megboyzz.devmenu.di.components.subcomponents.TrackingHandlerUseCaseComponent
|
||||
import com.megboyzz.devmenu.di.modules.DatabaseModule
|
||||
import com.megboyzz.devmenu.di.modules.RepositoryModule
|
||||
import com.megboyzz.devmenu.di.modules.SubcomponentsModule
|
||||
import com.megboyzz.devmenu.domain.usecases.files.GetFilePropsUseCase
|
||||
import dagger.BindsInstance
|
||||
import dagger.Component
|
||||
import dagger.Subcomponent
|
||||
import javax.inject.Singleton
|
||||
|
||||
|
||||
@Component(
|
||||
modules = [
|
||||
SubcomponentsModule::class,
|
||||
DatabaseModule::class,
|
||||
RepositoryModule::class
|
||||
]
|
||||
)
|
||||
@Singleton
|
||||
interface AppComponent {
|
||||
|
||||
val filesUseCaseComponent: FilesUseCaseComponent.Builder
|
||||
|
||||
val hideFilesUseCaseComponent: HideFilesUseCaseComponent.Builder
|
||||
|
||||
val replacementUseCaseComponent: ReplacementUseCaseComponent.Builder
|
||||
|
||||
val settingsUseCaseComponent: SettingsUseCaseComponent.Builder
|
||||
|
||||
val trackingHandlerUseCaseComponent: TrackingHandlerUseCaseComponent.Builder
|
||||
|
||||
@Component.Builder
|
||||
interface Builder{
|
||||
|
||||
@BindsInstance
|
||||
fun context(context: Context): Builder
|
||||
|
||||
fun build(): AppComponent
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
package com.megboyzz.devmenu.di.components.subcomponents
|
||||
|
||||
import android.content.Context
|
||||
import com.megboyzz.devmenu.di.components.AppComponent
|
||||
import com.megboyzz.devmenu.domain.usecases.files.GetExternalFilesUseCase
|
||||
import com.megboyzz.devmenu.domain.usecases.files.GetFilePropsUseCase
|
||||
import com.megboyzz.devmenu.domain.usecases.files.GetInternalFilesUseCase
|
||||
import com.megboyzz.devmenu.domain.usecases.files.RemoveFileUseCase
|
||||
import com.megboyzz.devmenu.domain.usecases.files.RenameFileUseCase
|
||||
import dagger.BindsInstance
|
||||
import dagger.Component
|
||||
import dagger.Subcomponent
|
||||
|
||||
@Subcomponent
|
||||
interface FilesUseCaseComponent {
|
||||
|
||||
val getExternalFilesUseCase: GetExternalFilesUseCase
|
||||
|
||||
val getInternalFilesUseCase: GetInternalFilesUseCase
|
||||
|
||||
val getFilePropsUseCase: GetFilePropsUseCase
|
||||
|
||||
val removeFileUseCase: RemoveFileUseCase
|
||||
|
||||
val renameFileUseCase: RenameFileUseCase
|
||||
|
||||
@Component.Builder
|
||||
interface Builder{
|
||||
fun build(): AppComponent
|
||||
}
|
||||
|
||||
}
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
package com.megboyzz.devmenu.di.components.subcomponents
|
||||
|
||||
import com.megboyzz.devmenu.di.components.AppComponent
|
||||
import com.megboyzz.devmenu.domain.usecases.hidefiles.GetAllHidedFilesUseCase
|
||||
import com.megboyzz.devmenu.domain.usecases.hidefiles.HideFileUseCase
|
||||
import com.megboyzz.devmenu.domain.usecases.hidefiles.UnHideAllUseCase
|
||||
import com.megboyzz.devmenu.domain.usecases.hidefiles.UnHideFileUseCase
|
||||
import dagger.Component
|
||||
import dagger.Subcomponent
|
||||
|
||||
@Subcomponent
|
||||
interface HideFilesUseCaseComponent {
|
||||
|
||||
val getAllHidedFilesUseCase: GetAllHidedFilesUseCase
|
||||
|
||||
val hideFileUseCase: HideFileUseCase
|
||||
|
||||
val unHideFileUseCase: UnHideFileUseCase
|
||||
|
||||
val unHideAllUseCase: UnHideAllUseCase
|
||||
|
||||
@Component.Builder
|
||||
interface Builder{
|
||||
fun build(): AppComponent
|
||||
}
|
||||
|
||||
}
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
package com.megboyzz.devmenu.di.components.subcomponents
|
||||
|
||||
import com.megboyzz.devmenu.di.components.AppComponent
|
||||
import com.megboyzz.devmenu.domain.usecases.replacement.GetAllReplacedFilesUseCase
|
||||
import com.megboyzz.devmenu.domain.usecases.replacement.RecoverAllReplacementsUseCase
|
||||
import com.megboyzz.devmenu.domain.usecases.replacement.RecoverReplacementUseCase
|
||||
import com.megboyzz.devmenu.domain.usecases.replacement.ReplaceFileUseCase
|
||||
import dagger.Component
|
||||
import dagger.Subcomponent
|
||||
|
||||
@Subcomponent
|
||||
interface ReplacementUseCaseComponent {
|
||||
|
||||
val getAllReplacedFilesUseCase: GetAllReplacedFilesUseCase
|
||||
|
||||
val recoverAllReplacedFilesUseCase: RecoverAllReplacementsUseCase
|
||||
|
||||
val recoverReplacementUseCase: RecoverReplacementUseCase
|
||||
|
||||
val replaceFileUseCase: ReplaceFileUseCase
|
||||
|
||||
@Component.Builder
|
||||
interface Builder{
|
||||
fun build(): AppComponent
|
||||
}
|
||||
|
||||
}
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
package com.megboyzz.devmenu.di.components.subcomponents
|
||||
|
||||
import com.megboyzz.devmenu.di.components.AppComponent
|
||||
import com.megboyzz.devmenu.domain.usecases.settings.RemoveGameDataUseCase
|
||||
import com.megboyzz.devmenu.domain.usecases.settings.SetGameLangUseCase
|
||||
import com.megboyzz.devmenu.domain.usecases.settings.SetTrackingSaveFileUseCase
|
||||
import com.megboyzz.devmenu.domain.usecases.settings.TurnOffDevMenuUseCase
|
||||
import dagger.Component
|
||||
import dagger.Subcomponent
|
||||
|
||||
@Subcomponent
|
||||
interface SettingsUseCaseComponent {
|
||||
|
||||
val removeGameDataUseCase: RemoveGameDataUseCase
|
||||
|
||||
val setGameLangUseCase: SetGameLangUseCase
|
||||
|
||||
val setTrackingSaveFileUseCase: SetTrackingSaveFileUseCase
|
||||
|
||||
val turnOffDevMenuUseCase: TurnOffDevMenuUseCase
|
||||
|
||||
@Component.Builder
|
||||
interface Builder{
|
||||
fun build(): AppComponent
|
||||
}
|
||||
|
||||
}
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
package com.megboyzz.devmenu.di.components.subcomponents
|
||||
|
||||
import com.megboyzz.devmenu.di.components.AppComponent
|
||||
import com.megboyzz.devmenu.domain.usecases.IsDevMenuEnabledUseCase
|
||||
import com.megboyzz.devmenu.domain.usecases.StartTrackingUseCase
|
||||
import com.megboyzz.devmenu.domain.usecases.StopTrackingUseCase
|
||||
import dagger.Component
|
||||
import dagger.Subcomponent
|
||||
|
||||
@Subcomponent
|
||||
interface TrackingHandlerUseCaseComponent {
|
||||
|
||||
val isDevMenuEnabledUseCase: IsDevMenuEnabledUseCase
|
||||
|
||||
val startTrackingUseCase: StartTrackingUseCase
|
||||
|
||||
val stopTrackingUseCase: StopTrackingUseCase
|
||||
|
||||
@Component.Builder
|
||||
interface Builder{
|
||||
fun build(): AppComponent
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package com.megboyzz.devmenu.di.modules
|
||||
|
||||
import android.content.Context
|
||||
import androidx.room.Room
|
||||
import androidx.room.RoomDatabase
|
||||
import com.megboyzz.devmenu.db.AppDatabase
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
|
||||
@Module
|
||||
class DatabaseModule {
|
||||
|
||||
@Provides
|
||||
fun providesDataBase(context: Context): AppDatabase =
|
||||
Room.databaseBuilder(
|
||||
context,
|
||||
AppDatabase::class.java,
|
||||
"replacement-db"
|
||||
).build()
|
||||
|
||||
|
||||
@Provides
|
||||
fun providesReplacementDao(appDatabase: AppDatabase) = appDatabase.replacementDao
|
||||
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
package com.megboyzz.devmenu.di.modules
|
||||
|
||||
import android.content.Context
|
||||
import com.megboyzz.devmenu.db.dao.ReplacementDao
|
||||
import com.megboyzz.devmenu.domain.repository.FilesRepository
|
||||
import com.megboyzz.devmenu.domain.repository.ReplacementRepository
|
||||
import com.megboyzz.devmenu.domain.repository.SaveRepository
|
||||
import com.megboyzz.devmenu.domain.repository.SettingsRepository
|
||||
import com.megboyzz.devmenu.domain.repository.TrackingRepository
|
||||
import com.megboyzz.devmenu.repositories.FilesRepositoryImpl
|
||||
import com.megboyzz.devmenu.repositories.ReplacementRepositoryImpl
|
||||
import com.megboyzz.devmenu.repositories.SaveRepositoryImpl
|
||||
import com.megboyzz.devmenu.repositories.SettingsRepositoryImpl
|
||||
import com.megboyzz.devmenu.repositories.TrackingRepositoryImpl
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
|
||||
@Module
|
||||
class RepositoryModule {
|
||||
|
||||
@Provides
|
||||
fun providesFilesRepository(context: Context): FilesRepository =
|
||||
FilesRepositoryImpl(context)
|
||||
|
||||
@Provides
|
||||
fun providesReplacementRepository(
|
||||
replacementDao: ReplacementDao,
|
||||
filesRepository: FilesRepository
|
||||
): ReplacementRepository =
|
||||
ReplacementRepositoryImpl(replacementDao, filesRepository)
|
||||
|
||||
@Provides
|
||||
fun providesSaveRepositoryImpl(
|
||||
filesRepository: FilesRepository
|
||||
): SaveRepository =
|
||||
SaveRepositoryImpl(filesRepository)
|
||||
|
||||
@Provides
|
||||
fun providesSettingsRepository(
|
||||
filesRepository: FilesRepository
|
||||
): SettingsRepository =
|
||||
SettingsRepositoryImpl(filesRepository)
|
||||
|
||||
@Provides
|
||||
fun providesTrackingRepository(
|
||||
context: Context,
|
||||
filesRepository: FilesRepository
|
||||
): TrackingRepository =
|
||||
TrackingRepositoryImpl(context, filesRepository)
|
||||
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package com.megboyzz.devmenu.di.modules
|
||||
|
||||
import com.megboyzz.devmenu.di.components.subcomponents.FilesUseCaseComponent
|
||||
import com.megboyzz.devmenu.di.components.subcomponents.HideFilesUseCaseComponent
|
||||
import com.megboyzz.devmenu.di.components.subcomponents.ReplacementUseCaseComponent
|
||||
import com.megboyzz.devmenu.di.components.subcomponents.SettingsUseCaseComponent
|
||||
import com.megboyzz.devmenu.di.components.subcomponents.TrackingHandlerUseCaseComponent
|
||||
import dagger.Module
|
||||
|
||||
@Module(
|
||||
subcomponents = [
|
||||
FilesUseCaseComponent::class,
|
||||
HideFilesUseCaseComponent::class,
|
||||
ReplacementUseCaseComponent::class,
|
||||
SettingsUseCaseComponent::class,
|
||||
TrackingHandlerUseCaseComponent::class
|
||||
]
|
||||
)
|
||||
object SubcomponentsModule
|
||||
@@ -0,0 +1,32 @@
|
||||
package com.megboyzz.devmenu.repositories
|
||||
|
||||
import android.content.Context
|
||||
import com.megboyzz.devmenu.domain.entities.FileProps
|
||||
import com.megboyzz.devmenu.domain.repository.FilesRepository
|
||||
import java.io.File
|
||||
import java.util.Date
|
||||
|
||||
class FilesRepositoryImpl(
|
||||
private val context: Context
|
||||
): FilesRepository {
|
||||
|
||||
override val internalRoot = File(context.applicationInfo.dataDir)
|
||||
override val externalRoot = context.getExternalFilesDir(null)!!
|
||||
|
||||
override fun getFileProps(file: File) = FileProps(
|
||||
fileName = file.name,
|
||||
size = file.length(),
|
||||
lastModify = Date(file.lastModified())
|
||||
)
|
||||
|
||||
override suspend fun hideFile(file: File): Boolean {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
|
||||
override suspend fun unHideFile(file: File): Boolean {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
|
||||
override val allHidedFiles: List<File>
|
||||
get() = TODO("Not yet implemented")
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
package com.megboyzz.devmenu.repositories
|
||||
|
||||
import com.megboyzz.devmenu.db.dao.ReplacementDao
|
||||
import com.megboyzz.devmenu.db.entites.Replacement
|
||||
import com.megboyzz.devmenu.domain.repository.FilesRepository
|
||||
import com.megboyzz.devmenu.domain.repository.ReplacementRepository
|
||||
import java.io.File
|
||||
import java.lang.Exception
|
||||
import javax.inject.Inject
|
||||
import kotlin.random.Random
|
||||
|
||||
class ReplacementRepositoryImpl @Inject constructor(
|
||||
private val replacementDao: ReplacementDao,
|
||||
private val filesRepository: FilesRepository
|
||||
): ReplacementRepository {
|
||||
|
||||
private val folderToSaveReplacements = File("${filesRepository.internalRoot}/replacements")
|
||||
|
||||
private fun generateRandomPostfix(): String {
|
||||
val length = 10
|
||||
return (1..length)
|
||||
.map { _ -> Random.nextInt(0, 36) }
|
||||
.joinToString("") { charIndex -> charIndex.toString(radix = 36) }
|
||||
}
|
||||
override suspend fun replaceFile(what: File, to: File): Result<Boolean> {
|
||||
|
||||
return kotlin.runCatching {
|
||||
val replacementFile =
|
||||
File(folderToSaveReplacements, "replacement-${generateRandomPostfix()}")
|
||||
replacementFile.createNewFile()
|
||||
|
||||
to.copyTo(replacementFile, overwrite = true)
|
||||
what.copyTo(to, overwrite = true)
|
||||
|
||||
val replacement = Replacement(
|
||||
originalFileName = what.absolutePath,
|
||||
replacementFileName = replacementFile.absolutePath
|
||||
)
|
||||
replacementDao.addReplacement(replacement)
|
||||
|
||||
return Result.success(true)
|
||||
}.onFailure {
|
||||
return Result.failure(it)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
override suspend fun recoverReplacement(replacement: File): Result<Boolean> {
|
||||
return kotlin.runCatching {
|
||||
val rep = replacementDao.getReplacementByOriginalName(replacement.absolutePath)
|
||||
|
||||
val repFile = File(rep.replacementFileName)
|
||||
|
||||
repFile.copyTo(File(rep.originalFileName), overwrite = true)
|
||||
|
||||
repFile.delete()
|
||||
|
||||
replacementDao.removeReplacement(rep)
|
||||
|
||||
return Result.success(true)
|
||||
}.onFailure {
|
||||
return Result.failure(it)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun getAllReplacements(): List<File> {
|
||||
return replacementDao.all.map {
|
||||
File(it.originalFileName)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package com.megboyzz.devmenu.repositories
|
||||
|
||||
import com.megboyzz.devmenu.domain.repository.FilesRepository
|
||||
import com.megboyzz.devmenu.domain.repository.SaveRepository
|
||||
import java.io.File
|
||||
|
||||
class SaveRepositoryImpl(
|
||||
private val filesRepository: FilesRepository
|
||||
): SaveRepository {
|
||||
|
||||
private val saveFile = File(filesRepository.internalRoot, "/files/var/nfstr_save.sb")
|
||||
|
||||
override suspend fun loadSaveFile(file: File): Boolean {
|
||||
|
||||
file.copyTo(saveFile, overwrite = true)
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
override suspend fun uploadCurrentSaveFile(fileToUpload: File): Boolean {
|
||||
|
||||
saveFile.copyTo(fileToUpload, overwrite = true)
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
package com.megboyzz.devmenu.repositories
|
||||
|
||||
import android.content.res.Resources
|
||||
import com.megboyzz.devmenu.domain.entities.GameLang
|
||||
import com.megboyzz.devmenu.domain.repository.FilesRepository
|
||||
import com.megboyzz.devmenu.domain.repository.SettingsRepository
|
||||
import java.io.File
|
||||
import java.nio.charset.Charset
|
||||
import javax.inject.Inject
|
||||
|
||||
class SettingsRepositoryImpl @Inject constructor(
|
||||
private val filesRepository: FilesRepository
|
||||
): SettingsRepository {
|
||||
|
||||
private val localeFile = File(filesRepository.internalRoot, "files/var/locale")
|
||||
|
||||
private fun getSystemLangAsGameLang(): GameLang {
|
||||
|
||||
val configuration = Resources.getSystem().configuration
|
||||
return GameLang.values().find { it.name == configuration.locale.language } ?: GameLang.Russian
|
||||
|
||||
}
|
||||
|
||||
|
||||
override var gameLang: GameLang
|
||||
get() {
|
||||
|
||||
val systemLang = getSystemLangAsGameLang()
|
||||
|
||||
if(!localeFile.exists()){
|
||||
localeFile.createNewFile()
|
||||
localeFile.writeBytes(byteArrayOf(0x02, 0x0) + systemLang.name.toByteArray())
|
||||
return systemLang
|
||||
}
|
||||
|
||||
val curLocale = String(localeFile.readBytes().takeLast(2).toByteArray())
|
||||
return GameLang.values().find { it.name == curLocale } ?: systemLang
|
||||
}
|
||||
set(value) {
|
||||
|
||||
if(!localeFile.exists()) localeFile.createNewFile()
|
||||
|
||||
localeFile.writeBytes(byteArrayOf(0x02, 0x0) + value.name.toByteArray())
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
package com.megboyzz.devmenu.repositories
|
||||
|
||||
import android.content.Context
|
||||
import com.megboyzz.devmenu.domain.entities.TrackingData
|
||||
import com.megboyzz.devmenu.domain.repository.FilesRepository
|
||||
import com.megboyzz.devmenu.domain.repository.TrackingRepository
|
||||
import com.megboyzz.devmenu.util.SaveFileObserver
|
||||
import javax.inject.Inject
|
||||
|
||||
class TrackingRepositoryImpl @Inject constructor(
|
||||
private val context: Context,
|
||||
private val filesRepository: FilesRepository
|
||||
) : TrackingRepository {
|
||||
|
||||
private val TAG = this::class.simpleName
|
||||
|
||||
private val trackingStateKey = "tracking_enabled"
|
||||
private val trackingSavePath = "tracking_save_path"
|
||||
private val defaultPathToSave = "${filesRepository.externalRoot}/files/saveTracking"
|
||||
|
||||
private val saveFile = "${filesRepository.internalRoot}/files/var/nfstr_save.sb"
|
||||
|
||||
private val sharedPreferences = context.getSharedPreferences("tracking", Context.MODE_PRIVATE)
|
||||
|
||||
private val editor = sharedPreferences.edit()
|
||||
|
||||
private lateinit var saveFileObserver: SaveFileObserver
|
||||
|
||||
override var trackingData: TrackingData
|
||||
get() {
|
||||
val enabled = sharedPreferences.getBoolean(trackingStateKey, false)
|
||||
val path = sharedPreferences.getString(trackingStateKey, defaultPathToSave)?: defaultPathToSave
|
||||
|
||||
return TrackingData(
|
||||
enabled = enabled,
|
||||
pathToSave = path
|
||||
)
|
||||
}
|
||||
set(value) {
|
||||
with(editor) {
|
||||
putBoolean(trackingStateKey, value.enabled)
|
||||
putString(trackingSavePath, value.pathToSave)
|
||||
apply()
|
||||
}
|
||||
}
|
||||
|
||||
override fun startTracking() {
|
||||
saveFileObserver = SaveFileObserver(saveFile, trackingData.pathToSave)
|
||||
saveFileObserver.startWatching()
|
||||
}
|
||||
|
||||
override fun stopTracking() {
|
||||
saveFileObserver.stopWatching()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
package com.megboyzz.devmenu.util
|
||||
|
||||
import android.os.FileObserver
|
||||
import android.util.Log
|
||||
import java.io.File
|
||||
import java.io.FileInputStream
|
||||
import java.io.FileOutputStream
|
||||
import java.util.LinkedList
|
||||
import java.util.Queue
|
||||
import java.util.zip.ZipEntry
|
||||
import java.util.zip.ZipOutputStream
|
||||
|
||||
abstract class BaseObserver(source: String) : FileObserver(source) {
|
||||
|
||||
private val eventQueue : Queue<Int> = LinkedList()
|
||||
|
||||
@OptIn(ExperimentalStdlibApi::class)
|
||||
final override fun onEvent(event: Int, path: String?) {
|
||||
|
||||
val e = FileEvent.entries.find { it.value == event }
|
||||
|
||||
val fileEvent = e ?: FileEvent.UNKNOWN
|
||||
|
||||
Log.i("BaseObserver", "event code = ${event.toHexString()}, FileEvent is $fileEvent")
|
||||
|
||||
onEvent(fileEvent, path)
|
||||
}
|
||||
|
||||
private fun hasLastThreeEvents() = runCatching {
|
||||
eventQueue.size == 3 && eventQueue.poll() == OPEN && eventQueue.poll() == ACCESS && eventQueue.poll() == CLOSE_NOWRITE
|
||||
}.getOrNull() ?: false
|
||||
|
||||
abstract fun onEvent(event: FileEvent, path: String?)
|
||||
|
||||
protected fun File.list(regex: String = ".") =
|
||||
this.list { dir, _ -> Regex(regex).matches(dir.absolutePath) }?.asList() ?: listOf()
|
||||
|
||||
|
||||
protected infix fun File.copyToThe(destination: File){
|
||||
this.copyTo(destination, overwrite = true)
|
||||
}
|
||||
|
||||
protected fun zipAll(outputFolder: String, filesToCompress: List<String>) {
|
||||
if(filesToCompress.isEmpty()){
|
||||
Log.i("BaseObserver", "no files exit!")
|
||||
return
|
||||
}
|
||||
try {
|
||||
// Проверяем, существует ли выходная папка. Если нет, создаем её.
|
||||
val outputDir = File(outputFolder)
|
||||
if (!outputDir.exists()) outputDir.mkdirs()
|
||||
|
||||
// Генерируем имя ZIP-архива на основе времени
|
||||
val timestamp = System.currentTimeMillis()
|
||||
val zipFileName = "$outputFolder/saves_$timestamp.zip"
|
||||
val zipOutputStream = ZipOutputStream(FileOutputStream(zipFileName))
|
||||
|
||||
for (fileToCompress in filesToCompress) {
|
||||
// Проверяем, существует ли файл
|
||||
val inputFile = File(fileToCompress)
|
||||
if (inputFile.exists()) {
|
||||
// Создаем объект ZipEntry для каждого файла
|
||||
val zipEntry = ZipEntry(inputFile.name)
|
||||
zipOutputStream.putNextEntry(zipEntry)
|
||||
|
||||
// Читаем содержимое файла и записываем его в ZIP-архив
|
||||
val fileInputStream = FileInputStream(inputFile)
|
||||
val buffer = ByteArray(1024)
|
||||
var len: Int
|
||||
while (fileInputStream.read(buffer).also { len = it } > 0) {
|
||||
zipOutputStream.write(buffer, 0, len)
|
||||
}
|
||||
fileInputStream.close()
|
||||
|
||||
// Закрываем текущий ZipEntry
|
||||
zipOutputStream.closeEntry()
|
||||
} else {
|
||||
Log.i("SaveFileObserver", "Файл не существует: $fileToCompress")
|
||||
}
|
||||
}
|
||||
|
||||
// Закрываем ZipOutputStream
|
||||
zipOutputStream.close()
|
||||
Log.i("SaveFileObserver", "Архивация завершена. Архив создан: $zipFileName")
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package com.megboyzz.devmenu.util
|
||||
|
||||
import android.os.FileObserver
|
||||
|
||||
enum class FileEvent(val value: Int) {
|
||||
|
||||
ACCESS(FileObserver.ACCESS),
|
||||
MODIFY(FileObserver.MODIFY),
|
||||
ATTRIB(FileObserver.ATTRIB),
|
||||
CLOSE_WRITE(FileObserver.CLOSE_WRITE),
|
||||
CLOSE_NOWRITE(FileObserver.CLOSE_NOWRITE),
|
||||
OPEN(FileObserver.OPEN),
|
||||
MOVED_FROM(FileObserver.MOVED_FROM),
|
||||
MOVED_TO(FileObserver.MOVED_TO),
|
||||
CREATE(FileObserver.CREATE),
|
||||
DELETE(FileObserver.DELETE),
|
||||
DELETE_SELF(FileObserver.DELETE_SELF),
|
||||
MOVE_SELF(FileObserver.MOVE_SELF),
|
||||
UNKNOWN(-1);
|
||||
|
||||
override fun toString() = this.name
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
package com.megboyzz.devmenu.util
|
||||
|
||||
import android.util.Log
|
||||
import java.io.File
|
||||
import kotlin.concurrent.thread
|
||||
|
||||
|
||||
class SaveFileObserver(
|
||||
private val source: String,
|
||||
private val destinationFolder: String
|
||||
) : BaseObserver(source) {
|
||||
|
||||
private var countSnapshots: Int = 1
|
||||
|
||||
private val checkList = listOf(
|
||||
//FileEvent.CLOSE_WRITE,
|
||||
//FileEvent.MODIFY,
|
||||
//FileEvent.DELETE,
|
||||
//FileEvent.CREATE,
|
||||
//FileEvent.ACCESS,
|
||||
//FileEvent.OPEN,
|
||||
FileEvent.CLOSE_NOWRITE
|
||||
)
|
||||
init {
|
||||
val dest = File(destinationFolder)
|
||||
if(dest.isFile) dest.delete()
|
||||
if(!dest.exists()) dest.mkdir()
|
||||
val list = dest.list{
|
||||
dir, name -> name.endsWith(".sb")
|
||||
}
|
||||
list?.forEach { File(dest, it).delete() }
|
||||
File(source) copyToThe File(destinationFolder, "BASE.sb")
|
||||
}
|
||||
|
||||
override fun onEvent(event: FileEvent, path: String?) {
|
||||
Log.i("SaveFileObserver", "onEvent $event")
|
||||
if (event in checkList) {
|
||||
Log.d("SaveFileObserver", "File created or modified: $source")
|
||||
File(source) copyToThe File(destinationFolder, "$event-$countSnapshots.sb")
|
||||
countSnapshots++
|
||||
}
|
||||
}
|
||||
|
||||
override fun stopWatching() {
|
||||
super.stopWatching()
|
||||
val filesList = File(destinationFolder).list{ _, name -> name.endsWith(".sb") }?.map { it }
|
||||
if (filesList != null) {
|
||||
zipAll(destinationFolder, filesList)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user