@@ -6,6 +6,7 @@ import android.app.PendingIntent
66import android.app.Service
77import android.appwidget.AppWidgetManager
88import android.content.ComponentName
9+ import android.content.ContentResolver
910import android.content.Context
1011import android.content.Intent
1112import android.content.pm.ApplicationInfo
@@ -19,6 +20,7 @@ import android.os.Build
1920import android.os.Environment
2021import android.os.IBinder
2122import android.provider.DocumentsContract
23+ import android.system.Os
2224import android.util.Base64
2325import android.util.Log
2426import androidx.core.app.NotificationCompat
@@ -39,6 +41,7 @@ import org.androidlabs.applistbackup.settings.Settings
3941import org.androidlabs.applistbackup.utils.Utils.clearPrefixSlash
4042import org.androidlabs.applistbackup.utils.Utils.isTV
4143import java.io.ByteArrayOutputStream
44+ import java.io.File
4245import java.net.URLDecoder
4346import java.nio.charset.StandardCharsets
4447import java.text.DecimalFormat
@@ -63,7 +66,7 @@ class BackupService : Service() {
6366 const val SERVICE_CHANNEL_ID = " BackupService"
6467 const val BACKUP_CHANNEL_ID = " Backup"
6568
66- const val FILE_NAME_PREFIX = " app-list-backup- "
69+ const val FILE_NAME_PREFIX = " app-list-backup"
6770
6871 val isRunning = MutableStateFlow (false )
6972
@@ -140,7 +143,8 @@ class BackupService : Service() {
140143 )
141144 }
142145 }
143- return files?.map { BackupRawFile .fromFile(it, context) } ? : emptyList()
146+ return files?.map { BackupRawFile .fromFile(it, context) }
147+ ?.sortedByDescending { it.lastModified } ? : emptyList()
144148 }
145149 } else {
146150 val backupsDir = DocumentFile .fromTreeUri(context, backupsUri) ? : return emptyList()
@@ -155,6 +159,7 @@ class BackupService : Service() {
155159 } == true
156160 }
157161 return files.map { BackupRawFile .fromDocumentFile(it, context) }
162+ .sortedByDescending { it.lastModified }
158163 }
159164 }
160165
@@ -163,14 +168,9 @@ class BackupService : Service() {
163168
164169 fun getLastCreatedFileUri (context : Context ): Uri ? {
165170 val files = getRawBackupFiles(context)
166- if (files.isNotEmpty()) {
167- val sortedFiles = files.sortedByDescending { it.lastModified }
168-
169- val lastCreatedFile = sortedFiles.firstOrNull()
170-
171- if (lastCreatedFile != null ) {
172- return lastCreatedFile.uri
173- }
171+ val lastCreatedFile = files.firstOrNull()
172+ if (lastCreatedFile != null ) {
173+ return lastCreatedFile.uri
174174 }
175175 return null
176176 }
@@ -182,38 +182,48 @@ class BackupService : Service() {
182182 .map { file ->
183183 val name = file.name
184184 val dateString = name.removePrefix(FILE_NAME_PREFIX ).substringBeforeLast(' .' )
185- val date = dateFormat.parse(dateString) ? : Date ()
186- val title = getTitleFromUri(file.uri) ? : name
185+ val date = if (dateString.isNotEmpty()) {
186+ dateFormat.parse(dateString) ? : Date ()
187+ } else {
188+ val timestamp = getFileDate(context, file.uri)
189+ Date (timestamp)
190+ }
191+ val title = getTitleFromUri(context, file.uri) ? : name
187192 BackupFile (file.uri, date, title)
188193 }
189- .sortedByDescending { it.date }
190194 }
191195
192- fun getTitleFromUri (uri : Uri ): String? {
196+ fun getTitleFromUri (context : Context , uri : Uri ): String? {
193197 val pattern =
194- Pattern .compile(" $FILE_NAME_PREFIX (\\ d{4}-\\ d{2}-\\ d{2}-\\ d{2}-\\ d{2}-\\ d{2})\\ .(\\ w+)" )
198+ Pattern .compile(" $FILE_NAME_PREFIX - (\\ d{4}-\\ d{2}-\\ d{2}-\\ d{2}-\\ d{2}-\\ d{2})\\ .(\\ w+)" )
195199 val matcher = pattern.matcher(uri.toString())
196200
197- return if (matcher.find()) {
201+ val titleFormatter = SimpleDateFormat (" MMM dd, yyyy HH:mm" , Locale .getDefault())
202+
203+ val isFind = matcher.find()
204+ if (! isFind) {
205+ val timestamp = getFileDate(context, uri)
206+ val date = titleFormatter.format(Date (timestamp))
207+ val extension = uri.toString().split(" ." ).last()
208+ val format = extension.let { BackupFormat .fromExtension(it) }
209+ return " $date (${format.value} )"
210+ } else {
198211 val dateString = matcher.group(1 )
199212 val extension = matcher.group(2 )
200213
201214 val dateFormat = SimpleDateFormat (" yyyy-MM-dd-HH-mm-ss" , Locale .getDefault())
202- val titleFormatter = SimpleDateFormat (" MMM dd, yyyy HH:mm" , Locale .getDefault())
203215
204216 val date = dateString?.let { dateFormat.parse(it) }
205217 if (date != null ) {
206218 if (extension != null ) {
207219 val format = extension.let { BackupFormat .fromExtension(it) }
208- " ${titleFormatter.format(date)} (${format.value} )"
220+ return " ${titleFormatter.format(date)} (${format.value} )"
209221 } else {
210- titleFormatter.format(date)
222+ return titleFormatter.format(date)
211223 }
212224 } else {
213- null
225+ return null
214226 }
215- } else {
216- null
217227 }
218228 }
219229
@@ -228,6 +238,48 @@ class BackupService : Service() {
228238 val broadcastIntent = Intent (" org.androidlabs.applistbackup.BACKUP_ACTION" )
229239 context.sendBroadcast(broadcastIntent)
230240 }
241+
242+ private fun getFileDate (context : Context , uri : Uri ): Long {
243+ try {
244+ when (uri.scheme) {
245+ ContentResolver .SCHEME_CONTENT -> {
246+ context.contentResolver.query(
247+ uri,
248+ arrayOf(DocumentsContract .Document .COLUMN_LAST_MODIFIED ),
249+ null ,
250+ null ,
251+ null
252+ )?.use { cursor ->
253+ if (cursor.moveToFirst()) {
254+ val lastModifiedIndex = cursor.getColumnIndex(
255+ DocumentsContract .Document .COLUMN_LAST_MODIFIED
256+ )
257+ if (lastModifiedIndex != - 1 ) {
258+ return cursor.getLong(lastModifiedIndex)
259+ }
260+ }
261+ }
262+
263+ val fileDescriptor = context.contentResolver.openFileDescriptor(uri, " r" )
264+ fileDescriptor?.use {
265+ val stat = Os .fstat(it.fileDescriptor)
266+ return stat.st_mtime * 1000L
267+ }
268+ }
269+
270+ ContentResolver .SCHEME_FILE -> {
271+ val file = File (uri.path ? : " " )
272+ if (file.exists()) {
273+ return file.lastModified()
274+ }
275+ }
276+ }
277+ } catch (e: Exception ) {
278+ Log .e(" BackupService" , " Error getting file date: ${e.message} " )
279+ }
280+
281+ return System .currentTimeMillis()
282+ }
231283 }
232284
233285 override fun onBind (intent : Intent ? ): IBinder ? {
@@ -301,6 +353,7 @@ class BackupService : Service() {
301353 val excludeItems = Settings .getBackupExcludeData(this )
302354 val currentDate = Date ()
303355 val currentTime = dateFormat.format(currentDate)
356+
304357 val type =
305358 getString(if (source != null && source == " tasker" ) R .string.automatic else R .string.manual)
306359
@@ -387,7 +440,7 @@ class BackupService : Service() {
387440
388441 formats.forEach { format ->
389442 try {
390- val fileName = " $FILE_NAME_PREFIX$currentTime .${format.fileExtension()} "
443+ val fileName = " $FILE_NAME_PREFIX - $currentTime .${format.fileExtension()} "
391444 val newFile = backupsDir.createFile(format.mimeType(), fileName)
392445
393446 when (format) {
@@ -730,7 +783,40 @@ class BackupService : Service() {
730783 val manager = getSystemService(NotificationManager ::class .java)
731784 manager.notify(getNotificationId(), endNotification)
732785 } else {
786+ val backupLimit = Settings .getBackupLimit(this )
787+ val isVersioningDisabled = backupLimit == 1
788+
789+ if (isVersioningDisabled) {
790+ successfulResults.forEach {
791+ val newFileName =
792+ " $FILE_NAME_PREFIX .${it.format.fileExtension()} "
793+ it.file?.renameTo(newFileName)?.let { newFile ->
794+ it.file = newFile
795+ }
796+ }
797+ }
798+
733799 val firstUri = successfulResults.first().file?.uri
800+
801+ if (backupLimit > 0 ) {
802+ val usedFormats = successfulResults.map { it.format }.distinct()
803+ val backups = getRawBackupFiles(this )
804+
805+ usedFormats.forEach { format ->
806+ val backupsByFormat = backups.filter {
807+ it.name.endsWith(format.fileExtension())
808+ }
809+ val currentCount = backupsByFormat.count()
810+ if (currentCount > backupLimit) {
811+ val filesToDeleteCount = currentCount - backupLimit
812+ val sortedBackups = backupsByFormat.sortedBy { it.lastModified }
813+ sortedBackups.take(filesToDeleteCount).forEach { backupFile ->
814+ backupFile.delete()
815+ }
816+ }
817+ }
818+ }
819+
734820 val mainActivityIntent = Intent (this , MainActivity ::class .java).apply {
735821 putExtra(" uri" , firstUri.toString())
736822 addFlags(Intent .FLAG_GRANT_READ_URI_PERMISSION )
0 commit comments