aboutsummaryrefslogtreecommitdiffstatshomepage
path: root/ui/src/main/java/com/wireguard/android/preference/ZipExporterPreference.kt
blob: db1b773514645accf814f5482ac23394e08403d2 (plain) (blame)
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
/*
 * Copyright © 2017-2019 WireGuard LLC. All Rights Reserved.
 * SPDX-License-Identifier: Apache-2.0
 */
package com.wireguard.android.preference

import android.Manifest
import android.content.Context
import android.content.pm.PackageManager
import android.util.AttributeSet
import android.util.Log
import androidx.preference.Preference
import com.google.android.material.snackbar.Snackbar
import com.wireguard.android.Application
import com.wireguard.android.R
import com.wireguard.android.activity.SettingsActivity
import com.wireguard.android.model.ObservableTunnel
import com.wireguard.android.util.DownloadsFileSaver
import com.wireguard.android.util.ErrorMessages
import com.wireguard.android.util.FragmentUtils
import com.wireguard.android.util.ObservableSortedKeyedList
import com.wireguard.config.Config
import java9.util.concurrent.CompletableFuture
import java.nio.charset.StandardCharsets
import java.util.ArrayList
import java.util.zip.ZipEntry
import java.util.zip.ZipOutputStream

/**
 * Preference implementing a button that asynchronously exports config zips.
 */
class ZipExporterPreference(context: Context, attrs: AttributeSet?) : Preference(context, attrs) {
    private var exportedFilePath: String? = null

    private fun exportZip() {
        Application.getTunnelManager().tunnels.thenAccept(this::exportZip)
    }

    private fun exportZip(tunnels: List<ObservableTunnel>) {
        val futureConfigs = tunnels.map { it.configAsync.toCompletableFuture() }.toTypedArray()
        if (futureConfigs.isEmpty()) {
            exportZipComplete(null, IllegalArgumentException(
                    context.getString(R.string.no_tunnels_error)))
            return
        }
        CompletableFuture.allOf(*futureConfigs)
                .whenComplete { _, exception ->
                    Application.getAsyncWorker().supplyAsync {
                        if (exception != null) throw exception
                        val outputFile = DownloadsFileSaver.save(context, "wireguard-export.zip", "application/zip", true)
                        try {
                            ZipOutputStream(outputFile.outputStream).use { zip ->
                                for (i in futureConfigs.indices) {
                                    zip.putNextEntry(ZipEntry(tunnels[i].name + ".conf"))
                                    zip.write(futureConfigs[i].getNow(null)!!.toWgQuickString().toByteArray(StandardCharsets.UTF_8))
                                }
                                zip.closeEntry()
                            }
                        } catch (e: Exception) {
                            outputFile.delete()
                            throw e
                        }
                        outputFile.fileName
                    }.whenComplete(this::exportZipComplete)
                }
    }

    private fun exportZipComplete(filePath: String?, throwable: Throwable?) {
        if (throwable != null) {
            val error = ErrorMessages.get(throwable)
            val message = context.getString(R.string.zip_export_error, error)
            Log.e(TAG, message, throwable)
            Snackbar.make(
                    FragmentUtils.getPrefActivity(this).findViewById(android.R.id.content),
                    message, Snackbar.LENGTH_LONG).show()
            isEnabled = true
        } else {
            exportedFilePath = filePath
            notifyChanged()
        }
    }

    override fun getSummary() = if (exportedFilePath == null) context.getString(R.string.zip_export_summary) else context.getString(R.string.zip_export_success, exportedFilePath)

    override fun getTitle() = context.getString(R.string.zip_export_title)

    override fun onClick() {
        FragmentUtils.getPrefActivity(this)
                .ensurePermissions(arrayOf(Manifest.permission.WRITE_EXTERNAL_STORAGE),
                        object : SettingsActivity.PermissionRequestCallback {
                            override fun done(permissions: Array<String>, grantResults: IntArray) {
                                if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                                    isEnabled = false
                                    exportZip()
                                }
                            }
                        })
    }

    companion object {
        private val TAG = "WireGuard/" + ZipExporterPreference::class.java.simpleName
    }
}