aboutsummaryrefslogtreecommitdiffstats
path: root/src/main/java/de/pixart/messenger/services/ExportLogsService.java
blob: e66873acc8fb834c09d8d37c849a38969d82b6e7 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
package de.pixart.messenger.services;

import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.os.IBinder;
import android.os.PowerManager;
import android.os.PowerManager.WakeLock;
import android.preference.PreferenceManager;
import android.support.annotation.BoolRes;
import android.util.Log;

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;

import javax.crypto.NoSuchPaddingException;

import de.pixart.messenger.Config;
import de.pixart.messenger.R;
import de.pixart.messenger.entities.Account;
import de.pixart.messenger.entities.Conversation;
import de.pixart.messenger.entities.Message;
import de.pixart.messenger.persistance.DatabaseBackend;
import de.pixart.messenger.persistance.FileBackend;
import de.pixart.messenger.utils.EncryptDecryptFile;
import de.pixart.messenger.utils.WakeLockHelper;
import rocks.xmpp.addr.Jid;

import static de.pixart.messenger.ui.SettingsActivity.USE_MULTI_ACCOUNTS;

public class ExportLogsService extends XmppConnectionService {

    private static final SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
    private static final String DIRECTORY_STRING_FORMAT = FileBackend.getAppLogsDirectory() + "%s";
    private static final String MESSAGE_STRING_FORMAT = "(%s) %s: %s\n";
    private static AtomicBoolean running = new AtomicBoolean(false);
    boolean ReadableLogsEnabled = false;
    private DatabaseBackend mDatabaseBackend;
    private List<Account> mAccounts;
    private WakeLock wakeLock;
    private PowerManager pm;

    @Override
    public void onCreate() {
        mDatabaseBackend = DatabaseBackend.getInstance(getBaseContext());
        mAccounts = mDatabaseBackend.getAccounts();
        final SharedPreferences ReadableLogs = PreferenceManager.getDefaultSharedPreferences(this);
        ReadableLogsEnabled = ReadableLogs.getBoolean("export_plain_text_logs", getResources().getBoolean(R.bool.plain_text_logs));
        pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
        wakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "ExportLogsService");
        this.startForeground(NotificationService.FOREGROUND_NOTIFICATION_ID, getNotificationService().exportLogsNotification());
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        if (running.compareAndSet(false, true)) {
            new Thread(() -> {
                startForcingForegroundNotification();
                export();
                stopForcingForegroundNotification();
                WakeLockHelper.release(wakeLock);
                running.set(false);
                stopSelf();
            }).start();
        }
        return START_NOT_STICKY;
    }

    private void export() {
        wakeLock.acquire();
        getNotificationService().exportLogsServiceNotification(getNotificationService().exportLogsNotification());
        List<Conversation> conversations = mDatabaseBackend.getConversations(Conversation.STATUS_AVAILABLE);
        conversations.addAll(mDatabaseBackend.getConversations(Conversation.STATUS_ARCHIVED));
        if (mAccounts.size() >= 1) {
            if (ReadableLogsEnabled) {
                for (Conversation conversation : conversations) {
                    writeToFile(conversation);
                }
            }
            try {
                ExportDatabase();
            } catch (IOException e) {
                e.printStackTrace();
            }
        } else {
            Log.d(Config.LOGTAG, "ExportLogsService: no accounts, aborting export");
        }
    }

    private void writeToFile(Conversation conversation) {
        Jid accountJid = resolveAccountUuid(conversation.getAccountUuid());
        Jid contactJid = conversation.getJid();

        File dir = new File(String.format(DIRECTORY_STRING_FORMAT, accountJid.asBareJid().toString()));
        dir.mkdirs();

        BufferedWriter bw = null;
        try {
            for (Message message : mDatabaseBackend.getMessagesIterable(conversation)) {
                if (message == null)
                    continue;
                if (message.getType() == Message.TYPE_TEXT || message.hasFileOnRemoteHost()) {
                    String date = simpleDateFormat.format(new Date(message.getTimeSent()));
                    if (bw == null) {
                        bw = new BufferedWriter(new FileWriter(
                                new File(dir, contactJid.asBareJid().toString() + ".txt")));
                    }
                    String jid = null;
                    switch (message.getStatus()) {
                        case Message.STATUS_RECEIVED:
                            jid = getMessageCounterpart(message);
                            break;
                        case Message.STATUS_SEND:
                        case Message.STATUS_SEND_RECEIVED:
                        case Message.STATUS_SEND_DISPLAYED:
                            jid = accountJid.asBareJid().toString();
                            break;
                    }
                    if (jid != null) {
                        String body = message.hasFileOnRemoteHost() ? message.getFileParams().url.toString() : message.getBody();
                        bw.write(String.format(MESSAGE_STRING_FORMAT, date, jid,
                                body.replace("\\\n", "\\ \n").replace("\n", "\\ \n")));
                    }
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                if (bw != null) {
                    bw.close();
                }
            } catch (IOException e1) {
                e1.printStackTrace();
            }
        }
    }

    private Jid resolveAccountUuid(String accountUuid) {
        for (Account account : mAccounts) {
            if (account.getUuid().equals(accountUuid)) {
                return account.getJid();
            }
        }
        return null;
    }

    private String getMessageCounterpart(Message message) {
        String trueCounterpart = (String) message.getContentValues().get(Message.TRUE_COUNTERPART);
        if (trueCounterpart != null) {
            return trueCounterpart;
        } else {
            return message.getCounterpart().toString();
        }
    }

    public void ExportDatabase() throws IOException {
        Log.d(Config.LOGTAG, "ExportLogsService: start creating backup");
        Account mAccount = mAccounts.get(0);
        String EncryptionKey = null;
        // Get hold of the db:
        FileInputStream InputFile = new FileInputStream(this.getDatabasePath(DatabaseBackend.DATABASE_NAME));
        // Set the output folder on the SDcard
        File directory = new File(FileBackend.getBackupDirectory());
        // Create the folder if it doesn't exist:
        if (!directory.exists()) {
            boolean directory_created = directory.mkdirs();
            Log.d(Config.LOGTAG, "ExportLogsService: backup directory created " + directory_created);
        }
        //Delete old database export file
        File temp_db_file = new File(directory + "/database.bak");
        if (temp_db_file.exists()) {
            Log.d(Config.LOGTAG, "ExportLogsService: Delete temp database backup file from " + temp_db_file.toString());
            boolean temp_db_file_deleted = temp_db_file.delete();
            Log.d(Config.LOGTAG, "ExportLogsService: old backup file deleted " + temp_db_file_deleted);
        }
        // Set the output file stream up:
        FileOutputStream OutputFile = new FileOutputStream(directory.getPath() + "/database.db.crypt");

        if (mAccounts.size() == 1 && !multipleAccounts()) {
            EncryptionKey = mAccount.getPassword(); //get account password
        } else {
            SharedPreferences multiaccount_prefs = getApplicationContext().getSharedPreferences(USE_MULTI_ACCOUNTS, Context.MODE_PRIVATE);
            EncryptionKey = multiaccount_prefs.getString("BackupPW", null);
        }
        if (EncryptionKey == null) {
            Log.d(Config.LOGTAG, "ExportLogsService: Database exporter: failed to write encryted backup to sdcard because of missing password");
            return;
        }

        // encrypt database from the input file to the output file
        try {
            EncryptDecryptFile.encrypt(InputFile, OutputFile, EncryptionKey);
            Log.d(Config.LOGTAG, "ExportLogsService: starting encrypted output to " + OutputFile.toString());
        } catch (NoSuchAlgorithmException | NoSuchPaddingException e) {
            Log.d(Config.LOGTAG, "ExportLogsService: Database exporter: encryption failed with " + e);
            e.printStackTrace();
        } catch (InvalidKeyException e) {
            Log.d(Config.LOGTAG, "ExportLogsService: Database exporter: encryption failed (invalid key) with " + e);
            e.printStackTrace();
        } catch (IOException e) {
            Log.d(Config.LOGTAG, "ExportLogsService: Database exporter: encryption failed (IO) with " + e);
            e.printStackTrace();
        } finally {
            Log.d(Config.LOGTAG, "ExportLogsService: backup job finished");
        }
    }

    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }

    public SharedPreferences getPreferences() {
        return PreferenceManager.getDefaultSharedPreferences(getApplicationContext());
    }

    public boolean getBooleanPreference(String name, @BoolRes int res) {
        return getPreferences().getBoolean(name, getResources().getBoolean(res));
    }

    public boolean multipleAccounts() {
        return getBooleanPreference("enable_multi_accounts", R.bool.enable_multi_accounts);
    }
}