-
Notifications
You must be signed in to change notification settings - Fork 118
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add support for customizing the output filename
This is a hidden feature and will not be exposed via BCR's GUI. A user can customize the output filename by copying the default template to <output directory>/bcr.properties and then editing the file. Fixes: #187 Signed-off-by: Andrew Gunnerson <chillermillerlong@hotmail.com>
- Loading branch information
1 parent
aab39c5
commit 588d3f6
Showing
4 changed files
with
277 additions
and
55 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,119 @@ | ||
package com.chiller3.bcr | ||
|
||
import android.content.Context | ||
import android.util.Log | ||
import androidx.documentfile.provider.DocumentFile | ||
import java.util.* | ||
import java.util.regex.Pattern | ||
|
||
class FilenameTemplate private constructor(props: Properties) { | ||
private val components = arrayListOf<Component>() | ||
|
||
init { | ||
Log.d(TAG, "Filename template: $props") | ||
|
||
while (true) { | ||
val index = components.size | ||
val text = props.getProperty("filename.$index.text") ?: break | ||
val default = props.getProperty("filename.$index.default") | ||
val prefix = props.getProperty("filename.$index.prefix") | ||
val suffix = props.getProperty("filename.$index.suffix") | ||
|
||
components.add(Component(text, default, prefix, suffix)) | ||
} | ||
|
||
if (components.isEmpty() || !components[0].text.startsWith(VAR_DATE)) { | ||
throw IllegalArgumentException("The first filename component must begin with $VAR_DATE") | ||
} | ||
|
||
Log.d(TAG, "Loaded filename components: $components") | ||
} | ||
|
||
fun evaluate(getVar: (String) -> String?): String { | ||
val varCache = hashMapOf<String, String?>() | ||
val getVarCached = { name: String -> | ||
varCache.getOrPut(name) { | ||
getVar(name) | ||
} | ||
} | ||
|
||
return buildString { | ||
for (c in components) { | ||
var text = evalVars(c.text, getVarCached) | ||
if (text.isEmpty() && c.default != null) { | ||
text = evalVars(c.default, getVarCached) | ||
} | ||
if (text.isNotEmpty()) { | ||
if (c.prefix != null) { | ||
append(evalVars(c.prefix, getVarCached)) | ||
} | ||
append(text) | ||
if (c.suffix != null) { | ||
append(evalVars(c.suffix, getVarCached)) | ||
} | ||
} | ||
} | ||
} | ||
} | ||
|
||
private data class Component( | ||
val text: String, | ||
val default: String?, | ||
val prefix: String?, | ||
val suffix: String?, | ||
) | ||
|
||
companion object { | ||
private val TAG = FilenameTemplate::class.java.simpleName | ||
|
||
private val VAR_PATTERN = Pattern.compile("""\${'$'}\{(\w+)\}""") | ||
private val VAR_DATE = "${'$'}{date}" | ||
|
||
private fun evalVars(input: String, getVar: (String) -> String?): String = | ||
StringBuffer().run { | ||
val m = VAR_PATTERN.matcher(input) | ||
|
||
while (m.find()) { | ||
val name = m.group(1)!! | ||
val replacement = getVar(name) | ||
|
||
m.appendReplacement(this, replacement ?: "") | ||
} | ||
|
||
m.appendTail(this) | ||
|
||
toString() | ||
} | ||
|
||
fun load(context: Context): FilenameTemplate { | ||
val props = Properties() | ||
|
||
val prefs = Preferences(context) | ||
val outputDir = prefs.outputDir?.let { | ||
// Only returns null on API <21 | ||
DocumentFile.fromTreeUri(context, it)!! | ||
} ?: DocumentFile.fromFile(prefs.defaultOutputDir) | ||
|
||
val templateFile = outputDir.findFile("bcr.properties") | ||
if (templateFile != null) { | ||
try { | ||
Log.d(TAG, "Loading custom filename template: ${templateFile.uri}") | ||
|
||
context.contentResolver.openInputStream(templateFile.uri)?.use { | ||
props.load(it) | ||
return FilenameTemplate(props) | ||
} | ||
} catch (e: Exception) { | ||
Log.w(TAG, "Failed to load custom filename template", e) | ||
} | ||
} | ||
|
||
Log.d(TAG, "Loading default filename template") | ||
|
||
context.resources.openRawResource(R.raw.filename_template).use { | ||
props.load(it) | ||
return FilenameTemplate(props) | ||
} | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,69 @@ | ||
# This file specifies the filename template for BCR's output files. To change | ||
# the default filename template, copy this file to `bcr.properties` in the | ||
# output directory and edit it to your liking. | ||
# | ||
# Syntax/rules: | ||
# 1. The filename components start at 0. | ||
# 2. Do not skip numbers for the components. For example, if there are 4 | ||
# components: 0, 1, 2, 4, then 4 is ignored because there's a gap in the | ||
# middle. | ||
# 3. Blank lines and lines beginning with # are ignored. | ||
# 4. The file extension is not part of this template. File extensions are | ||
# automatically determined by Android. | ||
# | ||
# Available options: | ||
# - filename.<num>.text: The text to add to the filename. Variables are included | ||
# with the ${...} syntax. If a variable is not defined, then it is replaced | ||
# with an empty string. | ||
# - filename.<num>.default: If `text` is empty, then this value is used as a | ||
# fallback. | ||
# - filename.<num>.prefix: If `text` (and `default`) are not empty, then this | ||
# value is added to the beginning. | ||
# - filename.<num>.suffix: If `text` (and `default`) are not empty, then this | ||
# value is added to the end. | ||
# | ||
# Troubleshooting: | ||
# If there is a syntax error, BCR will ignore the custom template and fall | ||
# back to the default. To find out more details, enable debug mode by long | ||
# pressing BCR's version number. After the next phone call, BCR will create a | ||
# log file in the output directory. Search for `FilenameTemplate` in the log | ||
# file. | ||
|
||
# Time of call. Must always be the first component. | ||
filename.0.text = ${date} | ||
|
||
# Call direction, which is either `in` or `out`. Only defined on Android 10+. | ||
filename.1.text = ${direction} | ||
filename.1.prefix = _ | ||
|
||
# SIM slot number. Only defined on Android 11+ if multiple SIMs are active and | ||
# the user has granted the phone permission. | ||
filename.2.text = ${sim_slot} | ||
filename.2.prefix = _sim | ||
|
||
# Phone number of the other party in the call. | ||
filename.3.text = ${phone_number} | ||
filename.3.prefix = _ | ||
|
||
# Caller ID as provided by CNAP from the carrier. | ||
filename.4.text = ${caller_name} | ||
filename.4.prefix = _ | ||
|
||
# Contact name. Only defined on Android 11+ if the user has granted the contacts | ||
# permission. | ||
filename.5.text = ${contact_name} | ||
filename.5.prefix = _ | ||
|
||
################################################################################ | ||
|
||
# Example: Add the call direction to the filename with a leading underscore. If | ||
# the call direction can't be determined, then add "unknown" instead. | ||
#filename.<num>.text = ${direction} | ||
#filename.<num>.default = unknown | ||
#filename.<num>.prefix = _ | ||
|
||
# Example: Add the contact name to the filename if it exists. Otherwise, fall | ||
# back to the caller ID. | ||
#filename.<num>.text = ${contact_name} | ||
#filename.<num>.default = ${caller_name} | ||
#filename.<num>.prefix = _ |