1package eu.siacs.conversations.utils;
2
3import android.content.Context;
4import android.net.Uri;
5import android.util.Log;
6import com.google.common.collect.Collections2;
7import com.google.common.collect.ComparisonChain;
8import com.google.common.collect.ImmutableList;
9import com.google.common.collect.ImmutableSet;
10import com.google.common.collect.Ordering;
11import com.google.common.util.concurrent.Futures;
12import com.google.common.util.concurrent.ListenableFuture;
13import eu.siacs.conversations.Config;
14import eu.siacs.conversations.R;
15import eu.siacs.conversations.persistance.DatabaseBackend;
16import eu.siacs.conversations.persistance.FileBackend;
17import eu.siacs.conversations.services.QuickConversationsService;
18import eu.siacs.conversations.xmpp.Jid;
19import java.io.DataInputStream;
20import java.io.File;
21import java.io.FileInputStream;
22import java.io.FileNotFoundException;
23import java.io.IOException;
24import java.io.InputStream;
25import java.util.ArrayList;
26import java.util.List;
27import java.util.concurrent.ExecutorService;
28import java.util.concurrent.Executors;
29
30public class BackupFile implements Comparable<BackupFile> {
31
32 private static final ExecutorService BACKUP_FILE_READER_EXECUTOR =
33 Executors.newSingleThreadExecutor();
34
35 private final Uri uri;
36 private final BackupFileHeader header;
37
38 private BackupFile(Uri uri, BackupFileHeader header) {
39 this.uri = uri;
40 this.header = header;
41 }
42
43 public static ListenableFuture<BackupFile> readAsync(final Context context, final Uri uri) {
44 return Futures.submit(() -> read(context, uri), BACKUP_FILE_READER_EXECUTOR);
45 }
46
47 private static BackupFile read(final File file) throws IOException {
48 final FileInputStream fileInputStream = new FileInputStream(file);
49 final DataInputStream dataInputStream = new DataInputStream(fileInputStream);
50 BackupFileHeader backupFileHeader = BackupFileHeader.read(dataInputStream);
51 fileInputStream.close();
52 return new BackupFile(Uri.fromFile(file), backupFileHeader);
53 }
54
55 public static BackupFile read(final Context context, final Uri uri) throws IOException {
56 final InputStream inputStream = context.getContentResolver().openInputStream(uri);
57 if (inputStream == null) {
58 throw new FileNotFoundException();
59 }
60 final DataInputStream dataInputStream = new DataInputStream(inputStream);
61 final BackupFileHeader backupFileHeader = BackupFileHeader.read(dataInputStream);
62 inputStream.close();
63 return new BackupFile(uri, backupFileHeader);
64 }
65
66 public BackupFileHeader getHeader() {
67 return header;
68 }
69
70 public Uri getUri() {
71 return uri;
72 }
73
74 public static ListenableFuture<List<BackupFile>> listAsync(final Context context) {
75 return Futures.submit(() -> list(context), BACKUP_FILE_READER_EXECUTOR);
76 }
77
78 private static List<BackupFile> list(final Context context) {
79 final var database = DatabaseBackend.getInstance(context);
80 final List<Jid> accounts = database.getAccountJids(false);
81 final var backupFiles = new ImmutableList.Builder<BackupFile>();
82 final var apps =
83 ImmutableSet.of("Conversations", "Quicksy", context.getString(R.string.app_name));
84 final List<File> directories = new ArrayList<>();
85 for (final String app : apps) {
86 directories.add(FileBackend.getLegacyBackupDirectory(app));
87 }
88 directories.add(FileBackend.getBackupDirectory(context));
89 for (final File directory : directories) {
90 if (!directory.exists() || !directory.isDirectory()) {
91 Log.d(Config.LOGTAG, "directory not found: " + directory.getAbsolutePath());
92 continue;
93 }
94 final File[] files = directory.listFiles();
95 if (files == null) {
96 continue;
97 }
98 Log.d(Config.LOGTAG, "looking for backups in " + directory);
99 for (final File file : files) {
100 if (file.isFile() && file.getName().endsWith(".ceb")) {
101 try {
102 final BackupFile backupFile = BackupFile.read(file);
103 if (accounts.contains(backupFile.getHeader().getJid())) {
104 Log.d(
105 Config.LOGTAG,
106 "skipping backup for " + backupFile.getHeader().getJid());
107 } else {
108 backupFiles.add(backupFile);
109 }
110 } catch (final IOException
111 | IllegalArgumentException
112 | BackupFileHeader.OutdatedBackupFileVersion e) {
113 Log.d(Config.LOGTAG, "unable to read backup file ", e);
114 }
115 }
116 }
117 }
118 final var list = backupFiles.build();
119 if (QuickConversationsService.isQuicksy()) {
120 return Ordering.natural()
121 .immutableSortedCopy(
122 Collections2.filter(
123 list,
124 b ->
125 b.header
126 .getJid()
127 .getDomain()
128 .equals(Config.QUICKSY_DOMAIN)));
129 }
130 return Ordering.natural().immutableSortedCopy(backupFiles.build());
131 }
132
133 @Override
134 public int compareTo(final BackupFile o) {
135 return ComparisonChain.start()
136 .compare(header.getJid(), o.header.getJid())
137 .compare(header.getTimestamp(), o.header.getTimestamp())
138 .result();
139 }
140}