Skip to content

Commit

Permalink
feat: RwLock for opening files in DomFileEditor
Browse files Browse the repository at this point in the history
  • Loading branch information
oSumAtrIX committed Sep 26, 2022
1 parent 4839f87 commit db4348c
Showing 1 changed file with 36 additions and 17 deletions.
53 changes: 36 additions & 17 deletions src/main/kotlin/app/revanced/patcher/data/impl/ResourceData.kt
Original file line number Diff line number Diff line change
Expand Up @@ -30,55 +30,74 @@ class ResourceData(private val resourceCacheDirectory: File) : Data, Iterable<Fi
}

/**
* DomFileEditor is a wrapper for a file that can be edited as a dom document.
* Wrapper for a file that can be edited as a dom document.
* Note: This constructor does not check for locks to the file when writing. Use the secondary constructor.
*
* @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.
* @param outputStream the output stream to write the xml file to. If null, the file will be read only.
*
*/
class DomFileEditor internal constructor(
private val inputStream: InputStream,
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
private var closed: Boolean = false

/**
* 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() }) {
// increase the lock
locks.merge(file.path, 1, Integer::sum)
filePath = file.path

// 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!!)
}

/**
* Closes the editor. Write backs and decreases the lock count.
* Note: Will not write back to the file if the file is still locked.
*/
override fun close() {
if (closed) return

inputStream.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()
// prevent writing to same file, if it is being locked
// isLocked will be false if the editor was created through a stream
val isLocked = filePath?.let { path ->
val isLocked = locks[path]!! > 1
// decrease the lock count if the editor was opened for a file
locks.merge(path, -1, Integer::sum)
isLocked
} ?: false

// if unlocked, write back to the file
if (!isLocked) {
it.value.use { stream ->
val result = StreamResult(stream)
TransformerFactory.newInstance().newTransformer().transform(DOMSource(file), result)
}

it.value.close()
return
}
}

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

private companion object {
// list of locked file paths
val locks = mutableListOf<String>()
// map of concurrent open files
val locks = mutableMapOf<String, Int>()
}
}

0 comments on commit db4348c

Please sign in to comment.