Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 0 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,6 @@ This is the privacy friendly backup application that enables users to create and
[<img src="https://fdroid.gitlab.io/artwork/badge/get-it-on.png"
alt="Get it on F-Droid"
height="80">](https://f-droid.org/packages/org.secuso.privacyfriendlybackup/)
[<img src="https://play.google.com/intl/en_us/badges/images/generic/en-play-badge.png"
alt="Get it on Google Play"
height="80">](https://play.google.com/store/apps/details?id=org.secuso.privacyfriendlybackup)

## Integrated PFAs
These apps support backups with the Privacy Friendly Backup app.
Expand Down
3 changes: 2 additions & 1 deletion app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,7 @@ dependencies {
implementation 'androidx.recyclerview:recyclerview:1.3.2'
implementation 'com.google.android.material:material:1.12.0'
implementation 'androidx.constraintlayout:constraintlayout:2.2.0'
implementation 'androidx.annotation:annotation:1.9.1'

testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test.ext:junit:1.2.1'
Expand All @@ -123,7 +124,7 @@ dependencies {
testImplementation "androidx.work:work-testing:$work_version"

// Room Database
def roomVersion = "2.6.1"
def roomVersion = "2.7.0"
implementation "androidx.room:room-runtime:$roomVersion"
annotationProcessor "androidx.room:room-compiler:$roomVersion"
ksp "androidx.room:room-compiler:$roomVersion"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,19 @@ import android.app.NotificationChannel
import android.app.NotificationManager
import android.os.Build
import android.util.Log
import androidx.core.content.edit
import androidx.lifecycle.ProcessLifecycleOwner
import androidx.lifecycle.lifecycleScope
import androidx.preference.PreferenceManager
import androidx.work.BackoffPolicy
import androidx.work.Configuration
import androidx.work.ExistingPeriodicWorkPolicy
import androidx.work.PeriodicWorkRequestBuilder
import androidx.work.WorkManager
import androidx.work.WorkRequest
import kotlinx.coroutines.launch
import org.secuso.privacyfriendlybackup.data.room.BackupDatabase
import org.secuso.privacyfriendlybackup.data.room.model.enums.StorageType
import org.secuso.privacyfriendlybackup.worker.BackupJobManagerWorker
import java.util.concurrent.TimeUnit

Expand All @@ -25,6 +32,28 @@ class BackupApplication : Application(), Configuration.Provider {
override fun onCreate() {
super.onCreate()

if (applicationContext != null) {
with(ProcessLifecycleOwner.get()) {
lifecycleScope.launch {
PreferenceManager.getDefaultSharedPreferences(applicationContext).apply{
val current = getString("pref_storage_type", null)
if (current == null) {
val total = BackupDatabase.getInstance(applicationContext)
.backupMetaDataDao()
.getTotal()
edit(commit = true) {
putString("pref_storage_type", if (total > 0) {
StorageType.EXTERNAL.toString()
} else {
StorageType.INTERNAL.toString()
})
}
}
}
}
}
}

createNotificationChannel()
schedulePeriodicWork()
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,13 @@ import android.content.Context
import androidx.lifecycle.LiveData
import androidx.lifecycle.MediatorLiveData
import androidx.lifecycle.MutableLiveData
import androidx.room.ColumnInfo
import androidx.room.PrimaryKey
import kotlinx.coroutines.Dispatchers.IO
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withContext
import org.secuso.privacyfriendlybackup.data.room.BackupDatabase
import org.secuso.privacyfriendlybackup.data.cloud.WebserviceProvider
import org.secuso.privacyfriendlybackup.data.external.ExternalBackupDataStoreHelper
import org.secuso.privacyfriendlybackup.data.room.model.BackupJob
import org.secuso.privacyfriendlybackup.data.room.model.enums.BackupJobAction
import org.secuso.privacyfriendlybackup.data.internal.InternalBackupDataStoreHelper
import org.secuso.privacyfriendlybackup.data.room.model.enums.StorageType
import java.util.*

Expand Down Expand Up @@ -56,6 +51,7 @@ class BackupDataStorageRepository private constructor(
suspend fun storeFile(context: Context, backupData: BackupData) : Pair<Boolean, Long> {
return when(backupData.storageType) {
StorageType.EXTERNAL -> ExternalBackupDataStoreHelper.storeData(context, backupData)
StorageType.INTERNAL -> InternalBackupDataStoreHelper.storeData(context, backupData)
else -> { false to 0 }
//StorageType.CLOUD -> webserviceProvider.storeData(context, packageName, dataId)
}
Expand All @@ -64,6 +60,7 @@ class BackupDataStorageRepository private constructor(
suspend fun storeFile(context: Context, packageName: String, dataId : Long, storageType: StorageType = StorageType.EXTERNAL) {
when(storageType) {
StorageType.EXTERNAL -> ExternalBackupDataStoreHelper.storeData(context, packageName, dataId)
StorageType.INTERNAL -> InternalBackupDataStoreHelper.storeData(context, packageName, dataId)
StorageType.CLOUD -> webserviceProvider.storeData(context, packageName, dataId)
}
}
Expand All @@ -83,6 +80,9 @@ class BackupDataStorageRepository private constructor(
StorageType.EXTERNAL -> {
data.postValue(ExternalBackupDataStoreHelper.getData(context,metadata))
}
StorageType.INTERNAL -> {
data.postValue(InternalBackupDataStoreHelper.getData(context,metadata))
}
StorageType.CLOUD -> {
data.postValue( TODO() )
}
Expand All @@ -99,6 +99,7 @@ class BackupDataStorageRepository private constructor(

return when (metadata.storageService) {
StorageType.EXTERNAL -> ExternalBackupDataStoreHelper.getData(context, metadata)
StorageType.INTERNAL -> InternalBackupDataStoreHelper.getData(context, metadata)
StorageType.CLOUD -> webserviceProvider.getData(context, metadata)
}
}
Expand All @@ -112,6 +113,7 @@ class BackupDataStorageRepository private constructor(

runBlocking {
val externalFilenames = ExternalBackupDataStoreHelper.listAvailableData(context)
val internalFilenames = InternalBackupDataStoreHelper.listAvailableData(context)
for(meta in metalist) {
when(meta.storageService) {
StorageType.EXTERNAL -> {
Expand All @@ -128,6 +130,20 @@ class BackupDataStorageRepository private constructor(
)
result.add(backupDataInfo)
}
StorageType.INTERNAL -> {
val available = internalFilenames.contains(meta.filename)
val backupDataInfo = BackupData(
id = meta._id,
filename = meta.filename,
packageName = meta.packageName,
timestamp = meta.timestamp,
data = null,
encrypted = meta.encrypted,
storageType = meta.storageService,
available = available
)
result.add(backupDataInfo)
}
StorageType.CLOUD -> {
TODO()
}
Expand All @@ -149,6 +165,9 @@ class BackupDataStorageRepository private constructor(
StorageType.EXTERNAL -> {
ExternalBackupDataStoreHelper.deleteData(context, metaData)
}
StorageType.INTERNAL -> {
InternalBackupDataStoreHelper.deleteData(context, metaData)
}
StorageType.CLOUD -> {
webserviceProvider.deleteData(context, metaData)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ package org.secuso.privacyfriendlybackup.data.cloud
import android.content.Context
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import org.secuso.privacyfriendlybackup.api.util.copyInputStreamToFile
import org.secuso.privacyfriendlybackup.api.util.hash
import org.secuso.privacyfriendlybackup.api.util.toHex
import org.secuso.privacyfriendlybackup.data.BackupDataStorageRepository
Expand All @@ -13,8 +12,6 @@ import org.secuso.privacyfriendlybackup.data.internal.InternalBackupDataStoreHel
import org.secuso.privacyfriendlybackup.data.room.BackupDatabase
import org.secuso.privacyfriendlybackup.data.room.model.StoredBackupMetaData
import org.secuso.privacyfriendlybackup.data.room.model.enums.StorageType
import org.secuso.privacyfriendlybackup.util.BackupDataUtil
import java.io.ByteArrayOutputStream
import java.io.File
import java.util.*

Expand Down Expand Up @@ -74,7 +71,7 @@ class WebserviceProvider {
return withContext(Dispatchers.IO) {
val files = File(context.getExternalFilesDir(null),
ExternalBackupDataStoreHelper.BACKUP_DIR
).listFiles { _, name -> name.toLowerCase(Locale.ENGLISH).endsWith(".backup") }
).listFiles { _, name -> name.lowercase(Locale.ENGLISH).endsWith(".backup") }
files?.map { it.name } ?: emptyList()
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ object ExternalBackupDataStoreHelper {

suspend fun listAvailableData(context: Context) : List<String> {
return withContext(Dispatchers.IO) {
val files = File(context.getExternalFilesDir(null), BACKUP_DIR).listFiles { _, name -> name.toLowerCase(Locale.ENGLISH).endsWith(".backup") }
val files = File(context.getExternalFilesDir(null), BACKUP_DIR).listFiles { _, name -> name.lowercase(Locale.ENGLISH).endsWith(".backup") }
files?.map { it.name } ?: emptyList()
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,20 @@
package org.secuso.privacyfriendlybackup.data.internal

import android.content.Context
import android.text.TextUtils
import android.util.Log
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import org.secuso.privacyfriendlybackup.api.util.copyInputStreamToFile
import org.secuso.privacyfriendlybackup.api.util.hash
import org.secuso.privacyfriendlybackup.api.util.toHex
import org.secuso.privacyfriendlybackup.data.BackupDataStorageRepository
import org.secuso.privacyfriendlybackup.data.room.BackupDatabase
import org.secuso.privacyfriendlybackup.data.room.model.enums.BackupJobAction
import org.secuso.privacyfriendlybackup.data.room.model.InternalBackupData
import org.secuso.privacyfriendlybackup.data.room.model.StoredBackupMetaData
import org.secuso.privacyfriendlybackup.data.room.model.enums.StorageType
import org.secuso.privacyfriendlybackup.util.BackupDataUtil.getFileName
import java.io.ByteArrayInputStream
import java.io.File
import java.io.IOException
import java.io.InputStream
Expand All @@ -17,6 +24,7 @@ object InternalBackupDataStoreHelper {
const val TAG = "PFA Internal"

const val BACKUP_DIR = "tempData"
const val INTERNAL_BACKUP_DIR = "backupData"

suspend fun storeBackupData(context: Context, packageName: String, inputStream: InputStream, date: Date, encrypted: Boolean = false) : Long {
val dataId = storeData(context, packageName, inputStream, date, encrypted)
Expand All @@ -42,6 +50,63 @@ object InternalBackupDataStoreHelper {
return dataId
}

suspend fun storeData(context: Context, packageName: String, dataId: Long) : Long {
return withContext(Dispatchers.IO) {
val path = File(context.filesDir, INTERNAL_BACKUP_DIR)
path.mkdirs()

val date = Date()

val (inputStream, data) = getInternalData(context, dataId)
val fileName = getFileName(date, packageName, data?.encrypted == true)
val file = File(path, fileName)

Log.d(TAG, file.toString())

val dataBytes = inputStream?.use {
return@use inputStream.readBytes()
}
file.copyInputStreamToFile(ByteArrayInputStream(dataBytes))

if (data != null) {
val hash = dataBytes!!.hash("SHA-1").toHex()
BackupDatabase.getInstance(context).backupMetaDataDao().insert(
StoredBackupMetaData(
packageName = data.packageName,
timestamp = date,
storageService = StorageType.INTERNAL,
filename = fileName,
encrypted = data.encrypted,
hash = hash
)
)
} else {
-1L
}
}
}

suspend fun storeData(context: Context, data: BackupDataStorageRepository.BackupData) : Pair<Boolean, Long> {
return withContext(Dispatchers.IO) {
val path = File(context.filesDir, INTERNAL_BACKUP_DIR)
path.mkdirs()
val file = File(path, data.filename)

file.copyInputStreamToFile(ByteArrayInputStream(data.data))
val hash = data.data!!.hash("SHA-1").toHex()

val id = BackupDatabase.getInstance(context).backupMetaDataDao().insert(StoredBackupMetaData(
packageName = data.packageName,
timestamp = data.timestamp,
storageService = StorageType.INTERNAL,
filename = data.filename,
encrypted = data.encrypted,
hash = hash
))
return@withContext true to id
}
}

suspend fun storeData(context: Context, packageName: String, inputStream: InputStream, date: Date, encrypted : Boolean = false) : Long {
val path = File(context.filesDir, BACKUP_DIR)
path.mkdirs()
Expand All @@ -61,10 +126,41 @@ object InternalBackupDataStoreHelper {
return BackupDatabase.getInstance(context).internalBackupDataDao().insert(data)
}

suspend fun getData(context: Context, metadata : StoredBackupMetaData) : BackupDataStorageRepository.BackupData? {
return withContext(Dispatchers.IO) {
val path = File(context.filesDir, INTERNAL_BACKUP_DIR)
val file = File(path, metadata.filename)

return@withContext BackupDataStorageRepository.BackupData(
metadata._id,
metadata.filename,
metadata.packageName,
metadata.timestamp,
file.inputStream().readBytes(),
metadata.encrypted,
StorageType.INTERNAL,
true
)
}
}

suspend fun getInternalData(context: Context, dataId: Long): Pair<InputStream?, InternalBackupData?> {
val data = BackupDatabase.getInstance(context).internalBackupDataDao().getById(dataId)
?: return Pair(null, null)

// if(data.packageName != callingPackageName && data.uid == callingUid) {
// Log.d(TAG, "[No Restore Data found.]")
// return null
// }

val path = File(context.filesDir, BACKUP_DIR)
return File(path, data.file).inputStream() to data
}

suspend fun getInternalStoredData(context: Context, dataId: Long): Pair<InputStream?, InternalBackupData?> {
val data = BackupDatabase.getInstance(context).internalBackupDataDao().getById(dataId)
?: return Pair(null, null)

// if(data.packageName != callingPackageName && data.uid == callingUid) {
// Log.d(TAG, "[No Restore Data found.]")
// return null
Expand Down Expand Up @@ -117,4 +213,21 @@ object InternalBackupDataStoreHelper {
}
}

suspend fun deleteData(context: Context, metadata : StoredBackupMetaData) {
withContext(Dispatchers.IO) {
val path = File(context.filesDir, INTERNAL_BACKUP_DIR)
val file = File(path, metadata.filename)
file.delete()
}
}

suspend fun listAvailableData(context: Context) : List<String> {
return withContext(Dispatchers.IO) {
val files = File(context.filesDir, INTERNAL_BACKUP_DIR).listFiles { _, name ->
name.lowercase(Locale.ENGLISH).endsWith(".backup")
}
files?.map { it.name } ?: emptyList()
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package org.secuso.privacyfriendlybackup.data.room.dao
import androidx.lifecycle.LiveData
import androidx.room.*
import org.secuso.privacyfriendlybackup.data.room.model.StoredBackupMetaData
import org.secuso.privacyfriendlybackup.data.room.model.enums.StorageType

@Dao
interface BackupMetaDataDao {
Expand Down Expand Up @@ -32,4 +33,10 @@ interface BackupMetaDataDao {

@Query("DELETE FROM StoredBackupMetaData WHERE _id IN (:ids)")
suspend fun deleteForIds(ids: List<Long>)

@Query("SELECT * FROM StoredBackupMetaData WHERE storageService != :storageType")
suspend fun getAllOfOtherStorageType(storageType: StorageType): List<StoredBackupMetaData>

@Query("SELECT COUNT(*) FROM StoredBackupMetaData")
suspend fun getTotal(): Long
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,11 @@ enum class StorageType(
@StringRes val nameResId : Int
) {
EXTERNAL(R.string.storage_type_external),
INTERNAL(R.string.storage_type_internal),
CLOUD(R.string.storage_type_drive);

companion object {
@JvmStatic
fun getStorageOptions() = listOf(EXTERNAL)
fun getStorageOptions() = listOf(EXTERNAL, INTERNAL)
}
}
Loading
Loading