@@ -27,6 +27,7 @@ import android.view.View
27
27
import android.view.ViewGroup
28
28
import androidx.activity.result.contract.ActivityResultContracts
29
29
import androidx.appcompat.app.AppCompatActivity
30
+ import androidx.collection.CircularArray
30
31
import androidx.core.app.ShareCompat
31
32
import androidx.core.content.res.ResourcesCompat
32
33
import androidx.lifecycle.lifecycleScope
@@ -62,8 +63,8 @@ import java.util.regex.Pattern
62
63
class LogViewerActivity : AppCompatActivity () {
63
64
private lateinit var binding: LogViewerActivityBinding
64
65
private lateinit var logAdapter: LogEntryAdapter
65
- private var logLines = arrayListOf <LogLine >()
66
- private var rawLogLines = StringBuffer ()
66
+ private var logLines = CircularArray <LogLine >()
67
+ private var rawLogLines = CircularArray < String > ()
67
68
private var recyclerView: RecyclerView ? = null
68
69
private var saveButton: MenuItem ? = null
69
70
private val year by lazy {
@@ -113,7 +114,7 @@ class LogViewerActivity : AppCompatActivity() {
113
114
binding.shareFab.setOnClickListener {
114
115
revokeLastUri()
115
116
val key = KeyPair ().privateKey.toHex()
116
- LOGS [key] = rawLogLines.toString().toByteArray( Charsets . UTF_8 )
117
+ LOGS [key] = rawLogBytes( )
117
118
lastUri = Uri .parse(" content://${BuildConfig .APPLICATION_ID } .exported-log/$key " )
118
119
val shareIntent = ShareCompat .IntentBuilder (this )
119
120
.setType(" text/plain" )
@@ -150,13 +151,24 @@ class LogViewerActivity : AppCompatActivity() {
150
151
151
152
private val downloadsFileSaver = DownloadsFileSaver (this )
152
153
154
+ private fun rawLogBytes () : ByteArray {
155
+ val builder = StringBuilder ()
156
+ for (i in 0 until rawLogLines.size()) {
157
+ builder.append(rawLogLines[i])
158
+ builder.append(' \n ' )
159
+ }
160
+ val ret = builder.toString().toByteArray(Charsets .UTF_8 )
161
+ builder.clear()
162
+ return ret
163
+ }
164
+
153
165
private suspend fun saveLog () {
154
166
var exception: Throwable ? = null
155
167
var outputFile: DownloadsFileSaver .DownloadsFile ? = null
156
168
withContext(Dispatchers .IO ) {
157
169
try {
158
170
outputFile = downloadsFileSaver.save(" wireguard-log.txt" , " text/plain" , true )
159
- outputFile?.outputStream?.write(rawLogLines.toString().toByteArray( Charsets . UTF_8 ))
171
+ outputFile?.outputStream?.write(rawLogBytes( ))
160
172
} catch (e: Throwable ) {
161
173
outputFile?.delete()
162
174
exception = e
@@ -191,24 +203,27 @@ class LogViewerActivity : AppCompatActivity() {
191
203
var priorModified = false
192
204
val bufferedLogLines = arrayListOf<LogLine >()
193
205
var timeout = 1000000000L / 2 // The timeout is initially small so that the view gets populated immediately.
206
+ val MAX_LINES = (1 shl 17 ) - 1
207
+ val MAX_BUFFERED_LINES = (1 shl 14 ) - 1
194
208
195
209
while (true ) {
196
210
val line = stdout.readLine() ? : break
197
- rawLogLines.append(line)
198
- rawLogLines.append(' \n ' )
211
+ if (rawLogLines.size() >= MAX_LINES )
212
+ rawLogLines.popFirst()
213
+ rawLogLines.addLast(line)
199
214
val logLine = parseLine(line)
200
215
if (logLine != null ) {
201
216
bufferedLogLines.add(logLine)
202
217
} else {
203
218
if (bufferedLogLines.isNotEmpty()) {
204
219
bufferedLogLines.last().msg + = " \n $line "
205
- } else if (logLines.isNotEmpty() ) {
206
- logLines.last() .msg + = " \n $line "
220
+ } else if (! logLines.isEmpty ) {
221
+ logLines[logLines.size() - 1 ] .msg + = " \n $line "
207
222
priorModified = true
208
223
}
209
224
}
210
225
val timeNow = System .nanoTime()
211
- if ((timeNow - timeLastNotify) < timeout && stdout.ready())
226
+ if (bufferedLogLines.size < MAX_BUFFERED_LINES && (timeNow - timeLastNotify) < timeout && stdout.ready())
212
227
continue
213
228
timeout = 1000000000L * 5 / 2 // Increase the timeout after the initial view has something in it.
214
229
timeLastNotify = timeNow
@@ -219,13 +234,23 @@ class LogViewerActivity : AppCompatActivity() {
219
234
logAdapter.notifyItemChanged(posStart - 1 )
220
235
priorModified = false
221
236
}
222
- logLines.addAll(bufferedLogLines)
237
+ val fullLen = logLines.size() + bufferedLogLines.size
238
+ if (fullLen >= MAX_LINES ) {
239
+ val numToRemove = fullLen - MAX_LINES + 1
240
+ logLines.removeFromStart(numToRemove)
241
+ logAdapter.notifyItemRangeRemoved(0 , numToRemove)
242
+ posStart - = numToRemove
243
+
244
+ }
245
+ for (bufferedLine in bufferedLogLines) {
246
+ logLines.addLast(bufferedLine)
247
+ }
223
248
bufferedLogLines.clear()
224
- logAdapter.notifyItemRangeInserted(posStart, logLines.size - posStart)
225
- posStart = logLines.size
249
+ logAdapter.notifyItemRangeInserted(posStart, logLines.size() - posStart)
250
+ posStart = logLines.size()
226
251
227
252
if (isScrolledToBottomAlready) {
228
- recyclerView?.scrollToPosition(logLines.size - 1 )
253
+ recyclerView?.scrollToPosition(logLines.size() - 1 )
229
254
}
230
255
}
231
256
}
@@ -279,7 +304,7 @@ class LogViewerActivity : AppCompatActivity() {
279
304
}
280
305
}
281
306
282
- override fun getItemCount () = logLines.size
307
+ override fun getItemCount () = logLines.size()
283
308
284
309
override fun onCreateViewHolder (parent : ViewGroup , viewType : Int ): ViewHolder {
285
310
val view = LayoutInflater .from(parent.context)
0 commit comments