/* * 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.components.impl.stores; import com.intellij.notification.NotificationsManager; import com.intellij.openapi.application.AccessToken; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.application.ModalityState; import com.intellij.openapi.application.ReadAction; import com.intellij.openapi.components.*; import com.intellij.openapi.components.StateStorage.SaveSession; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.project.Project; import com.intellij.openapi.project.impl.ProjectImpl; import com.intellij.openapi.util.InvalidDataException; import com.intellij.openapi.util.Pair; import com.intellij.openapi.util.io.FileUtil; import com.intellij.openapi.util.text.StringUtil; import com.intellij.openapi.vfs.*; import com.intellij.util.SmartList; import com.intellij.util.messages.MessageBus; import org.jdom.Element; import org.jdom.JDOMException; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.io.BufferedReader; import java.io.File; import java.io.IOException; import java.io.InputStreamReader; import java.util.ArrayList; import java.util.List; public class ProjectStoreImpl extends BaseFileConfigurableStoreImpl implements IProjectStore { private static final Logger LOG = Logger.getInstance(ProjectStoreImpl.class); protected ProjectImpl myProject; private String myPresentableUrl; ProjectStoreImpl(@NotNull ProjectImpl project, @NotNull PathMacroManager pathMacroManager) { super(pathMacroManager); myProject = project; } @Override public TrackingPathMacroSubstitutor[] getSubstitutors() { return new TrackingPathMacroSubstitutor[]{getStateStorageManager().getMacroSubstitutor()}; } @Override protected boolean optimizeTestLoading() { return myProject.isOptimiseTestLoadSpeed(); } @Override protected Project getProject() { return myProject; } @Override public void setProjectFilePath(@NotNull final String filePath) { final StateStorageManager stateStorageManager = getStateStorageManager(); final LocalFileSystem fs = LocalFileSystem.getInstance(); final File file = new File(filePath); final File dirStore = file.isDirectory() ? new File(file, Project.DIRECTORY_STORE_FOLDER) : new File(file.getParentFile(), Project.DIRECTORY_STORE_FOLDER); String defaultFilePath = new File(dirStore, "misc.xml").getPath(); // deprecated stateStorageManager.addMacro(StoragePathMacros.PROJECT_FILE, defaultFilePath); stateStorageManager.addMacro(StoragePathMacros.DEFAULT_FILE, defaultFilePath); final File ws = new File(dirStore, "workspace.xml"); stateStorageManager.addMacro(StoragePathMacros.WORKSPACE_FILE, ws.getPath()); stateStorageManager.addMacro(StoragePathMacros.PROJECT_CONFIG_DIR, dirStore.getPath()); ApplicationManager.getApplication().invokeAndWait(() -> VfsUtil.markDirtyAndRefresh(false, true, true, fs.refreshAndFindFileByIoFile(dirStore)), ModalityState.defaultModalityState()); myPresentableUrl = null; } @Override @Nullable public VirtualFile getProjectBaseDir() { if (myProject.isDefault()) return null; final String path = getProjectBasePath(); if (path == null) return null; return LocalFileSystem.getInstance().findFileByPath(path); } @Override public String getProjectBasePath() { if (myProject.isDefault()) return null; final String path = getProjectFilePath(); if (!StringUtil.isEmptyOrSpaces(path)) { return getBasePath(new File(path)); } //we are not yet initialized completely ("open directory", etc) StateStorage storage = getStateStorageManager().getStateStorage(StoragePathMacros.DEFAULT_FILE, RoamingType.PER_USER); if (!(storage instanceof FileBasedStorage)) { return null; } return getBasePath(((FileBasedStorage)storage).getFile()); } private String getBasePath(@NotNull File file) { if (myProject.isDefault()) { return file.getParent(); } else { File parentFile = file.getParentFile(); return parentFile == null ? null : parentFile.getParent(); } } @NotNull @Override public String getProjectName() { final VirtualFile baseDir = getProjectBaseDir(); assert baseDir != null : "project file=" + getProjectFilePath(); final VirtualFile ideaDir = baseDir.findChild(Project.DIRECTORY_STORE_FOLDER); if (ideaDir != null && ideaDir.isValid()) { final VirtualFile nameFile = ideaDir.findChild(ProjectImpl.NAME_FILE); if (nameFile != null && nameFile.isValid()) { try { try (BufferedReader in = new BufferedReader(new InputStreamReader(nameFile.getInputStream(), CharsetToolkit.UTF8_CHARSET))) { final String name = in.readLine(); if (name != null && name.length() > 0) { return name.trim(); } } } catch (IOException ignored) { } } } return baseDir.getName().replace(":", ""); } @Override public String getPresentableUrl() { if (myProject.isDefault()) { return null; } if (myPresentableUrl == null) { String url = !myProject.isDefault() ? getProjectBasePath() : getProjectFilePath(); if (url != null) { myPresentableUrl = FileUtil.toSystemDependentName(url); } } return myPresentableUrl; } @Override public void loadProject() throws IOException, JDOMException, InvalidDataException, StateStorageException { myProject.init(); } @Override public VirtualFile getProjectFile() { return myProject.isDefault() ? null : ((FileBasedStorage)getDefaultFileStorage()).getVirtualFile(); } @NotNull private XmlElementStorage getDefaultFileStorage() { // XmlElementStorage if default project, otherwise FileBasedStorage XmlElementStorage storage = (XmlElementStorage)getStateStorageManager().getStateStorage(StoragePathMacros.DEFAULT_FILE, RoamingType.PER_USER); assert storage != null; return storage; } @Override public VirtualFile getWorkspaceFile() { if (myProject.isDefault()) return null; final FileBasedStorage storage = (FileBasedStorage)getStateStorageManager().getStateStorage(StoragePathMacros.WORKSPACE_FILE, RoamingType.DISABLED); assert storage != null; return storage.getVirtualFile(); } @Override public void loadProjectFromTemplate(@NotNull ProjectImpl defaultProject) { defaultProject.save(); Element element = ((DefaultProjectStoreImpl)defaultProject.getStateStore()).getStateCopy(); if (element != null) { getDefaultFileStorage().setDefaultState(element); } } @NotNull @Override public String getProjectFilePath() { return myProject.isDefault() ? "" : ((FileBasedStorage)getDefaultFileStorage()).getFilePath(); } @NotNull @Override protected XmlElementStorage getMainStorage() { return getDefaultFileStorage(); } @NotNull @Override protected StateStorageManager createStateStorageManager() { return new ProjectStateStorageManager(myPathMacroManager.createTrackingSubstitutor(), myProject); } @NotNull @Override protected <T> Storage[] getComponentStorageSpecs(@NotNull PersistentStateComponent<T> persistentStateComponent, @NotNull State stateSpec, @NotNull StateStorageOperation operation) { Storage[] storages = stateSpec.storages(); if (storages.length == 1) { return storages; } assert storages.length > 0; if (operation == StateStorageOperation.READ) { List<Storage> result = new SmartList<>(); for (int i = storages.length - 1; i >= 0; i--) { Storage storage = storages[i]; if (storage.scheme() == StorageScheme.DIRECTORY_BASED) { result.add(storage); } } for (Storage storage : storages) { if (storage.scheme() == StorageScheme.DEFAULT) { result.add(storage); } } return result.toArray(new Storage[result.size()]); } else if (operation == StateStorageOperation.WRITE) { List<Storage> result = new SmartList<>(); for (Storage storage : storages) { if (storage.scheme() == StorageScheme.DIRECTORY_BASED) { result.add(storage); } } if (!result.isEmpty()) { return result.toArray(new Storage[result.size()]); } for (Storage storage : storages) { if (storage.scheme() == StorageScheme.DEFAULT) { result.add(storage); } } return result.toArray(new Storage[result.size()]); } else { return new Storage[]{}; } } @Override protected final void doSave(@Nullable List<SaveSession> saveSessions, @NotNull List<Pair<SaveSession, VirtualFile>> readonlyFiles) { ProjectImpl.UnableToSaveProjectNotification[] notifications = NotificationsManager.getNotificationsManager().getNotificationsOfType(ProjectImpl.UnableToSaveProjectNotification.class, myProject); if (notifications.length > 0) { throw new SaveCancelledException(); } beforeSave(readonlyFiles); super.doSave(saveSessions, readonlyFiles); if (!readonlyFiles.isEmpty()) { ReadonlyStatusHandler.OperationStatus status; AccessToken token = ReadAction.start(); try { status = ReadonlyStatusHandler.getInstance(myProject).ensureFilesWritable(getFilesList(readonlyFiles)); } finally { token.finish(); } if (status.hasReadonlyFiles()) { ProjectImpl.dropUnableToSaveProjectNotification(myProject, status.getReadonlyFiles()); throw new SaveCancelledException(); } else { List<Pair<SaveSession, VirtualFile>> oldList = new ArrayList<>(readonlyFiles); readonlyFiles.clear(); for (Pair<SaveSession, VirtualFile> entry : oldList) { executeSave(entry.first, readonlyFiles); } if (!readonlyFiles.isEmpty()) { ProjectImpl.dropUnableToSaveProjectNotification(myProject, getFilesList(readonlyFiles)); throw new SaveCancelledException(); } } } } protected void beforeSave(@NotNull List<Pair<SaveSession, VirtualFile>> readonlyFiles) { } @NotNull private static VirtualFile[] getFilesList(List<Pair<SaveSession, VirtualFile>> readonlyFiles) { final VirtualFile[] files = new VirtualFile[readonlyFiles.size()]; for (int i = 0, size = readonlyFiles.size(); i < size; i++) { files[i] = readonlyFiles.get(i).second; } return files; } @NotNull @Override protected MessageBus getMessageBus() { return myProject.getMessageBus(); } }