read-only sources must works without configured ICS repo — StreamProvider API

This commit is contained in:
Vladimir Krivosheev
2016-12-27 14:29:34 +01:00
parent ad0e6cda8b
commit 148fafa5cb
15 changed files with 172 additions and 104 deletions
@@ -278,16 +278,15 @@ class SchemeManagerImpl<T : Scheme, MUTABLE_SCHEME : T>(val fileSpec: String,
val oldSchemes = schemes
val schemes = oldSchemes.toMutableList()
val newSchemesOffset = schemes.size
if (provider != null && provider.isApplicable(fileSpec, roamingType)) {
provider.processChildren(fileSpec, roamingType, { canRead(it) }) { name, input, readOnly ->
catchAndLog(name) {
val scheme = loadScheme(name, input, schemes, filesToDelete)
if (readOnly && scheme != null) {
readOnlyExternalizableSchemes.put(scheme.name, scheme)
}
if (provider != null && provider.processChildren(fileSpec, roamingType, { canRead(it) }) { name, input, readOnly ->
catchAndLog(name) {
val scheme = loadScheme(name, input, schemes, filesToDelete)
if (readOnly && scheme != null) {
readOnlyExternalizableSchemes.put(scheme.name, scheme)
}
true
}
true
}) {
}
else {
ioDirectory.directoryStreamIfExists({ canRead(it.fileName.toString()) }) {
@@ -717,14 +716,13 @@ class SchemeManagerImpl<T : Scheme, MUTABLE_SCHEME : T>(val fileSpec: String,
}
private fun deleteFiles(errors: MutableList<Throwable>, filesToDelete: MutableSet<String>) {
if (provider != null && provider.enabled) {
if (provider != null) {
val iterator = filesToDelete.iterator()
for (name in iterator) {
errors.catch {
val spec = "$fileSpec/$name"
if (provider.isApplicable(spec, roamingType)) {
if (provider.delete(spec, roamingType)) {
iterator.remove()
provider.delete(spec, roamingType)
}
}
}
@@ -10,28 +10,25 @@ class StreamProviderWrapper : StreamProvider {
override val enabled: Boolean
get() = streamProvider.let { it != null && it.enabled }
override fun isApplicable(fileSpec: String, roamingType: RoamingType): Boolean {
return enabled && streamProvider!!.isApplicable(fileSpec, roamingType)
}
override fun isApplicable(fileSpec: String, roamingType: RoamingType) = streamProvider?.isApplicable(fileSpec, roamingType) ?: false
override fun <R> read(fileSpec: String, roamingType: RoamingType, consumer: (InputStream?) -> R): R {
return streamProvider!!.read(fileSpec, roamingType, consumer)
}
override fun read(fileSpec: String, roamingType: RoamingType, consumer: (InputStream?) -> Unit) = streamProvider?.read(fileSpec, roamingType, consumer) ?: false
override fun processChildren(path: String,
roamingType: RoamingType,
filter: Function1<String, Boolean>,
processor: Function3<String, InputStream, Boolean, Boolean>) {
streamProvider!!.processChildren(path, roamingType, filter, processor)
processor: Function3<String, InputStream, Boolean, Boolean>): Boolean {
return streamProvider?.let {
it.processChildren(path, roamingType, filter, processor)
true
} ?: false
}
override fun write(fileSpec: String, content: ByteArray, size: Int, roamingType: RoamingType) {
streamProvider!!.write(fileSpec, content, size, roamingType)
}
override fun delete(fileSpec: String, roamingType: RoamingType) {
streamProvider!!.delete(fileSpec, roamingType)
}
override fun delete(fileSpec: String, roamingType: RoamingType) = streamProvider?.delete(fileSpec, roamingType) ?: false
}
fun StreamProvider?.getOriginalProvider() = if (this is StreamProviderWrapper) streamProvider else null
@@ -47,25 +47,25 @@ abstract class XmlElementStorage protected constructor(val fileSpec: String,
override fun hasState(storageData: StateMap, componentName: String) = storageData.hasState(componentName)
override fun loadData(): StateMap {
val element: Element?
// we don't use local data if has stream provider
if (provider != null && provider.isApplicable(fileSpec, roamingType)) {
element = LOG.catchAndLog {
loadDataFromProvider().apply { dataLoadedFromProvider(this) }
override fun loadData() = loadElement()?.let { loadState(it) } ?: StateMap.EMPTY
private fun loadElement(useStreamProvider: Boolean = true): Element? {
var element: Element? = null
LOG.catchAndLog {
if (!useStreamProvider || !(provider?.read(fileSpec, roamingType) {
it?.let {
element = loadElement(it)
}
} ?: false)) {
element = loadLocalData()
}
}
else {
element = loadLocalData()
}
return element?.let { loadState(element) } ?: StateMap.EMPTY
return element
}
protected open fun dataLoadedFromProvider(element: Element?) {
}
private fun loadDataFromProvider(): Element? = provider!!.read(fileSpec, roamingType) { it?.let(::loadElement) }
private fun loadState(element: Element): StateMap {
beforeElementLoaded(element)
return StateMap.fromMap(FileStorageCoreUtil.load(element, pathMacroSubstitutor, true))
@@ -127,15 +127,15 @@ abstract class XmlElementStorage protected constructor(val fileSpec: String,
}
val provider = storage.provider
if (provider != null && provider.isApplicable(storage.fileSpec, storage.roamingType)) {
if (element == null) {
provider.delete(storage.fileSpec, storage.roamingType)
}
else {
// we should use standard line-separator (\n) - stream provider can share file content on any OS
provider.write(storage.fileSpec, element.toBufferExposingByteArray(), storage.roamingType)
if (element == null) {
if (provider == null || !provider.delete(storage.fileSpec, storage.roamingType)) {
saveLocally(null)
}
}
else if (provider != null && provider.isApplicable(storage.fileSpec, storage.roamingType)) {
// we should use standard line-separator (\n) - stream provider can share file content on any OS
provider.write(storage.fileSpec, element.toBufferExposingByteArray(), storage.roamingType)
}
else {
saveLocally(element)
}
@@ -163,14 +163,14 @@ abstract class XmlElementStorage protected constructor(val fileSpec: String,
updatedFrom(changedComponentNames, deleted, true)
}
fun updatedFrom(changedComponentNames: MutableSet<String>, deleted: Boolean, streamProvider: Boolean) {
fun updatedFrom(changedComponentNames: MutableSet<String>, deleted: Boolean, useStreamProvider: Boolean) {
if (roamingType == RoamingType.DISABLED) {
// storage roaming was changed to DISABLED, but settings repository has old state
return
}
LOG.catchAndLog {
val newElement = if (deleted) null else if (streamProvider) loadDataFromProvider() else loadLocalData()
val newElement = if (deleted) null else loadElement(useStreamProvider)
val states = storageDataRef.get()
if (newElement == null) {
// if data was loaded, mark as changed all loaded components
@@ -323,7 +323,8 @@ internal class ApplicationStoreTest {
private fun writeConfig(fileName: String, @Language("XML") data: String) = testAppConfig.writeChild(fileName, data)
private class MyStreamProvider : StreamProvider {
override fun processChildren(path: String, roamingType: RoamingType, filter: (String) -> Boolean, processor: (String, InputStream, Boolean) -> Boolean) {
override fun processChildren(path: String, roamingType: RoamingType, filter: (String) -> Boolean, processor: (String, InputStream, Boolean) -> Boolean): Boolean {
return true
}
val data: MutableMap<RoamingType, MutableMap<String, String>> = THashMap()
@@ -341,13 +342,15 @@ internal class ApplicationStoreTest {
return map
}
override fun <R> read(fileSpec: String, roamingType: RoamingType, consumer: (InputStream?) -> R): R {
override fun read(fileSpec: String, roamingType: RoamingType, consumer: (InputStream?) -> Unit): Boolean {
val data = getMap(roamingType).get(fileSpec)
return data?.let { ByteArrayInputStream(it.toByteArray()) }.let(consumer)
data?.let { ByteArrayInputStream(it.toByteArray()) }.let(consumer)
return true
}
override fun delete(fileSpec: String, roamingType: RoamingType) {
data[roamingType]?.remove(fileSpec)
override fun delete(fileSpec: String, roamingType: RoamingType): Boolean {
data.get(roamingType)?.remove(fileSpec)
return true
}
}
@@ -11,17 +11,18 @@ class MockStreamProvider(private val dir: Path) : StreamProvider {
dir.resolve(fileSpec).write(content, 0, size)
}
override fun <R> read(fileSpec: String, roamingType: RoamingType, consumer: (InputStream?) -> R): R {
override fun read(fileSpec: String, roamingType: RoamingType, consumer: (InputStream?) -> Unit): Boolean {
val file = dir.resolve(fileSpec)
try {
return file.inputStream().use(consumer)
file.inputStream().use(consumer)
}
catch (e: NoSuchFileException) {
return consumer(null)
consumer(null)
}
return true
}
override fun processChildren(path: String, roamingType: RoamingType, filter: (name: String) -> Boolean, processor: (name: String, input: InputStream, readOnly: Boolean) -> Boolean) {
override fun processChildren(path: String, roamingType: RoamingType, filter: (name: String) -> Boolean, processor: (name: String, input: InputStream, readOnly: Boolean) -> Boolean): Boolean {
dir.resolve(path).directoryStreamIfExists({ filter(it.fileName.toString()) }) {
for (file in it) {
val attributes = file.basicAttributesIfExists()
@@ -40,9 +41,12 @@ class MockStreamProvider(private val dir: Path) : StreamProvider {
}
}
}
return true
}
override fun delete(fileSpec: String, roamingType: RoamingType) {
override fun delete(fileSpec: String, roamingType: RoamingType): Boolean {
dir.resolve(fileSpec).delete()
return true
}
}
@@ -26,9 +26,7 @@ import javax.swing.ButtonGroup
import javax.swing.JLabel
class LayoutBuilder(val `$`: LayoutBuilderImpl, val buttonGroup: ButtonGroup? = null) {
inline fun row(label: String, init: Row.() -> Unit) {
row(label = Label(label), init = init)
}
inline fun row(label: String, init: Row.() -> Unit) = row(label = Label(label), init = init)
inline fun row(label: JLabel? = null, separated: Boolean = false, init: Row.() -> Unit): Row {
val row = `$`.newRow(label, buttonGroup, separated)
@@ -81,6 +81,7 @@ internal class MigLayoutBuilder : LayoutBuilderImpl {
}
lc.noVisualPadding()
lc.hideMode = 3
container.layout = MigLayout(lc)
@@ -209,6 +210,19 @@ private class MigLayoutRow(private val componentConstraints: MutableMap<Componen
}
}
override var visible: Boolean = true
get() = field
set(value) {
if (field == value) {
return
}
field = value
for (c in components) {
c.isVisible = value
}
}
override var subRowsEnabled: Boolean = true
get() = field
set(value) {
@@ -220,6 +234,17 @@ private class MigLayoutRow(private val componentConstraints: MutableMap<Componen
_subRows?.forEach { it.enabled = value }
}
override var subRowsVisible: Boolean = true
get() = field
set(value) {
if (field == value) {
return
}
field = value
_subRows?.forEach { it.visible = value }
}
override operator fun JComponent.invoke(vararg constraints: CCFlags, gapLeft: Int, growPolicy: GrowPolicy?) {
addComponent(this, constraints, gapLeft = gapLeft, growPolicy = growPolicy)
}
@@ -45,8 +45,12 @@ import javax.swing.JLabel
abstract class Row() {
abstract var enabled: Boolean
abstract var visible: Boolean
abstract var subRowsEnabled: Boolean
abstract var subRowsVisible: Boolean
abstract val subRows: List<Row>
protected abstract val builder: LayoutBuilderImpl
@@ -24,6 +24,9 @@ interface StreamProvider {
val enabled: Boolean
get() = true
/**
* Called only on `write`
*/
fun isApplicable(fileSpec: String, roamingType: RoamingType = RoamingType.DEFAULT) = true
/**
@@ -33,14 +36,22 @@ interface StreamProvider {
*/
fun write(fileSpec: String, content: ByteArray, size: Int = content.size, roamingType: RoamingType = RoamingType.DEFAULT)
fun <R> read(fileSpec: String, roamingType: RoamingType = RoamingType.DEFAULT, consumer: (InputStream?) -> R): R
/**
* `true` if provider is applicable for file.
*/
fun read(fileSpec: String, roamingType: RoamingType = RoamingType.DEFAULT, consumer: (InputStream?) -> Unit): Boolean
fun processChildren(path: String, roamingType: RoamingType, filter: (name: String) -> Boolean, processor: (name: String, input: InputStream, readOnly: Boolean) -> Boolean)
/**
* `true` if provider is fully responsible and local sources must be not used.
*/
fun processChildren(path: String, roamingType: RoamingType, filter: (name: String) -> Boolean, processor: (name: String, input: InputStream, readOnly: Boolean) -> Boolean): Boolean
/**
* Delete file or directory
*
* `true` if provider is fully responsible and local sources must be not used.
*/
fun delete(fileSpec: String, roamingType: RoamingType = RoamingType.DEFAULT)
fun delete(fileSpec: String, roamingType: RoamingType = RoamingType.DEFAULT): Boolean
}
@TestOnly
@@ -27,24 +27,26 @@ import java.io.InputStream
class SchemeManagerIprProvider(private val subStateTagName: String) : StreamProvider {
private val nameToData = ContainerUtil.newConcurrentMap<String, ByteArray>()
override fun <R> read(fileSpec: String, roamingType: RoamingType, consumer: (InputStream?) -> R): R {
val name = PathUtilRt.getFileName(fileSpec)
return nameToData.get(name)?.let(ByteArray::inputStream).let { consumer(it) }
override fun read(fileSpec: String, roamingType: RoamingType, consumer: (InputStream?) -> Unit): Boolean {
nameToData.get(PathUtilRt.getFileName(fileSpec))?.let(ByteArray::inputStream).let { consumer(it) }
return true
}
override fun delete(fileSpec: String, roamingType: RoamingType) {
override fun delete(fileSpec: String, roamingType: RoamingType): Boolean {
nameToData.remove(PathUtilRt.getFileName(fileSpec))
return true
}
override fun processChildren(path: String,
roamingType: RoamingType,
filter: (String) -> Boolean,
processor: (String, InputStream, Boolean) -> Boolean) {
processor: (String, InputStream, Boolean) -> Boolean): Boolean {
for ((name, data) in nameToData) {
if (filter(name) && !data.inputStream().use { processor(name, it, false) }) {
break
}
}
return true
}
override fun write(fileSpec: String, content: ByteArray, size: Int, roamingType: RoamingType) {
+30 -8
View File
@@ -47,7 +47,7 @@ internal const val PLUGIN_NAME = "Settings Repository"
internal val LOG = logger<IcsManager>()
val icsManager by lazy(LazyThreadSafetyMode.NONE) {
internal val icsManager by lazy(LazyThreadSafetyMode.NONE) {
ApplicationLoadListener.EP_NAME.findExtension(IcsApplicationLoadListener::class.java).icsManager
}
@@ -58,6 +58,7 @@ class IcsManager @JvmOverloads constructor(dir: Path, val schemeManagerFactory:
val settings: IcsSettings
val repositoryManager: RepositoryManager = GitRepositoryManager(credentialsStore, dir.resolve("repository"))
val readOnlySourcesManager = ReadOnlySourceManager(this, dir)
init {
settings = try {
@@ -69,8 +70,6 @@ class IcsManager @JvmOverloads constructor(dir: Path, val schemeManagerFactory:
}
}
val readOnlySourcesManager = ReadOnlySourceManager(settings, dir)
val repositoryService: RepositoryService = GitRepositoryService()
private val commitAlarm = SingleAlarm(Runnable {
@@ -98,7 +97,11 @@ class IcsManager @JvmOverloads constructor(dir: Path, val schemeManagerFactory:
}
inner class ApplicationLevelProvider : IcsStreamProvider(null) {
override fun delete(fileSpec: String, roamingType: RoamingType) {
override fun delete(fileSpec: String, roamingType: RoamingType): Boolean {
if (!repositoryActive) {
return false
}
if (syncManager.writeAndDeleteProhibited) {
throw IllegalStateException("Delete is prohibited now")
}
@@ -106,6 +109,8 @@ class IcsManager @JvmOverloads constructor(dir: Path, val schemeManagerFactory:
if (repositoryManager.delete(toRepositoryPath(fileSpec, roamingType))) {
scheduleCommit()
}
return true
}
}
@@ -179,9 +184,9 @@ class IcsManager @JvmOverloads constructor(dir: Path, val schemeManagerFactory:
override val enabled: Boolean
get() = this@IcsManager.active
override fun isApplicable(fileSpec: String, roamingType: RoamingType): Boolean = enabled
override fun isApplicable(fileSpec: String, roamingType: RoamingType): Boolean = repositoryActive
override fun processChildren(path: String, roamingType: RoamingType, filter: (name: String) -> Boolean, processor: (name: String, input: InputStream, readOnly: Boolean) -> Boolean) {
override fun processChildren(path: String, roamingType: RoamingType, filter: (name: String) -> Boolean, processor: (name: String, input: InputStream, readOnly: Boolean) -> Boolean): Boolean {
val fullPath = toRepositoryPath(path, roamingType, null)
// first of all we must load read-only schemes - scheme could be overridden if bundled or read-only, so, such schemes must be loaded first
@@ -189,7 +194,12 @@ class IcsManager @JvmOverloads constructor(dir: Path, val schemeManagerFactory:
repository.processChildren(fullPath, filter) { name, input -> processor(name, input, true) }
}
if (!repositoryActive) {
return false
}
repositoryManager.processChildren(fullPath, filter) { name, input -> processor(name, input, false) }
return true
}
override fun write(fileSpec: String, content: ByteArray, size: Int, roamingType: RoamingType) {
@@ -206,9 +216,17 @@ class IcsManager @JvmOverloads constructor(dir: Path, val schemeManagerFactory:
protected open fun isAutoCommit(fileSpec: String, roamingType: RoamingType) = true
override fun <R> read(fileSpec: String, roamingType: RoamingType, consumer: (InputStream?) -> R): R = repositoryManager.read(toRepositoryPath(fileSpec, roamingType, projectId), consumer)
override fun read(fileSpec: String, roamingType: RoamingType, consumer: (InputStream?) -> Unit): Boolean {
if (!repositoryActive) {
return false
}
override fun delete(fileSpec: String, roamingType: RoamingType) {
repositoryManager.read(toRepositoryPath(fileSpec, roamingType, projectId), consumer)
return true
}
override fun delete(fileSpec: String, roamingType: RoamingType): Boolean {
return false
}
}
}
@@ -218,6 +236,10 @@ class IcsApplicationLoadListener : ApplicationLoadListener {
private set
override fun beforeApplicationLoaded(application: Application, configPath: String) {
if (application.isUnitTestMode) {
return
}
val customPath = System.getProperty("ics.settingsRepository")
val pluginSystemDir = if (customPath == null) Paths.get(configPath, "settingsRepository") else Paths.get(FileUtil.expandUserHome(customPath))
icsManager = IcsManager(pluginSystemDir)
@@ -33,14 +33,14 @@ import org.jetbrains.settingsRepository.git.upstream
import org.jetbrains.settingsRepository.git.use
import java.nio.file.Path
class ReadOnlySourceManager(private val settings: IcsSettings, val rootDir: Path) {
class ReadOnlySourceManager(private val icsManager: IcsManager, val rootDir: Path) {
private val repositoryList = object : AtomicClearableLazyValue<List<Repository>>() {
override fun compute(): List<Repository> {
if (settings.readOnlySources.isEmpty()) {
if (icsManager.settings.readOnlySources.isEmpty()) {
return emptyList()
}
return settings.readOnlySources.mapSmartNotNull { source ->
return icsManager.settings.readOnlySources.mapSmartNotNull { source ->
LOG.catchAndLog {
if (!source.active) {
return@mapSmartNotNull null
@@ -64,7 +64,7 @@ class ReadOnlySourceManager(private val settings: IcsSettings, val rootDir: Path
get() = repositoryList.value
fun setSources(sources: List<ReadonlySource>) {
settings.readOnlySources = sources
icsManager.settings.readOnlySources = sources
repositoryList.drop()
}
+1 -1
View File
@@ -152,7 +152,7 @@ internal class AutoSyncManager(private val icsManager: IcsManager) {
if (!onAppExit &&
!app.isDisposeInProgress &&
updateResult != null &&
updateStoragesFromStreamProvider(app.stateStore as ComponentStoreImpl, updateResult, app.messageBus)) {
updateStoragesFromStreamProvider(icsManager, app.stateStore as ComponentStoreImpl, updateResult, app.messageBus)) {
// force to avoid saveAll & confirmation
app.exit(true, true, true)
}
@@ -50,17 +50,18 @@ internal fun createRepositoryListEditor(): ConfigurableUi<IcsSettings> {
}
return object: ConfigurableUi<IcsSettings> {
private var noRepositoryRow: Row? = null
private var repositoryRow: Row? = null
override fun isModified(settings: IcsSettings) = editor.isModified
override fun getComponent() = panel {
row("Repository:") {
if (editor.model.isEmpty) {
hint("Use File -> Settings Repository... to configure")
}
else {
editor.comboBox()
deleteButton()
}
noRepositoryRow = row("Repository:") {
hint("Use File -> Settings Repository... to configure")
}
repositoryRow = row("Repository:") {
editor.comboBox()
deleteButton()
}
}
@@ -82,6 +83,9 @@ internal fun createRepositoryListEditor(): ConfigurableUi<IcsSettings> {
editor.reset(list)
editor.model.selectedItem = upstream
noRepositoryRow!!.visible = list.isEmpty()
repositoryRow!!.visible = list.isNotEmpty()
deleteButton.isEnabled = editor.model.selectedItem != null
}
}
+19 -19
View File
@@ -142,7 +142,7 @@ internal class SyncManager(private val icsManager: IcsManager, private val autoS
if (updateResult != null) {
val app = ApplicationManager.getApplication()
restartApplication = updateStoragesFromStreamProvider(app.stateStore as ComponentStoreImpl, updateResult!!, app.messageBus,
restartApplication = updateStoragesFromStreamProvider(icsManager, app.stateStore as ComponentStoreImpl, updateResult!!, app.messageBus,
reloadAllSchemes = syncType == SyncType.OVERWRITE_LOCAL)
}
}
@@ -158,30 +158,30 @@ internal class SyncManager(private val icsManager: IcsManager, private val autoS
}
return updateResult != null || isReadOnlySourcesChanged
}
}
private fun updateCloudSchemes(indicator: ProgressIndicator): Boolean {
val changedRootDirs = icsManager.readOnlySourcesManager.update(indicator) ?: return false
val schemeManagersToReload = SmartList<SchemeManagerImpl<*, *>>()
icsManager.schemeManagerFactory.value.process {
val fileSpec = toRepositoryPath(it.fileSpec, it.roamingType)
if (changedRootDirs.contains(fileSpec)) {
schemeManagersToReload.add(it)
}
}
if (schemeManagersToReload.isNotEmpty()) {
invokeAndWaitIfNeed {
for (schemeManager in schemeManagersToReload) {
schemeManager.reload()
private fun updateCloudSchemes(indicator: ProgressIndicator): Boolean {
val changedRootDirs = icsManager.readOnlySourcesManager.update(indicator) ?: return false
val schemeManagersToReload = SmartList<SchemeManagerImpl<*, *>>()
icsManager.schemeManagerFactory.value.process {
val fileSpec = toRepositoryPath(it.fileSpec, it.roamingType)
if (changedRootDirs.contains(fileSpec)) {
schemeManagersToReload.add(it)
}
}
}
return schemeManagersToReload.isNotEmpty()
if (schemeManagersToReload.isNotEmpty()) {
invokeAndWaitIfNeed {
for (schemeManager in schemeManagersToReload) {
schemeManager.reload()
}
}
}
return schemeManagersToReload.isNotEmpty()
}
}
internal fun updateStoragesFromStreamProvider(store: ComponentStoreImpl, updateResult: UpdateResult, messageBus: MessageBus, reloadAllSchemes: Boolean = false): Boolean {
internal fun updateStoragesFromStreamProvider(icsManager: IcsManager, store: ComponentStoreImpl, updateResult: UpdateResult, messageBus: MessageBus, reloadAllSchemes: Boolean = false): Boolean {
val (changed, deleted) = (store.storageManager as StateStorageManagerImpl).getCachedFileStorages(updateResult.changed, updateResult.deleted, ::toIdeaPath)
val schemeManagersToReload = SmartList<SchemeManagerImpl<*, *>>()