Skip to content

Commit

Permalink
Merge branch 'release/v1.0.1' into zowe-release/v1.0.1
Browse files Browse the repository at this point in the history
Signed-off-by: Uladzislau <[email protected]>
  • Loading branch information
KUGDev committed Apr 7, 2023
2 parents b01bddb + f5416f6 commit 4cb57a8
Show file tree
Hide file tree
Showing 13 changed files with 246 additions and 117 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -48,12 +48,10 @@ interface ContentSynchronizer {
* Performs synchronization of file with mainframe.
* @param syncProvider instance of [SyncProvider] class that contains necessary data and handler to start synchronize.
* @param progressIndicator indicator to reflect synchronization process (not required).
* @param forceReload flag indicating that the content should be force reloaded.
*/
fun synchronizeWithRemote(
syncProvider: SyncProvider,
progressIndicator: ProgressIndicator? = null,
forceReload: Boolean = false
progressIndicator: ProgressIndicator? = null
)

/**
Expand All @@ -63,6 +61,14 @@ interface ContentSynchronizer {
*/
fun successfulContentStorage(syncProvider: SyncProvider): ByteArray


/**
* Checks if a file needs to be uploaded to MF.
* @param syncProvider instance of [SyncProvider] class that contains the necessary data to check.
* @return true if the upload is needed or false otherwise.
*/
fun isFileUploadNeeded(syncProvider: SyncProvider): Boolean

/**
* Checks if it is possible to synchronize the file with MF.
* Trying to upload file content into MF.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import org.zowe.explorer.dataops.DataOpsManager
import org.zowe.explorer.utils.castOrNull
import org.zowe.explorer.vfs.MFVirtualFile

// TODO: need to rework
/**
* Listener that reacts on document saving to local file system and synchronizes it with mainframe.
* Needed to fully implement auto-save.
Expand All @@ -33,7 +34,7 @@ class MFAutoSaveDocumentListener: BulkFileListener {
return
}
events.forEach {
if (!it.isFromSave) {
if (!it.isFromSave && it.requestor !is MFVirtualFile) {
return
}
val vFile = it.file
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,11 @@ import com.intellij.openapi.progress.ProgressIndicator
import com.intellij.openapi.vfs.VirtualFile
import org.zowe.explorer.dataops.DataOpsManager
import org.zowe.explorer.dataops.attributes.FileAttributes
import org.zowe.explorer.utils.ContentStorage
import org.zowe.explorer.utils.runReadActionInEdtAndWait
import org.zowe.explorer.utils.runWriteActionInEdt
import org.zowe.explorer.utils.runWriteActionInEdtAndWait
import org.zowe.explorer.dataops.attributes.RemoteUssAttributes
import org.zowe.explorer.utils.*
import org.zowe.explorer.dataops.attributes.RemoteUssAttributes
import org.zowe.explorer.editor.DocumentChangeListener
import org.zowe.explorer.editor.FileContentChangeListener
import java.io.IOException
import java.nio.charset.Charset
import java.util.concurrent.ConcurrentHashMap

private const val SUCCESSFUL_CONTENT_STORAGE_NAME_PREFIX = "sync_storage_"
Expand All @@ -37,6 +34,18 @@ abstract class RemoteAttributedContentSynchronizer<FAttributes : FileAttributes>
val dataOpsManager: DataOpsManager
) : ContentSynchronizer {

init {
subscribe(
componentManager = dataOpsManager.componentManager,
topic = FileContentChangeListener.FILE_CONTENT_CHANGED,
handler = object : FileContentChangeListener {
override fun onUpdate(file: VirtualFile) {
needToUpload.add(DocumentedSyncProvider(file))
}
}
)
}

/**
* [FileAttributes] implementation class
*/
Expand All @@ -54,6 +63,7 @@ abstract class RemoteAttributedContentSynchronizer<FAttributes : FileAttributes>
private val handlerToStorageIdMap = ConcurrentHashMap<SyncProvider, Int>()
private val idToBinaryFileMap = ConcurrentHashMap<Int, VirtualFile>()
private val fetchedAtLeastOnce = ConcurrentHashMap.newKeySet<SyncProvider>()
private val needToUpload = ConcurrentHashMap.newKeySet<SyncProvider>()

/**
* Abstract method for fetching content from mainframe.
Expand Down Expand Up @@ -102,8 +112,7 @@ abstract class RemoteAttributedContentSynchronizer<FAttributes : FileAttributes>
*/
override fun synchronizeWithRemote(
syncProvider: SyncProvider,
progressIndicator: ProgressIndicator?,
forceReload: Boolean
progressIndicator: ProgressIndicator?
) {
runCatching {
log.info("Starting synchronization for file ${syncProvider.file.name}.")
Expand All @@ -126,6 +135,7 @@ abstract class RemoteAttributedContentSynchronizer<FAttributes : FileAttributes>
if (!wasFetchedBefore(syncProvider)) {
log.info("Setting initial content for file ${syncProvider.file.name}")
runWriteActionInEdtAndWait { syncProvider.putInitialContent(adaptedFetchedBytes) }
runReadActionInEdtAndWait { syncProvider.getDocument()?.addDocumentListener(DocumentChangeListener()) }
changeFileEncodingTo(syncProvider.file, currentCharset)
initLineSeparator(syncProvider.file)
successfulStatesStorage.writeStream(recordId).use { it.write(adaptedFetchedBytes) }
Expand All @@ -139,7 +149,7 @@ abstract class RemoteAttributedContentSynchronizer<FAttributes : FileAttributes>
val doUploadContent =
syncProvider.saveStrategy.decide(syncProvider.file, oldStorageBytes, adaptedFetchedBytes)

if (doUploadContent && !forceReload) {
if (doUploadContent && isFileUploadNeeded(syncProvider)) {
log.info("Save strategy decided to forcefully update file content on mainframe.")
val newContentPrepared = contentAdapter.prepareContentToMainframe(fileContent, syncProvider.file)
runWriteActionInEdtAndWait { syncProvider.loadNewContent(newContentPrepared) }
Expand All @@ -152,8 +162,8 @@ abstract class RemoteAttributedContentSynchronizer<FAttributes : FileAttributes>
}
} else { /*do nothing*/
}
syncCharsetsIfNeeded(syncProvider.file, currentCharset)
}
needToUpload.remove(syncProvider)
}
.onSuccess {
syncProvider.onSyncSuccess()
Expand All @@ -171,6 +181,13 @@ abstract class RemoteAttributedContentSynchronizer<FAttributes : FileAttributes>
return recordId?.let { successfulStatesStorage.getBytes(it) } ?: ByteArray(0)
}

/**
* Base implementation of [ContentSynchronizer.isFileUploadNeeded] method for each content synchronizer.
*/
override fun isFileUploadNeeded(syncProvider: SyncProvider): Boolean {
return needToUpload.firstOrNull { syncProvider == it } != null
}

/**
* Base implementation of [ContentSynchronizer.isFileSyncPossible] method for each content synchronizer.
*/
Expand All @@ -183,15 +200,4 @@ abstract class RemoteAttributedContentSynchronizer<FAttributes : FileAttributes>
}
return true
}

/**
* Synchronizes the current charset with the file charset if needed.
* @param file virtual file to sync.
* @param currentCharset current content charset.
*/
private fun syncCharsetsIfNeeded(file: VirtualFile, currentCharset: Charset) {
if (currentCharset != file.charset) {
changeFileEncodingTo(file, currentCharset)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -99,9 +99,11 @@ class SyncAction : DumbAwareAction() {
val syncProvider = DocumentedSyncProvider(file)
val currentContent = runReadActionInEdtAndWait { syncProvider.retrieveCurrentContent() }
val previousContent = contentSynchronizer?.successfulContentStorage(syncProvider)
val needToUpload = contentSynchronizer?.isFileUploadNeeded(syncProvider) == true
e.presentation.isEnabledAndVisible = file.isWritable
&& !service<ConfigService>().isAutoSyncEnabled
&& !(currentContent contentEquals previousContent)
&& needToUpload
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,9 @@

package org.zowe.explorer.dataops.content.synchronizer

import com.intellij.icons.AllIcons
import com.intellij.openapi.fileEditor.impl.LoadTextUtil
import com.intellij.openapi.project.Project
import com.intellij.openapi.ui.Messages
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.openapi.vfs.encoding.EncodingManager
import org.zowe.explorer.utils.runWriteActionInEdtAndWait
import java.nio.charset.Charset

Expand All @@ -29,11 +26,6 @@ const val LF_LINE_SEPARATOR: String = "\n"

const val CR_LINE_SEPARATOR: String = "\r"

enum class ContentEncodingMode(val value: String) {
CONVERT("CONVERT"),
RELOAD("RELOAD")
}

/** Remove string's last blank line */
fun String.removeLastNewLine(): String {
return if (endsWith(NEW_LINE)) {
Expand All @@ -56,70 +48,14 @@ fun ByteArray.addNewLine(): ByteArray {
return this.plus(NEW_LINE.toByteArray())
}

/**
* Dialog for selecting content encoding mode (reload, convert or cancel).
* @param fileName name of the file.
* @param encodingName name of the file encoding.
* @param project the project to show dialog.
* @return content encoding mode [ContentEncodingMode] or null.
*/
fun showReloadConvertCancelDialog(fileName: String, encodingName: String, project: Project?): ContentEncodingMode? {
val result = Messages.showDialog(
project,
"The encoding you've chosen ('${encodingName}') may change the contents of '${fileName}'.<br>"
+ "Do you want to<br>"
+ "1. <b>Reload</b> the file from remote in the new encoding '${encodingName}' and overwrite contents " +
"(may not display correctly) or<br>"
+ "2. <b>Convert</b> the text and overwrite file in the new encoding?<br>",
"${fileName}: Reload or Convert to ${encodingName}",
arrayOf("Reload", "Convert", "Cancel"),
2,
AllIcons.General.QuestionDialog,
null
)
return when (result) {
0 -> ContentEncodingMode.RELOAD
1 -> ContentEncodingMode.CONVERT
else -> null
}
}

/**
* Dialog for selecting content encoding mode (reload or cancel).
* @param fileName name of the file.
* @param encodingName name of the file encoding.
* @param project the project to show dialog.
* @return content encoding mode [ContentEncodingMode] or null.
*/
fun showReloadCancelDialog(fileName: String, encodingName: String, project: Project?): ContentEncodingMode? {
val result = Messages.showDialog(
project,
"The encoding you've chosen ('${encodingName}') may change the contents of '${fileName}'.<br>"
+ "Do you want to <b>Reload</b> the file from remote in the new encoding '${encodingName}' and overwrite contents " +
"(may not display correctly).<br>",
"${fileName}: Reload to ${encodingName}",
arrayOf("Reload", "Cancel"),
1,
AllIcons.General.QuestionDialog,
null
)
return when (result) {
0 -> ContentEncodingMode.RELOAD
else -> null
}
}

/** Initializes the line separator to the contents of the file (by default "\n"). */
fun initLineSeparator(file: VirtualFile) {
if (file.contentsToByteArray().isEmpty()) {
file.detectedLineSeparator = LF_LINE_SEPARATOR
}
file.detectedLineSeparator = LoadTextUtil.detectLineSeparator(file, true)
}

/** Changes the file encoding to the specified one. */
fun changeFileEncodingTo(file: VirtualFile, charset: Charset) {
fun initLineSeparator(file: VirtualFile, project: Project? = null) {
runWriteActionInEdtAndWait {
EncodingManager.getInstance().setEncoding(file, charset)
LoadTextUtil.changeLineSeparators(
project,
file,
LoadTextUtil.detectLineSeparator(file, true) ?: LF_LINE_SEPARATOR,
file
)
}
}
50 changes: 50 additions & 0 deletions src/main/kotlin/org/zowe/explorer/editor/DocumentChangeListener.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
/*
* This program and the accompanying materials are made available under the terms of the
* Eclipse Public License v2.0 which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-v20.html
*
* SPDX-License-Identifier: EPL-2.0
*
* Copyright IBA Group 2020
*/

package org.zowe.explorer.editor

import com.intellij.openapi.editor.event.DocumentEvent
import com.intellij.openapi.editor.event.DocumentListener
import com.intellij.openapi.fileEditor.FileDocumentManager
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.util.messages.Topic
import org.zowe.explorer.dataops.DataOpsManager
import org.zowe.explorer.utils.isBeingEditingNow
import org.zowe.explorer.utils.sendTopic
import org.zowe.explorer.vfs.MFVirtualFile

/**
* File content change listener that provides information about whether the file has been modified by the user.
*/
interface FileContentChangeListener {

companion object {
@JvmField
val FILE_CONTENT_CHANGED = Topic.create("fileContentChanged", FileContentChangeListener::class.java)
}

fun onUpdate(file: VirtualFile)
}

/**
* Document change listener that listens for all document changes and
* sends an event about it if the user has made changes.
*/
class DocumentChangeListener: DocumentListener {

override fun documentChanged(event: DocumentEvent) {
val file = FileDocumentManager.getInstance().getFile(event.document)
if (file is MFVirtualFile && file.isBeingEditingNow()) {
sendTopic(FileContentChangeListener.FILE_CONTENT_CHANGED, DataOpsManager.instance.componentManager)
.onUpdate(file)
}
super.documentChanged(event)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,8 @@ class FileEditorEventsListener : FileEditorManagerListener.Before {
val contentSynchronizer = service<DataOpsManager>().getContentSynchronizer(file)
val currentContent = runReadActionInEdtAndWait { syncProvider.retrieveCurrentContent() }
val previousContent = contentSynchronizer?.successfulContentStorage(syncProvider)
if (!(currentContent contentEquals previousContent)) {
val needToUpload = contentSynchronizer?.isFileUploadNeeded(syncProvider) == true
if (!(currentContent contentEquals previousContent) && needToUpload) {
if (showSyncOnCloseDialog(file.name, source.project)) {
runModalTask(
title = "Syncing ${file.name}",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,21 +47,17 @@ class FileSelectionChangeListener: FileEditorManagerListener {
extensionPoint.extensionList.none { it.id == StatusBar.StandardWidgets.LINE_SEPARATOR_PANEL } &&
lineSeparatorWidgetFactories.none { it.canBeEnabledOn(statusBar) }
) {
val widget = statusBar.getWidget(StatusBar.StandardWidgets.LINE_SEPARATOR_PANEL) ?: return
val factory = LineSeparatorWidgetFactory()
val order = LoadingOrder.readOrder("after positionWidget")
extensionPoint.registerExtension(factory, order, widget)
extensionPoint.registerExtension(LineSeparatorWidgetFactory(), order) {}
}
val encodingPanelWidgetFactories =
extensionPoint.extensionList.mapNotNull { it.castOrNull<EncodingPanelWidgetFactory>() }
if (
extensionPoint.extensionList.none { it.id == StatusBar.StandardWidgets.ENCODING_PANEL } &&
encodingPanelWidgetFactories.none { it.canBeEnabledOn(statusBar) }
) {
val widget = statusBar.getWidget(StatusBar.StandardWidgets.ENCODING_PANEL) ?: return
val factory = EncodingPanelWidgetFactory()
val order = LoadingOrder.readOrder("after lineSeparatorWidget, before powerStatus")
extensionPoint.registerExtension(factory, order, widget)
extensionPoint.registerExtension(EncodingPanelWidgetFactory(), order) {}
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,11 @@ import org.zowe.explorer.dataops.attributes.RemoteJobAttributes
import org.zowe.explorer.dataops.content.synchronizer.DEFAULT_TEXT_CHARSET
import org.zowe.explorer.dataops.content.synchronizer.DocumentedSyncProvider
import org.zowe.explorer.dataops.content.synchronizer.SaveStrategy
import org.zowe.explorer.dataops.content.synchronizer.changeFileEncodingTo
import org.zowe.explorer.dataops.operations.jobs.BasicGetJclRecordsParams
import org.zowe.explorer.dataops.operations.jobs.GetJclRecordsOperation
import org.zowe.explorer.explorer.ui.JES_EXPLORER_VIEW
import org.zowe.explorer.explorer.ui.JobNode
import org.zowe.explorer.utils.changeFileEncodingTo
import org.zowe.explorer.dataops.operations.jobs.*
import org.zowe.explorer.explorer.ui.*
import org.zowe.explorer.utils.runWriteActionInEdtAndWait
Expand Down
Loading

0 comments on commit 4cb57a8

Please sign in to comment.