blob: 6142b696ff24a317413727ac763218112cabf5c7 [file] [log] [blame]
/*
* Copyright 2000-2014 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.intellij.openapi.options;
import com.intellij.openapi.application.Application;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.application.ModalityState;
import com.intellij.openapi.application.impl.ApplicationImpl;
import com.intellij.openapi.components.RoamingType;
import com.intellij.openapi.components.impl.stores.StorageUtil;
import com.intellij.openapi.components.impl.stores.StreamProvider;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.editor.DocumentRunnable;
import com.intellij.openapi.ui.Messages;
import com.intellij.openapi.util.*;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.vfs.LocalFileSystem;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.openapi.vfs.VirtualFileAdapter;
import com.intellij.openapi.vfs.VirtualFileEvent;
import com.intellij.openapi.vfs.newvfs.NewVirtualFile;
import com.intellij.util.Alarm;
import com.intellij.util.SmartList;
import com.intellij.util.UniqueFileNamesProvider;
import com.intellij.util.containers.HashSet;
import com.intellij.util.text.UniqueNameGenerator;
import gnu.trove.THashMap;
import gnu.trove.THashSet;
import org.jdom.Document;
import org.jdom.Element;
import org.jdom.JDOMException;
import org.jdom.Parent;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.io.File;
import java.io.IOException;
import java.util.*;
public class SchemesManagerImpl<T extends Scheme, E extends ExternalizableScheme> extends AbstractSchemesManager<T, E> {
private static final Logger LOG = Logger.getInstance(SchemesManagerFactoryImpl.class);
@NonNls private static final String DEFAULT_EXT = ".xml";
private final Set<String> myDeletedNames = new LinkedHashSet<String>();
private final Set<String> myFilesToDelete = new HashSet<String>();
@NonNls private static final String SHARED_SCHEME = "shared-scheme";
@NonNls private static final String SHARED_SCHEME_ORIGINAL = "shared-scheme-original";
private static final String NAME = "name";
@NonNls private static final String ORIGINAL_SCHEME_PATH = "original-scheme-path";
private final String myFileSpec;
private final SchemeProcessor<E> myProcessor;
private final RoamingType myRoamingType;
@NonNls private static final String SCHEME_LOCAL_COPY = "scheme-local-copy";
@NonNls private static final String DELETED_XML = "__deleted.xml";
private final StreamProvider myProvider;
private final File myBaseDir;
private VirtualFile myVFSBaseDir;
private static final String DESCRIPTION = "description";
private static final boolean EXPORT_IS_AVAILABLE = false;
private static final String USER = "user";
private boolean myListenerAdded = false;
private Alarm myRefreshAlarm;
private String mySchemeExtension = DEFAULT_EXT;
private boolean myUpgradeExtension = false;
public SchemesManagerImpl(@NotNull String fileSpec,
@NotNull SchemeProcessor<E> processor,
@NotNull RoamingType roamingType,
@Nullable StreamProvider provider,
@NotNull File baseDir) {
myFileSpec = fileSpec;
myProcessor = processor;
myRoamingType = roamingType;
myProvider = provider;
myBaseDir = baseDir;
if (processor instanceof SchemeExtensionProvider) {
mySchemeExtension = ((SchemeExtensionProvider)processor).getSchemeExtension();
myUpgradeExtension = ((SchemeExtensionProvider)processor).isUpgradeNeeded();
}
//noinspection ResultOfMethodCallIgnored
myBaseDir.mkdirs();
addVFSListener();
}
@Override
@NotNull
public Collection<E> loadSchemes() {
if (myVFSBaseDir != null) {
return doLoad();
}
return Collections.emptyList();
}
private Collection<E> doLoad() {
myDeletedNames.addAll(readDeletedSchemeNames());
Map<String, E> read = new LinkedHashMap<String, E>();
for (E e : readSchemesFromFileSystem()) {
read.put(e.getName(), e);
}
for (E e : readSchemesFromProviders()) {
read.put(e.getName(), e);
}
Collection<E> result = read.values();
initLoadedSchemes(result);
return result;
}
private void addVFSListener() {
Application app = ApplicationManager.getApplication();
if (app == null || myListenerAdded) return;
final LocalFileSystem system = LocalFileSystem.getInstance();
myVFSBaseDir = system.findFileByIoFile(myBaseDir);
if (myVFSBaseDir == null && !app.isUnitTestMode() && !app.isHeadlessEnvironment()) {
myRefreshAlarm = new Alarm(Alarm.ThreadToUse.SWING_THREAD);
myRefreshAlarm.addRequest(new Runnable(){
@Override
public void run() {
ensureVFSBaseDir();
}
}, 60 * 1000, ModalityState.NON_MODAL);
}
system.addVirtualFileListener(new VirtualFileAdapter() {
@Override
public void contentsChanged(@NotNull final VirtualFileEvent event) {
onFileContentChanged(event);
}
@Override
public void fileCreated(@NotNull final VirtualFileEvent event) {
VirtualFile file = event.getFile();
if (event.getRequestor() == null && isFileUnder(file, myVFSBaseDir) && !myInsideSave) {
List<E> read = new ArrayList<E>();
readSchemeFromFile(read, file, true);
if (!read.isEmpty()) {
E readScheme = read.get(0);
myProcessor.initScheme(readScheme);
myProcessor.onSchemeAdded(readScheme);
}
}
}
@Override
public void fileDeleted(@NotNull final VirtualFileEvent event) {
VirtualFile parent = event.getParent();
if (event.getRequestor() == null && parent != null && parent.equals(myVFSBaseDir) && !myInsideSave) {
File ioFile = new File(event.getFileName());
E scheme = findSchemeFor(ioFile.getName());
T oldCurrentScheme = null;
if (scheme != null) {
oldCurrentScheme = getCurrentScheme();
@SuppressWarnings("unchecked") T t = (T)scheme;
removeScheme(t);
myProcessor.onSchemeDeleted(scheme);
}
T newCurrentScheme = getCurrentScheme();
if (oldCurrentScheme != null && newCurrentScheme == null) {
if (!mySchemes.isEmpty()) {
setCurrentSchemeName(mySchemes.get(0).getName());
newCurrentScheme = getCurrentScheme();
}
}
if (oldCurrentScheme != newCurrentScheme) {
myProcessor.onCurrentSchemeChanged(oldCurrentScheme);
}
}
}
});
myListenerAdded = true;
}
private void onFileContentChanged(final VirtualFileEvent event) {
VirtualFile file = event.getFile();
if (event.getRequestor() == null && isFileUnder(file, myVFSBaseDir) && !myInsideSave) {
File ioFile = new File(file.getPath());
E scheme = findSchemeFor(ioFile.getName());
ArrayList<E> read = new ArrayList<E>();
T oldCurrentScheme = null;
if (scheme != null) {
oldCurrentScheme = getCurrentScheme();
@SuppressWarnings("unchecked") T t = (T)scheme;
removeScheme(t);
myProcessor.onSchemeDeleted(scheme);
}
readSchemeFromFile(read, file, true);
if (!read.isEmpty()) {
E readScheme = read.get(0);
myProcessor.initScheme(readScheme);
myProcessor.onSchemeAdded(readScheme);
T newCurrentScheme = getCurrentScheme();
if (oldCurrentScheme != null && newCurrentScheme == null) {
setCurrentSchemeName(readScheme.getName());
newCurrentScheme = getCurrentScheme();
}
if (oldCurrentScheme != newCurrentScheme) {
myProcessor.onCurrentSchemeChanged(oldCurrentScheme);
}
}
}
}
private E findSchemeFor(@NotNull String ioFileName) {
for (T scheme : mySchemes) {
if (scheme instanceof ExternalizableScheme) {
if (ioFileName.equals(((ExternalizableScheme)scheme).getExternalInfo().getCurrentFileName() + mySchemeExtension)) {
//noinspection CastConflictsWithInstanceof,unchecked
return (E)scheme;
}
}
}
return null;
}
private static boolean isFileUnder(final VirtualFile file, final VirtualFile baseDirFile) {
return file.getParent().equals(baseDirFile);
}
private Collection<String> readDeletedSchemeNames() {
Collection<String> result = new THashSet<String>();
if (myProvider == null || !myProvider.isEnabled()) {
return result;
}
try {
Document deletedNameDoc = StorageUtil.loadDocument(myProvider.loadContent(getFileFullPath(DELETED_XML), myRoamingType));
if (deletedNameDoc != null) {
for (Element child : deletedNameDoc.getRootElement().getChildren()) {
String deletedSchemeName = child.getAttributeValue("name");
if (deletedSchemeName != null) {
result.add(deletedSchemeName);
}
}
}
}
catch (Exception e) {
LOG.debug(e);
}
return result;
}
private void initLoadedSchemes(@NotNull Collection<E> read) {
for (E scheme : read) {
myProcessor.initScheme(scheme);
checkCurrentScheme(scheme);
}
}
@NotNull
private Collection<E> readSchemesFromProviders() {
if (myProvider == null || !myProvider.isEnabled()) {
return Collections.emptyList();
}
Collection<E> result = new SmartList<E>();
for (String subPath : myProvider.listSubFiles(myFileSpec, myRoamingType)) {
if (!subPath.equals(DELETED_XML)) {
try {
final Document subDocument = StorageUtil.loadDocument(myProvider.loadContent(getFileFullPath(subPath), myRoamingType));
if (subDocument != null) {
E scheme = readScheme(subDocument);
boolean fileRenamed = false;
assert scheme != null;
T existing = findSchemeByName(scheme.getName());
if (existing != null && existing instanceof ExternalizableScheme) {
String currentFileName = ((ExternalizableScheme)existing).getExternalInfo().getCurrentFileName();
if (currentFileName != null && !currentFileName.equals(subPath)) {
deleteServerFiles(subPath);
subPath = currentFileName;
fileRenamed = true;
}
}
String fileName = checkFileNameIsFree(subPath, scheme.getName());
if (!fileRenamed && !fileName.equals(subPath)) {
deleteServerFiles(subPath);
}
loadScheme(scheme, false, fileName);
result.add(scheme);
}
}
catch (Exception e) {
LOG.info("Cannot load data from stream provider: " + e.getLocalizedMessage());
}
}
}
return result;
}
private VirtualFile ensureFileText(final String fileName, final byte[] text) throws IOException {
return ApplicationManager.getApplication().runWriteAction(new ThrowableComputable<VirtualFile, IOException>() {
@Override
public VirtualFile compute() throws IOException {
VirtualFile file = myVFSBaseDir.findChild(fileName);
if (file == null) file = myVFSBaseDir.createChildData(SchemesManagerImpl.this, fileName);
if (!Arrays.equals(file.contentsToByteArray(), text)) {
file.setBinaryContent(text);
}
return file;
}
});
}
@NotNull
private String checkFileNameIsFree(@NotNull String subPath, @NotNull String schemeName) {
for (Scheme scheme : mySchemes) {
if (scheme instanceof ExternalizableScheme) {
String name = ((ExternalizableScheme)scheme).getExternalInfo().getCurrentFileName();
if (name != null &&
!schemeName.equals(scheme.getName()) &&
subPath.length() == (name.length() + mySchemeExtension.length()) &&
subPath.startsWith(name) &&
subPath.endsWith(mySchemeExtension)) {
return UniqueNameGenerator.generateUniqueName(UniqueFileNamesProvider.convertName(schemeName), collectAllFileNames());
/*VirtualFile oldFile = myVFSBaseDir.findChild(subPath);
if (oldFile != null) {
oldFile.copy(this, myVFSBaseDir, uniqueFileName + EXT);
}
externalInfo.setCurrentFileName(uniqueFileName);*/
}
}
}
return subPath;
}
@NotNull
private Collection<String> collectAllFileNames() {
Set<String> result = new THashSet<String>();
for (T scheme : mySchemes) {
if (scheme instanceof ExternalizableScheme) {
ExternalInfo externalInfo = ((ExternalizableScheme)scheme).getExternalInfo();
if (externalInfo.getCurrentFileName() != null) {
result.add(externalInfo.getCurrentFileName());
}
}
}
return result;
}
private void loadScheme(final E scheme, boolean forceAdd, final String name) {
if (scheme != null && (!myDeletedNames.contains(scheme.getName()) || forceAdd)) {
T existing = findSchemeByName(scheme.getName());
if (existing != null) {
if (!Comparing.equal(existing.getClass(), scheme.getClass())) {
LOG.warn("'" + scheme.getName() + "' " + existing.getClass().getSimpleName() + " replaced with " + scheme.getClass().getSimpleName());
}
mySchemes.remove(existing);
if (isExternalizable(existing)) {
@SuppressWarnings("unchecked") E e = (E)existing;
myProcessor.onSchemeDeleted(e);
}
}
@SuppressWarnings("unchecked") T t = (T)scheme;
addNewScheme(t, true);
saveFileName(name, scheme);
scheme.getExternalInfo().setPreviouslySavedName(scheme.getName());
}
}
private Collection<E> readSchemesFromFileSystem() {
Collection<E> result = new ArrayList<E>();
VirtualFile[] files = myVFSBaseDir.getChildren();
if (files != null) {
for (VirtualFile file : files) {
readSchemeFromFile(result, file, false);
}
}
else {
ApplicationManager.getApplication().invokeLater(
new Runnable(){
@Override
public void run() {
String msg = "Cannot read directory: " + myBaseDir.getAbsolutePath() + " directory does not exist";
Messages.showErrorDialog(msg, "Read Settings");
}
}
);
}
return result;
}
private boolean canRead(VirtualFile file) {
if (!file.isDirectory()) {
String ext = "." + file.getExtension();
if (DEFAULT_EXT.equalsIgnoreCase(ext) && !DEFAULT_EXT.equals(mySchemeExtension) && myUpgradeExtension) {
return myVFSBaseDir.findChild(file.getName() + mySchemeExtension) == null;
}
else if (mySchemeExtension.equalsIgnoreCase(ext)) {
return true;
}
}
return false;
}
private void readSchemeFromFile(final Collection<E> result, final VirtualFile file, final boolean forceAdd) {
if (canRead(file)) {
try {
final Document document;
try {
document = JDOMUtil.loadDocument(file.getInputStream());
}
catch (JDOMException e) {
try {
File initialIOFile = new File(myBaseDir, file.getName());
if (initialIOFile.isFile()) {
FileUtil.copy(initialIOFile, new File(myBaseDir, file.getName() + ".copy"));
}
}
catch (IOException e1) {
LOG.info(e1);
//ignore
}
LOG.info("Error reading file " + file.getPath() + ": " + e.getLocalizedMessage());
throw e;
}
final E scheme = readScheme(document);
if (scheme != null) {
loadScheme(scheme, forceAdd, file.getName());
result.add(scheme);
}
}
catch (final Exception e) {
ApplicationManager.getApplication().invokeLater(
new Runnable(){
@Override
public void run() {
String msg = "Cannot read scheme " + file.getName() + " from '" + myFileSpec + "': " + e.getLocalizedMessage();
LOG.info(msg, e);
Messages.showErrorDialog(msg, "Load Settings");
}
}
);
}
}
}
@Nullable
private E readScheme(final Document subDocument) throws InvalidDataException, IOException, JDOMException {
Element rootElement = subDocument.getRootElement();
if (rootElement.getName().equals(SHARED_SCHEME)) {
String schemeName = rootElement.getAttributeValue(NAME);
String schemePath = rootElement.getAttributeValue(ORIGINAL_SCHEME_PATH);
Document sharedDocument = loadGlobalScheme(schemePath);
if (sharedDocument != null) {
E result = readScheme(sharedDocument);
if (result != null) {
renameScheme(result, schemeName);
result.getExternalInfo().setOriginalPath(schemePath);
result.getExternalInfo().setIsImported(true);
}
return result;
}
else {
Element localCopyElement = subDocument.getRootElement().getChild(SCHEME_LOCAL_COPY);
if (localCopyElement != null) {
Element firstChild = localCopyElement.getChildren().get(0);
return myProcessor.readScheme(new Document(firstChild.clone()));
}
else {
return null;
}
}
}
else if (rootElement.getName().equals(SHARED_SCHEME_ORIGINAL)) {
SharedSchemeData schemeData = unwrap(subDocument);
E scheme = myProcessor.readScheme(schemeData.original);
if (scheme != null) {
renameScheme(scheme, schemeData.name);
}
return scheme;
}
else {
return myProcessor.readScheme(subDocument);
}
}
@Nullable
private static Document loadGlobalScheme(final String schemePath) throws IOException {
StreamProvider provider = getProvider();
return provider != null && provider.isEnabled() ? StorageUtil.loadDocument(provider.loadContent(schemePath, getRoamingType(provider))) : null;
}
private void saveFileName(String fileName, final E schemeKey) {
if (StringUtil.endsWithIgnoreCase(fileName, mySchemeExtension)) {
fileName = fileName.substring(0, fileName.length() - mySchemeExtension.length());
}
else if (StringUtil.endsWithIgnoreCase(fileName, DEFAULT_EXT)) {
fileName = fileName.substring(0, fileName.length() - DEFAULT_EXT.length());
}
schemeKey.getExternalInfo().setCurrentFileName(fileName);
}
private static long computeHashValue(Parent element) {
return JDOMUtil.getTreeHash(element instanceof Element ? (Element)element : ((Document)element).getRootElement());
}
@Nullable
private org.jdom.Parent writeSchemeToDocument(@NotNull E scheme) throws WriteExternalException {
if (isShared(scheme)) {
String originalPath = scheme.getExternalInfo().getOriginalPath();
if (originalPath != null) {
Element root = new Element(SHARED_SCHEME);
root.setAttribute(NAME, scheme.getName());
root.setAttribute(ORIGINAL_SCHEME_PATH, originalPath);
Element localCopy = new Element(SCHEME_LOCAL_COPY);
localCopy.addContent(getClone(myProcessor.writeScheme(scheme)));
root.addContent(localCopy);
return root;
}
else {
return null;
}
}
else {
return myProcessor.writeScheme(scheme);
}
}
@NotNull
private static Element getClone(@NotNull Parent result) {
return (result instanceof Element ? (Element)result : ((Document)result).getRootElement()).clone();
}
public void updateConfigFilesFromStreamProviders() {
}
private static class SharedSchemeData {
@NotNull private final Document original;
@NotNull private final String name;
private final String user;
private final String description;
private SharedSchemeData(@NotNull Document original, @NotNull String name, String user, String description) {
this.original = original;
this.name = name;
this.user = user;
this.description = description;
}
}
@Override
@NotNull
public Collection<SharedScheme<E>> loadSharedSchemes(Collection<T> currentSchemeList) {
StreamProvider provider = getProvider();
if (provider == null || !provider.isEnabled()) {
return Collections.emptyList();
}
Collection<String> names = new THashSet<String>(getAllSchemeNames(currentSchemeList));
Map<String, SharedScheme<E>> result = new THashMap<String, SharedScheme<E>>();
for (String subPath : provider.listSubFiles(myFileSpec, getRoamingType(provider))) {
try {
final Document subDocument = StorageUtil.loadDocument(provider.loadContent(getFileFullPath(subPath), getRoamingType(provider)));
if (subDocument != null) {
SharedSchemeData original = unwrap(subDocument);
final E scheme = myProcessor.readScheme(original.original);
if (!alreadyShared(subPath, currentSchemeList)) {
String schemeName = original.name;
String uniqueName = UniqueNameGenerator.generateUniqueName("[shared] " + schemeName, names);
renameScheme(scheme, uniqueName);
schemeName = uniqueName;
scheme.getExternalInfo().setOriginalPath(getFileFullPath(subPath));
scheme.getExternalInfo().setIsImported(true);
result.put(schemeName, new SharedScheme<E>(original.user == null ? "unknown" : original.user, original.description, scheme));
}
}
}
catch (Exception e) {
LOG.debug("Cannot load data from IDEAServer: " + e.getLocalizedMessage());
}
}
for (SharedScheme<E> t : result.values()) {
myProcessor.initScheme(t.getScheme());
}
return result.values();
}
@NotNull
private static SharedSchemeData unwrap(@NotNull Document subDocument) {
Element rootElement = subDocument.getRootElement();
SharedSchemeData result;
String name = rootElement.getAttributeValue(NAME);
if (rootElement.getName().equals(SHARED_SCHEME_ORIGINAL)) {
String description = rootElement.getAttributeValue(DESCRIPTION);
String user = rootElement.getAttributeValue(USER);
Document original = new Document(rootElement.getChildren().iterator().next().clone());
result = new SharedSchemeData(original, name, user, description);
}
else {
result = new SharedSchemeData(subDocument, name, null, null);
}
return result;
}
private boolean alreadyShared(final String subPath, final Collection<T> currentSchemeList) {
for (T t : currentSchemeList) {
if (t instanceof ExternalizableScheme) {
ExternalInfo info = ((ExternalizableScheme)t).getExternalInfo();
if (info.isIsImported()) {
if (getFileFullPath(subPath).equals(info.getOriginalPath())) {
return true;
}
}
}
}
return false;
}
private String getFileFullPath(@NotNull String subPath) {
return myFileSpec + '/' + subPath;
}
@SuppressWarnings("deprecation")
@Override
public void exportScheme(@NotNull final E scheme, final String name, final String description) throws WriteExternalException, IOException {
StreamProvider provider = getProvider();
if (provider == null) {
return;
}
Parent document = myProcessor.writeScheme(scheme);
if (document != null) {
String fileSpec = getFileFullPath(UniqueFileNamesProvider.convertName(scheme.getName())) + mySchemeExtension;
if (!provider.isApplicable(fileSpec, getRoamingType(provider))) {
return;
}
Element wrapped = wrap(document, name, description);
if (provider instanceof CurrentUserHolder) {
wrapped = wrapped.clone();
String userName = ((CurrentUserHolder)provider).getCurrentUserName();
if (userName != null) {
wrapped.setAttribute(USER, userName);
}
}
StorageUtil.doSendContent(provider, fileSpec, wrapped, getRoamingType(provider), false);
}
}
@SuppressWarnings("deprecation")
@NotNull
private static RoamingType getRoamingType(@NotNull StreamProvider provider) {
// for deprecated old stream we use GLOBAL as before to preserve backward compatibility
return provider instanceof CurrentUserHolder ? RoamingType.GLOBAL : RoamingType.PER_USER;
}
@NotNull
private static Element wrap(@NotNull Parent original, @NotNull String name, @NotNull String description) {
Element sharedElement = new Element(SHARED_SCHEME_ORIGINAL);
sharedElement.setAttribute(NAME, name);
sharedElement.setAttribute(DESCRIPTION, description);
sharedElement.addContent(getClone(original));
return sharedElement;
}
@Override
public boolean isImportAvailable() {
return getProvider() != null;
}
@Nullable
private static StreamProvider getProvider() {
StreamProvider provider = ((ApplicationImpl)ApplicationManager.getApplication()).getStateStore().getStateStorageManager().getStreamProvider();
return provider == null || !provider.isEnabled() ? null : provider;
}
@Override
public boolean isExportAvailable() {
return EXPORT_IS_AVAILABLE;
}
@Override
public boolean isShared(final Scheme scheme) {
return scheme instanceof ExternalizableScheme && ((ExternalizableScheme)scheme).getExternalInfo().isIsImported();
}
@Override
public void save() throws WriteExternalException {
if (myRefreshAlarm != null) {
myRefreshAlarm.cancelAllRequests();
myRefreshAlarm = null;
}
if (myVFSBaseDir == null) {
ensureVFSBaseDir();
}
if (myVFSBaseDir != null) {
doSave();
}
}
private void ensureVFSBaseDir() {
//noinspection ResultOfMethodCallIgnored
myBaseDir.mkdirs();
ApplicationManager.getApplication().runWriteAction(new DocumentRunnable.IgnoreDocumentRunnable(){
@Override
public void run() {
VirtualFile dir = LocalFileSystem.getInstance().refreshAndFindFileByIoFile(myBaseDir);
myVFSBaseDir = dir;
if (dir != null) {
dir.getChildren();
((NewVirtualFile)dir).markDirtyRecursively();
dir.refresh(false, true);
}
}
});
}
private boolean myInsideSave = false;
private void doSave() throws WriteExternalException {
myInsideSave = true;
try {
ApplicationManager.getApplication().runWriteAction(new DocumentRunnable.IgnoreDocumentRunnable() {
@Override
public void run() {
((NewVirtualFile)myVFSBaseDir).markDirtyRecursively();
myVFSBaseDir.refresh(false, true);
}
});
final Collection<T> schemes = getAllSchemes();
//noinspection ResultOfMethodCallIgnored
myBaseDir.mkdirs();
final UniqueFileNamesProvider fileNameProvider = new UniqueFileNamesProvider();
reserveUsingFileNames(schemes, fileNameProvider);
final WriteExternalException[] ex = new WriteExternalException[1];
ApplicationManager.getApplication().runWriteAction(new DocumentRunnable.IgnoreDocumentRunnable() {
@Override
public void run() {
deleteFilesFromDeletedSchemes();
try {
saveSchemes(schemes, fileNameProvider);
}
catch (WriteExternalException e) {
ex[0] = e;
}
}
});
if (ex[0] != null) {
throw ex[0];
}
if (myDeletedNames.isEmpty()) {
deleteServerFiles(DELETED_XML);
}
else if (myProvider != null && myProvider.isEnabled()) {
StorageUtil.sendContent(myProvider, getFileFullPath(DELETED_XML), createDeletedElement(), myRoamingType, true);
}
}
finally {
myInsideSave = false;
}
}
@Override
public File getRootDirectory() {
return myBaseDir;
}
private void deleteFilesFromDeletedSchemes() {
for (String deletedName : myFilesToDelete) {
deleteLocalAndServerFiles(deletedName + mySchemeExtension);
if (!DEFAULT_EXT.equals(mySchemeExtension)) {
deleteLocalAndServerFiles(deletedName + DEFAULT_EXT);
}
}
myFilesToDelete.clear();
}
private void deleteLocalAndServerFiles(final String fileName) {
VirtualFile file = myVFSBaseDir.findChild(fileName);
if (file != null) {
try {
file.delete(this);
}
catch (IOException e) {
LOG.info("Cannot delete file " + file.getPath() + ": " + e.getLocalizedMessage());
}
}
deleteServerFiles(fileName);
}
private void deleteServerFiles(@NotNull String path) {
if (myProvider != null && myProvider.isEnabled()) {
StorageUtil.delete(myProvider, getFileFullPath(path), myRoamingType);
}
}
private void saveSchemes(final Collection<T> schemes, final UniqueFileNamesProvider fileNameProvider) throws WriteExternalException {
for (T scheme : schemes) {
if (isExternalizable(scheme)) {
@SuppressWarnings("unchecked") final E eScheme = (E)scheme;
eScheme.getExternalInfo().setPreviouslySavedName(eScheme.getName());
if (myProcessor.shouldBeSaved(eScheme)) {
final String fileName = getFileNameForScheme(fileNameProvider, eScheme);
try {
final Parent element = writeSchemeToDocument(eScheme);
if (element != null) {
long newHash = computeHashValue(element);
Long oldHash = eScheme.getExternalInfo().getHash();
saveIfNeeded(eScheme, fileName, element, newHash, oldHash);
}
}
catch (final IOException e) {
Application app = ApplicationManager.getApplication();
if (app.isUnitTestMode() || app.isCommandLine()) {
LOG.error("Cannot write scheme " + fileName + " in '" + myFileSpec + "': " + e.getLocalizedMessage(), e);
}
else {
app.invokeLater(new Runnable(){
@Override
public void run() {
Messages.showErrorDialog("Cannot save scheme '" + eScheme.getName() + ": " + e.getLocalizedMessage(), "Save Settings");
}
});
}
}
}
}
}
}
private String getFileNameForScheme(final UniqueFileNamesProvider fileNameProvider, final E scheme) {
final String fileName;
if (scheme.getExternalInfo().getCurrentFileName() != null) {
fileName = scheme.getExternalInfo().getCurrentFileName();
fileNameProvider.reserveFileName(fileName);
}
else {
fileName = fileNameProvider.suggestName(scheme.getName());
}
return fileName + mySchemeExtension;
}
private void saveIfNeeded(E schemeKey, String fileName, Parent element, long newHash, Long oldHash) throws IOException {
if (oldHash == null || newHash != oldHash.longValue() || myVFSBaseDir.findChild(fileName) == null) {
ensureFileText(fileName, StorageUtil.elementToBytes(element, true).toByteArray());
schemeKey.getExternalInfo().setHash(newHash);
saveFileName(fileName, schemeKey);
saveOnServer(fileName, element);
}
}
private void saveOnServer(final String fileName, @NotNull Parent element) {
if (myProvider != null && myProvider.isEnabled()) {
StorageUtil.sendContent(myProvider, getFileFullPath(fileName), element, myRoamingType, true);
}
}
private void reserveUsingFileNames(final Collection<T> schemes, final UniqueFileNamesProvider fileNameProvider) {
fileNameProvider.reserveFileName(DELETED_XML);
for (T scheme : schemes) {
if (scheme instanceof ExternalizableScheme) {
ExternalInfo info = ((ExternalizableScheme)scheme).getExternalInfo();
final String fileName = info.getCurrentFileName();
if (fileName != null) {
if (Comparing.equal(info.getPreviouslySavedName(),scheme.getName())) {
fileNameProvider.reserveFileName(fileName);
}
else {
myFilesToDelete.add(fileName);
info.setCurrentFileName(null);
}
}
}
}
}
@NotNull
private Element createDeletedElement() {
Element root = new Element("deleted-schemes");
for (String deletedName : myDeletedNames) {
Element child = new Element("scheme");
root.addContent(child);
child.setAttribute("name", deletedName);
}
return root;
}
@Override
protected void onSchemeDeleted(final Scheme toDelete) {
if (toDelete instanceof ExternalizableScheme) {
ExternalInfo info = ((ExternalizableScheme)toDelete).getExternalInfo();
String previouslyUsedName = info.getPreviouslySavedName();
if (previouslyUsedName != null) {
myDeletedNames.add(previouslyUsedName);
}
if (info.getCurrentFileName() != null) {
myFilesToDelete.add(info.getCurrentFileName());
}
}
}
@Override
protected void onSchemeAdded(final T scheme) {
myDeletedNames.remove(scheme.getName());
if (scheme instanceof ExternalizableScheme) {
((ExternalizableScheme)scheme).getExternalInfo().setPreviouslySavedName(scheme.getName());
}
}
}