blob: 75b0999c66aef25da90c2fb6e400a9ced4ba4504 [file] [log] [blame]
/*
* Copyright 2000-2013 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.components.impl.stores;
import com.intellij.notification.Notification;
import com.intellij.notification.NotificationType;
import com.intellij.notification.Notifications;
import com.intellij.openapi.Disposable;
import com.intellij.openapi.application.Application;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.application.PathManager;
import com.intellij.openapi.components.StateStorageException;
import com.intellij.openapi.components.TrackingPathMacroSubstitutor;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.util.JDOMUtil;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.vfs.*;
import com.intellij.openapi.vfs.tracker.VirtualFileTracker;
import com.intellij.util.io.fs.FileSystem;
import com.intellij.util.io.fs.IFile;
import com.intellij.util.messages.MessageBus;
import org.jdom.Document;
import org.jdom.Element;
import org.jdom.JDOMException;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.picocontainer.PicoContainer;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
public class FileBasedStorage extends XmlElementStorage {
private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.components.impl.stores.FileBasedStorage");
private final String myFilePath;
private final IFile myFile;
protected final String myRootElementName;
private static boolean myConfigDirectoryRefreshed = false;
private volatile VirtualFile myCachedVirtualFile;
public FileBasedStorage(@Nullable TrackingPathMacroSubstitutor pathMacroManager,
StreamProvider streamProvider,
final String filePath,
final String fileSpec,
String rootElementName,
@NotNull Disposable parentDisposable,
PicoContainer picoContainer,
ComponentRoamingManager componentRoamingManager, ComponentVersionProvider localComponentVersionProvider) {
super(pathMacroManager, parentDisposable, rootElementName, streamProvider, fileSpec, componentRoamingManager, localComponentVersionProvider);
Application app = ApplicationManager.getApplication();
if (!myConfigDirectoryRefreshed && (app.isUnitTestMode() || app.isDispatchThread())) {
try {
syncRefreshPathRecursively(PathManager.getConfigPath(), "componentVersions");
}
finally {
//noinspection AssignmentToStaticFieldFromInstanceMethod
myConfigDirectoryRefreshed = true;
}
}
myRootElementName = rootElementName;
myFilePath = filePath;
myFile = FileSystem.FILE_SYSTEM.createFile(myFilePath);
VirtualFileTracker virtualFileTracker = (VirtualFileTracker)picoContainer.getComponentInstanceOfType(VirtualFileTracker.class);
MessageBus messageBus = (MessageBus)picoContainer.getComponentInstanceOfType(MessageBus.class);
if (virtualFileTracker != null && messageBus != null) {
final String path = myFile.getAbsolutePath();
final String fileUrl = LocalFileSystem.PROTOCOL_PREFIX + path.replace(File.separatorChar, '/');
final Listener listener = messageBus.syncPublisher(STORAGE_TOPIC);
virtualFileTracker.addTracker(fileUrl, new VirtualFileAdapter() {
@Override
public void fileMoved(VirtualFileMoveEvent event) {
myCachedVirtualFile = null;
}
@Override
public void fileDeleted(VirtualFileEvent event) {
myCachedVirtualFile = null;
}
@Override
public void contentsChanged(final VirtualFileEvent event) {
if (!isDisposed()) {
listener.storageFileChanged(event, FileBasedStorage.this);
}
}
}, false, this);
}
}
private static void syncRefreshPathRecursively(@NotNull String configDirectoryPath, @Nullable final String excludeDir) {
VirtualFile configDir = LocalFileSystem.getInstance().refreshAndFindFileByPath(configDirectoryPath);
if (configDir != null) {
requestAllChildren(configDir, excludeDir);
VfsUtil.markDirtyAndRefresh(false, true, false, configDir);
}
}
private static void requestAllChildren(final VirtualFile configDir, @Nullable final String excludeDir) {
VfsUtilCore.visitChildrenRecursively(configDir, new VirtualFileVisitor() {
@Override
public boolean visitFile(@NotNull VirtualFile file) {
return excludeDir == null || !excludeDir.equals(file.getName());
}
});
}
@Override
protected MySaveSession createSaveSession(final MyExternalizationSession externalizationSession) {
return new FileSaveSession(externalizationSession);
}
public void resetProviderCache() {
myProviderUpToDateHash = -1;
if (myRemoteVersionProvider != null) {
myRemoteVersionProvider.myProviderVersions = null;
}
}
private class FileSaveSession extends MySaveSession {
protected FileSaveSession(MyExternalizationSession externalizationSession) {
super(externalizationSession);
}
@Override
protected boolean physicalContentNeedsSave() {
VirtualFile file = getVirtualFile();
if (file == null || !file.exists())
return !myStorageData.isEmpty();
return !StorageUtil.contentEquals(getDocumentToSave(), file);
}
@Override
protected int calcHash() {
int hash = myStorageData.getHash();
if (myPathMacroSubstitutor != null) {
hash = 31 * hash + myPathMacroSubstitutor.hashCode();
}
return hash;
}
@Override
protected void doSave() throws StateStorageException {
if (myBlockSavingTheContent) {
return;
}
if (ApplicationManager.getApplication().isUnitTestMode() && myFile != null && StringUtil.startsWithChar(myFile.getPath(), '$')) {
throw new StateStorageException("It seems like some macros were not expanded for path: " + myFile.getPath());
}
LOG.assertTrue(myFile != null);
myCachedVirtualFile = StorageUtil.save(myFile, getDocumentToSave(), this);
}
@NotNull
@Override
public Collection<IFile> getStorageFilesToSave() throws StateStorageException {
boolean needsSave = needsSave();
if (needsSave) {
if (LOG.isDebugEnabled()) {
LOG.info("File " + myFileSpec + " needs save; hash=" + myUpToDateHash + "; currentHash=" + calcHash() + "; " +
"content needs save=" + physicalContentNeedsSave());
}
return getAllStorageFiles();
}
else {
return Collections.emptyList();
}
}
@NotNull
@Override
public List<IFile> getAllStorageFiles() {
return Collections.singletonList(myFile);
}
}
@Override
protected void loadState(final StorageData result, final Element element) throws StateStorageException {
((FileStorageData)result).myFilePath = myFile.getAbsolutePath();
super.loadState(result, element);
}
@Override
@NotNull
protected StorageData createStorageData() {
return new FileStorageData(myRootElementName);
}
public static class FileStorageData extends StorageData {
String myFilePath;
public FileStorageData(final String rootElementName) {
super(rootElementName);
}
protected FileStorageData(FileStorageData storageData) {
super(storageData);
myFilePath = storageData.myFilePath;
}
@Override
public StorageData clone() {
return new FileStorageData(this);
}
@NonNls
public String toString() {
return "FileStorageData[" + myFilePath + "]";
}
}
@Nullable
public VirtualFile getVirtualFile() {
VirtualFile virtualFile = myCachedVirtualFile;
if (virtualFile == null) {
myCachedVirtualFile = virtualFile = StorageUtil.getVirtualFile(myFile);
}
return virtualFile;
}
public File getFile() {
return new File(myFile.getPath());
}
@Override
@Nullable
protected Document loadDocument() throws StateStorageException {
myBlockSavingTheContent = false;
try {
VirtualFile file = getVirtualFile();
if (file == null || file.isDirectory() || !file.isValid()) {
LOG.info("Document was not loaded for " + myFileSpec + " file is " + (file == null ? "null" : "directory"));
return null;
}
if (file.getLength() == 0) {
return processReadException(null);
}
return loadDocumentImpl(file);
}
catch (final JDOMException e) {
return processReadException(e);
}
catch (final IOException e) {
return processReadException(e);
}
}
@Nullable
private Document processReadException(@Nullable final Exception e) {
boolean contentTruncated = e == null;
myBlockSavingTheContent = isProjectOrModuleFile() && !contentTruncated;
if (!ApplicationManager.getApplication().isUnitTestMode() && !ApplicationManager.getApplication().isHeadlessEnvironment()) {
if (e != null) {
LOG.info(e);
}
final String message = "Cannot load settings from file '" + myFile.getPath() + "': " + (e == null ? "content truncated" : e.getLocalizedMessage()) + "\n" +
getInvalidContentMessage(contentTruncated);
Notifications.Bus.notify(
new Notification(Notifications.SYSTEM_MESSAGES_GROUP_ID, "Load Settings", message, NotificationType.WARNING));
}
return null;
}
private boolean isProjectOrModuleFile() {
return StorageUtil.isProjectOrModuleFile(myFileSpec);
}
private String getInvalidContentMessage(boolean contentTruncated) {
return isProjectOrModuleFile() && !contentTruncated ? "Please correct the file content" : "File content will be recreated";
}
private static Document loadDocumentImpl(final VirtualFile file) throws IOException, JDOMException {
InputStream stream = file.getInputStream();
try {
return JDOMUtil.loadDocument(stream);
}
finally {
stream.close();
}
}
public String getFileName() {
return myFile.getName();
}
public String getFilePath() {
return myFilePath;
}
@Override
public void setDefaultState(final Element element) {
element.setName(myRootElementName);
super.setDefaultState(element);
}
protected boolean physicalContentNeedsSave(final Document doc) {
VirtualFile file = getVirtualFile();
return file == null || !file.exists() || !StorageUtil.contentEquals(doc, file);
}
@Nullable
public File updateFileExternallyFromStreamProviders() throws IOException {
StorageData loadedData = loadData(true);
Document document = getDocument(loadedData);
if (physicalContentNeedsSave(document)) {
File file = new File(myFile.getAbsolutePath());
JDOMUtil.writeDocument(document, file, "\n");
return file;
}
return null;
}
}