Skip to content

Commit

Permalink
feat: exclusive mutable access to files
Browse files Browse the repository at this point in the history
  • Loading branch information
oSumAtrIX committed Aug 1, 2022
1 parent 8d95b14 commit 814ce0b
Showing 1 changed file with 44 additions and 9 deletions.
53 changes: 44 additions & 9 deletions src/main/kotlin/app/revanced/patcher/data/impl/ResourceData.kt
Expand Up @@ -19,31 +19,66 @@ class ResourceData(private val resourceCacheDirectory: File) : Data, Iterable<Fi
override fun iterator() = resourceCacheDirectory.walkTopDown().iterator()

inner class XmlFileHolder {
operator fun get(inputStream: InputStream, outputStream: OutputStream) =
DomFileEditor(inputStream, lazyOf(outputStream))
operator fun get(inputStream: InputStream) =
DomFileEditor(inputStream)

operator fun get(path: String): DomFileEditor {
return DomFileEditor(this@ResourceData[path])
}

operator fun get(path: String) = DomFileEditor(this@ResourceData[path])
}
}

/**
* DomFileEditor is a wrapper for a file that can be edited as a dom document.
*
* @param inputStream the input stream to read the xml file from.
* @param outputStream the output stream to write the xml file to. If null, the file will not be written.
*/
class DomFileEditor internal constructor(
private val inputStream: InputStream,
private val outputStream: Lazy<OutputStream>,
private val outputStream: Lazy<OutputStream>? = null,
) : Closeable {
// path to the xml file to unlock the resource when closing the editor
private var filePath: String? = null

/**
* The document of the xml file
*/
val file: Document = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(inputStream)
.also(Document::normalize)

// lazily open an output stream
// this is required because when constructing a DomFileEditor the output stream is created along with the input stream, which is not allowed
// the workaround is to lazily create the output stream. This way it would be used after the input stream is closed, which happens in the constructor
constructor(file: File) : this(file.inputStream(), lazy { file.outputStream() })
constructor(file: File) : this(file.inputStream(), lazy { file.outputStream() }) {
filePath = file.path

override fun close() {
val result = StreamResult(outputStream.value)
TransformerFactory.newInstance().newTransformer().transform(DOMSource(file), result)
// prevent sharing mutability of the same file between multiple instances of DomFileEditor
if (locks.contains(filePath))
throw IllegalStateException("Can not create a DomFileEditor for that file because it is already locked by another instance of DomFileEditor.")
locks.add(filePath!!)
}

override fun close() {
inputStream.close()
outputStream.value.close()

// if the output stream is not null, do not close it
outputStream?.let {
val result = StreamResult(it.value)
TransformerFactory.newInstance().newTransformer().transform(DOMSource(file), result)

it.value.close()
}

// remove the lock, if it exists
filePath?.let {
locks.remove(it)
}
}

private companion object {
// list of locked file paths
val locks = mutableListOf<String>()
}
}

0 comments on commit 814ce0b

Please sign in to comment.