AppDetailFragment.kt 6.43 KB
Newer Older
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/*
 * Copyright by the original author or authors.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

package de.ccc.events.badge.card10.hatchery

25
import android.net.Uri
Anon's avatar
Anon committed
26
import android.os.AsyncTask
27
import android.os.Bundle
Anon's avatar
Anon committed
28
import android.util.Log
29
30
31
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
Anon's avatar
Anon committed
32
import androidx.appcompat.app.AlertDialog
33
import androidx.core.net.toUri
34
import androidx.fragment.app.Fragment
35
import androidx.fragment.app.FragmentManager
36
import de.ccc.events.badge.card10.R
Anon's avatar
Anon committed
37
import de.ccc.events.badge.card10.common.LoadingDialog
38
39
import de.ccc.events.badge.card10.filetransfer.BatchTransferFragment
import de.ccc.events.badge.card10.filetransfer.TransferJob
40
import kotlinx.android.synthetic.main.app_detail_fragment.*
Anon's avatar
Anon committed
41
42
import org.apache.commons.compress.archivers.tar.TarArchiveInputStream
import org.apache.commons.compress.compressors.gzip.GzipCompressorInputStream
Anon's avatar
Anon committed
43
import java.io.File
44

Anon's avatar
Anon committed
45
46
private const val TAG = "AppDetailFragment"

47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
class AppDetailFragment : Fragment() {

    private lateinit var app: App

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        val bundle = arguments ?: throw IllegalStateException()
        app = bundle.getParcelable<App>("app") ?: throw IllegalStateException()
    }

    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?) =
        inflater.inflate(R.layout.app_detail_fragment, container, false)

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        label_name.text = app.name
        label_download_count.text = getString(R.string.app_detail_downloads, app.download_counter)
        label_content_size.text = getString(R.string.app_detail_content_size, app.size_of_content)

        label_description.text = app.description
Anon's avatar
Anon committed
67
68

        button_download.setOnClickListener {
Anon's avatar
Anon committed
69
70
71
72
73
74
75
76
77
78
79
80
            val ctx = activity ?: throw java.lang.IllegalStateException()

            val loadingDialog = LoadingDialog()
            loadingDialog.show(fragmentManager, "loading")

            val errorDialog =
                AlertDialog.Builder(ctx).setMessage(R.string.hatchery_error_generic)
                    .setPositiveButton(
                        R.string.dialog_action_ok
                    ) { dialog, _ -> dialog.dismiss() }
                    .create()

81
82
            val fm = fragmentManager ?: throw java.lang.IllegalStateException()
            ReleaseDownload(app, ctx.cacheDir, loadingDialog, errorDialog, fm).execute()
Anon's avatar
Anon committed
83
84
85
        }
    }

Anon's avatar
Anon committed
86
87
88
89
    private class ReleaseDownload(
        private val app: App,
        private val cacheDir: File,
        private val loadingDialog: LoadingDialog,
90
91
92
        private val errorDialog: AlertDialog,
        private val fragmentManager: FragmentManager
    ) : AsyncTask<Void, Void, List<TransferJob>?>() {
Anon's avatar
Anon committed
93

94
        override fun doInBackground(vararg p0: Void?): List<TransferJob>? {
Anon's avatar
Anon committed
95
            return try {
Anon's avatar
Anon committed
96
97
                cacheDir.deleteRecursively()
                cacheDir.mkdir()
98
                val appDir = File(cacheDir.absolutePath + "/apps").mkdirs()
Anon's avatar
Anon committed
99

Anon's avatar
Anon committed
100
                val inputStream = HatcheryClient().openDownloadStream(app)
Anon's avatar
Anon committed
101
                val file = File.createTempFile(app.slug, ".tar.gz", cacheDir)
Anon's avatar
Anon committed
102
103
104
105
                val outputStream = file.outputStream()

                inputStream.copyTo(outputStream)

106
                val appFiles = mutableListOf<TransferJob>()
Anon's avatar
Anon committed
107
108
109
110
111
112
113
114
                val tarStream = TarArchiveInputStream(GzipCompressorInputStream(file.inputStream()))
                while (true) {
                    val entry = tarStream.nextTarEntry ?: break
                    if (entry.isDirectory) {
                        continue
                    }

                    // TODO: A bit hacky. Maybe there is a better way?
115
                    val targetFile = File(cacheDir, "apps/${entry.name}")
Anon's avatar
Anon committed
116
117
118
119
                    targetFile.parentFile?.mkdirs()
                    targetFile.createNewFile()
                    Log.d(TAG, "Extracting ${entry.name} to ${targetFile.absolutePath}")
                    tarStream.copyTo(targetFile.outputStream())
Anon's avatar
Anon committed
120
                    appFiles.add(TransferJob(targetFile.toUri(), "/apps/${entry.name}"))
Anon's avatar
Anon committed
121
122
                }

123
124
125
126
                val launcher = createLauncher(app.slug, cacheDir)
                appFiles.add(launcher)

                appFiles
Anon's avatar
Anon committed
127
128
129
130
131
            } catch (e: Exception) {
                null
            }
        }

132
133
        override fun onPostExecute(jobs: List<TransferJob>?) {
            if (jobs == null) {
Anon's avatar
Anon committed
134
135
136
137
138
139
                loadingDialog.dismiss()
                errorDialog.show()
                return
            }

            loadingDialog.dismiss()
140
141
142
143
144
145
146
147
148
149

            val bundle = Bundle()
            bundle.putParcelableArray("jobs", jobs.toTypedArray())
            val fragment = BatchTransferFragment()
            fragment.arguments = bundle

            fragmentManager.beginTransaction()
                .replace(R.id.fragment_container, fragment)
                .addToBackStack(null)
                .commit()
Anon's avatar
Anon committed
150
        }
151

152
        fun createLauncher(slug: String, cacheDir: File): TransferJob {
153
154
155
156
157
158
159
            val fileName = "$slug.py"
            val file = File(cacheDir, fileName)
            file.createNewFile()

            val src = """
                # Launcher script for $slug
                import os
Anon's avatar
Anon committed
160
                os.exec("/apps/$slug/__init__.py")
161
162
163
164
            """.trimIndent()

            file.writeText(src)

Anon's avatar
Anon committed
165
            return TransferJob(file.toUri(), "/$fileName")
166
        }
167
168
    }
}