
com.jetbrains.pluginverifier.plugin.PluginFilesBank.kt Maven / Gradle / Ivy
/*
* Copyright 2000-2020 JetBrains s.r.o. and other contributors. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
*/
package com.jetbrains.pluginverifier.plugin
import com.jetbrains.plugin.structure.base.utils.deleteLogged
import com.jetbrains.plugin.structure.base.utils.listFiles
import com.jetbrains.plugin.structure.base.utils.nameWithoutExtension
import com.jetbrains.plugin.structure.base.utils.replaceInvalidFileNameCharacters
import com.jetbrains.pluginverifier.repository.Downloadable
import com.jetbrains.pluginverifier.repository.PluginInfo
import com.jetbrains.pluginverifier.repository.PluginRepository
import com.jetbrains.pluginverifier.repository.cleanup.DiskSpaceSetting
import com.jetbrains.pluginverifier.repository.cleanup.LruFileSizeSweepPolicy
import com.jetbrains.pluginverifier.repository.downloader.DownloadProvider
import com.jetbrains.pluginverifier.repository.downloader.DownloadStatistics
import com.jetbrains.pluginverifier.repository.downloader.UrlDownloader
import com.jetbrains.pluginverifier.repository.files.FileRepository
import com.jetbrains.pluginverifier.repository.files.FileRepositoryResult
import com.jetbrains.pluginverifier.repository.files.IdleFileLock
import com.jetbrains.pluginverifier.repository.repositories.marketplace.MarketplaceRepository
import com.jetbrains.pluginverifier.repository.repositories.marketplace.UpdateInfo
import org.apache.commons.io.FileUtils
import java.net.URL
import java.nio.file.Path
/**
* Storage of plugin files cached locally.
*
* Each plugin is identified by its [PluginInfo] and can be accessed in [getPluginFile].
* A lock is registered for the plugin file to avoid use-remove conflicts
* when one thread uses the file and another thread deletes it.
*/
class PluginFilesBank(
private val fileRepository: FileRepository,
private val urlProvider: (PluginInfo) -> URL?,
private val downloadProvider: DownloadProvider
) : PluginFileProvider {
companion object {
fun create(
pluginRepository: PluginRepository,
pluginsDir: Path,
diskSpaceSetting: DiskSpaceSetting
): PluginFilesBank {
val sweepPolicy = LruFileSizeSweepPolicy(diskSpaceSetting)
val urlProvider: (PluginInfo) -> URL? = { (it as? Downloadable)?.downloadUrl }
val urlDownloader = UrlDownloader(urlProvider)
val downloadProvider = DownloadProvider(pluginsDir, urlDownloader) { key ->
when (key) {
is UpdateInfo -> getFileNameForMarketplacePlugin(key)
else -> (key.pluginId + "-" + key.version).replaceInvalidFileNameCharacters()
}
}
val fileRepository = FileRepository(
downloadProvider,
sweepPolicy,
"downloaded-plugins"
)
if (pluginRepository is MarketplaceRepository) {
addAlreadyDownloadedPlugins(pluginsDir, pluginRepository, fileRepository)
}
return PluginFilesBank(fileRepository, urlProvider, downloadProvider)
}
private fun getFileNameForMarketplacePlugin(pluginInfo: UpdateInfo): String =
"${pluginInfo.pluginIntId}_${pluginInfo.updateId}"
private fun getPluginIdAndUpdateIdByPath(path: Path): Pair? {
val fileName = path.nameWithoutExtension
val pluginId = fileName.substringBefore("_", "").toIntOrNull() ?: return null
val updateId = fileName.substringAfter("_", "").toIntOrNull() ?: return null
return pluginId to updateId
}
private fun addAlreadyDownloadedPlugins(
pluginsDir: Path,
pluginRepository: MarketplaceRepository,
fileRepository: FileRepository
) {
val pathToPluginIdAndUpdateId = hashMapOf>()
for (path in pluginsDir.listFiles()) {
val pluginIdAndUpdateId = getPluginIdAndUpdateIdByPath(path)
if (pluginIdAndUpdateId != null) {
pathToPluginIdAndUpdateId[path] = pluginIdAndUpdateId
} else {
path.deleteLogged()
}
}
//Batch request many update infos. Much faster than N > 1000 individual requests.
val updateIdToUpdateInfo = pluginRepository.getPluginInfosForManyPluginIdsAndUpdateIds(
pathToPluginIdAndUpdateId.map { it.value }
)
for ((path, pluginIdAndUpdateId) in pathToPluginIdAndUpdateId) {
val updateInfo = updateIdToUpdateInfo[pluginIdAndUpdateId.second]
if (updateInfo != null) {
fileRepository.add(updateInfo, path)
} else {
path.deleteLogged()
}
}
}
}
val downloadStatistics: DownloadStatistics
get() = downloadProvider.downloadStatistics
/**
* Returns a plugin file for the [pluginInfo] that
* can be cached locally or downloaded from the repository.
*
* The result is wrapped into the [FileRepositoryResult] to
* indicate possible outcomes of the fetching.
* In case a [FileRepositoryResult.Found] is returned,
* a file lock is registered for the plugin file to protect it against
* deletion or eviction while the file is used.
*/
override fun getPluginFile(pluginInfo: PluginInfo): PluginFileProvider.Result {
val downloadUrl = urlProvider(pluginInfo)
?: return PluginFileProvider.Result.NotFound("Cannot find plugin $pluginInfo")
if (downloadUrl.protocol == "file") {
val file = FileUtils.toFile(downloadUrl)
return if (file.exists()) {
PluginFileProvider.Result.Found(IdleFileLock(file.toPath()))
} else {
PluginFileProvider.Result.NotFound("Plugin file doesn't exist: $file")
}
}
return with(fileRepository.getFile(pluginInfo)) {
when (this) {
is FileRepositoryResult.Found -> PluginFileProvider.Result.Found(lockedFile)
is FileRepositoryResult.NotFound -> PluginFileProvider.Result.NotFound(reason)
is FileRepositoryResult.Failed -> PluginFileProvider.Result.Failed(reason, error)
}
}
}
/**
* Returns a set of plugins available locally at the moment.
*/
fun getAvailablePluginFiles() = fileRepository.getAvailableFiles()
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy