Commit c672f39d authored by Alexander Diewald's avatar Alexander Diewald
Browse files

Project: Refactor project import/load

* Use a single code path that relies on kernel mechanisms instead of
  N different, custom implementations.
* Move basic project handling to non-UI plugins.

Issue-Ref: 3993
Issue-Url: https://af3-developer.fortiss.org/issues/3993

Signed-off-by: Alexander Diewald's avatarAlexander Diewald <diewald@fortiss.org>
parent 567145e4
......@@ -16,7 +16,6 @@
package test.org.fortiss.af3.component.library;
import static org.fortiss.af3.component.utils.ComponentLibraryModelElementFactory.createLibraryComponentPackage;
import static org.fortiss.af3.project.utils.FileUtils.saveFileLibrary;
import static org.fortiss.af3.project.utils.LibraryModelElementFactory.createFileLibrary;
import static org.fortiss.tooling.kernel.utils.PrototypesUtils.findPrototypeComposableWith;
......@@ -29,6 +28,7 @@ import org.fortiss.af3.component.model.Component;
import org.fortiss.af3.component.model.ComponentRef;
import org.fortiss.af3.component.model.LibraryComponentPackage;
import org.fortiss.af3.project.model.FileLibrary;
import org.fortiss.af3.project.utils.ProjectUtils;
import org.fortiss.tooling.base.library.ModelElementLibraryService;
import org.fortiss.tooling.kernel.extension.data.ITopLevelElement;
import org.fortiss.tooling.kernel.extension.data.Prototype;
......@@ -96,7 +96,7 @@ public class LibraryTestBase {
FileLibrary fileLibrary = createFileLibrary(libName);
LibraryComponentPackage libraryComponentPackage = createLibraryComponentPackage(packName);
fileLibrary.getRootElements().add(libraryComponentPackage);
saveFileLibrary(fileLibrary);
ProjectUtils.saveFileLibrary(fileLibrary);
sleep();
}
......
ExportProjectUtils.java 95b02d271335e46ce89f78c912f85236fc852988 GREEN
FileNameValidatorUtil.java c566595d083e756c20c919458d81e8a91ab0e10e YELLOW
FolderOrProjectDialogUtils.java 14823ea1da085d019e21e50703f6f4e3fff8aa83 GREEN
ImportProjectUtils.java 99b4d98048b1f4f2ca7c59cce3b64c34202abdf1 YELLOW
NewProjectUtils.java b11771f9fa4451657dafd7c6cfc4e0dce6ac821a YELLOW
ImportProjectUtils.java f9612da036dc59dbd0d80b250dcfbbedc2b8ce10 YELLOW
NewProjectUiUtils.java cd17cec67769d1c62c9923869c43293036d103e4 YELLOW
ParsingUtils.java 7bbc27436511ee9d88f8a31f0b19462406d01517 GREEN
......@@ -16,29 +16,31 @@
package org.fortiss.af3.project.ui.utils;
import static org.eclipse.core.resources.ResourcesPlugin.getWorkspace;
import static org.eclipse.emf.common.util.URI.createFileURI;
import static org.eclipse.jface.dialogs.MessageDialog.openInformation;
import static org.eclipse.emf.common.util.URI.createURI;
import static org.eclipse.ui.PlatformUI.getWorkbench;
import static org.fortiss.af3.project.AF3Project.AF3_PRJ_SUFFIX;
import static org.fortiss.af3.project.storage.LocationProvider.AF3_20_TO_22_PROJECT_FILES_EXTENSION;
import static org.fortiss.af3.project.storage.LocationProvider.CURRENT_AF3_PROJECT_FILES_EXTENSION;
import static org.fortiss.af3.project.ui.AF3ProjectUIActivator.getDefault;
import static org.fortiss.af3.project.ui.utils.NewProjectUtils.createNewGeneralProject;
import static org.fortiss.af3.project.ui.utils.NewProjectUtils.requestFileNameDialog;
import static org.fortiss.af3.project.utils.FileUtils.generalProjectExists;
import static org.fortiss.af3.project.ui.utils.NewProjectUiUtils.requestFileNameDialog;
import static org.fortiss.af3.project.utils.FileUtils.getDefaultGeneralProjectURI;
import static org.fortiss.af3.project.utils.FileUtils.importFileProject;
import static org.fortiss.tooling.kernel.ui.util.MessageUtilsExtended.showErrorInUIThread;
import static org.fortiss.tooling.kernel.utils.LoggingUtils.error;
import static org.fortiss.tooling.kernel.utils.LoggingUtils.showError;
import static org.fortiss.tooling.kernel.utils.LoggingUtils.showInfo;
import java.io.File;
import java.io.IOException;
import java.net.URISyntaxException;
import java.net.URL;
import org.eclipse.core.runtime.FileLocator;
import org.eclipse.core.runtime.Path;
import org.eclipse.emf.common.util.URI;
import org.eclipse.swt.SWT;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.FileDialog;
import org.eclipse.swt.widgets.Shell;
import org.fortiss.af3.project.utils.ProjectUtils;
/**
* Utility methods for importing projects into workspace.
......@@ -65,42 +67,72 @@ public class ImportProjectUtils {
return fd.open();
}
/**
* Tests if a file with the given name exists in the workspace. If yes, the user is
* asked for another name for the file.
*
* @param targetFileName
* name of the AF3 project file to be imported. If this location is occupied, the
* user is asked for another file name.
*
* @return a URI to the new user defined file, or {@code null} in case the user cancelled the
* import. This file doesn't exist at this moment.
*/
public static URI determineTargetFileLocation(String targetFileName) {
URI targetURI = getDefaultGeneralProjectURI().appendSegment(targetFileName)
.appendFileExtension(AF3_PRJ_SUFFIX);
return determineTargetFileLocation(targetURI);
}
/**
* Tests if a file at the given URI exists already. If yes, the user is
* asked for another name for the file.
*
* @param initialFileURI
* @param fileURI
* the initial URI of the location to which the file should be
* imported. If this location is not free, the user is asked for
* another file name.
*
* @return a URI to the new user defined file, or
* {@code null in case the user cancelled the import}. This file doesn't exist at
* this moment.
* @return a URI to the new user defined file, or {@code null} in case the user cancelled the
* import. This file doesn't exist at this moment.
*/
private static URI determineTargetFileLocation(URI initialFileURI) {
// The file name that may be changed by the user.
String targetFileName = initialFileURI.lastSegment();
public static URI determineTargetFileLocation(URI fileURI) {
// Create file handler.
File targetFile = getTargetFile(targetFileName);
File targetFile = new File(fileURI.toString());
// If a file with this name already exists in the AF3 workspace, ask the
// user for another file name.
if(targetFile.exists()) {
targetFileName = requestFileNameDialog(targetFile);
String targetFileName = requestFileNameDialog(targetFile);
if(targetFileName == null) {
return null;
}
targetFile = getTargetFile(targetFileName);
fileURI.trimSegments(1).appendSegment(targetFileName);
}
return createFileURI(targetFile.toString());
return fileURI;
}
/** Returns the target {@link File} for the given {@code targetFileName}. */
private static File getTargetFile(String targetFileName) {
return new File(getDefaultGeneralProjectURI().appendSegment(targetFileName).toFileString());
/**
* Imports the given {@link File} at the given {@link URI}. Resources that are referenced from
* the project are also copied to the target location.
* NOTE: Libraries are ignored during the import.
*
* @param sourceFileURI
* {@link File} that will be imported.
* @throws IOException
* if any error occurred when writing to the target location or inconsistencies in
* the project file w.r.t. to referenced files were detected.
*/
public static void importFileProject(URI sourceFileURI) throws IOException {
// Isolate the file name from the path to it.
String sourceFileName = sourceFileURI.lastSegment();
// Determine the file location of the target file.
URI targetFileURI = determineTargetFileLocation(
getDefaultGeneralProjectURI().appendSegment(sourceFileName));
ProjectUtils.importFileProject(sourceFileURI, targetFileURI);
}
/**
......@@ -110,15 +142,24 @@ public class ImportProjectUtils {
* the URI of the file that should be imported
*/
public static void importUISelectedFile(URI sourceFileURI) {
File sourceFile = new File(sourceFileURI.toFileString());
try {
// Resolve URIs encoded as eclipse platform URIs. For normal file URIs, the lines below
// are no-ops.
URL url = new URL(sourceFileURI.toString());
String uriString = FileLocator.resolve(url).toURI().toString();
sourceFileURI = createURI(uriString);
} catch(URISyntaxException | IOException e1) {
showError("Could not find and open the file located at " + sourceFileURI + ".");
return;
}
File sourceFile = new File(sourceFileURI.toString());
// Check if really an af3_2x project is given.
Path sourcePath = new Path(sourceFile.getPath());
// @CodeFormatterOff
if( ! ( sourcePath.getFileExtension().equals(CURRENT_AF3_PROJECT_FILES_EXTENSION)
|| sourcePath.getFileExtension().equals(AF3_20_TO_22_PROJECT_FILES_EXTENSION))) {
openInformation(Display.getDefault().getActiveShell(), "Invalid AF3 project file",
sourcePath.toString() + " is not an AF3 project!");
showInfo("Invalid AF3 project file: " + sourcePath.toString() + " is not an AF3 project!");
return;
}
// @CodeFormatterOn
......@@ -128,19 +169,13 @@ public class ImportProjectUtils {
URI projectURI = getDefaultGeneralProjectURI();
URI sourceDir = sourceFileURI.trimSegments(1);
if(sourceDir.equals(projectURI)) {
showErrorInUIThread("Cannot import Project",
"The selected project is located in the AF3 workspace" +
" folder. AF3 does not support importing AF3 projects" +
" from this folder. Export the selected project to a" +
" different location first.");
showError("The selected project is located in the AF3 workspace" +
" folder. AF3 does not support importing AF3 projects" +
" from this folder. Export the selected project to a" +
" different location first.");
return;
}
// If the general project doesn't exist, create one.
if(!generalProjectExists()) {
createNewGeneralProject();
}
// Isolate the file name from the path to it.
String sourceFileName = sourceFileURI.lastSegment();
......@@ -148,14 +183,9 @@ public class ImportProjectUtils {
URI targetFileURI = determineTargetFileLocation(
getDefaultGeneralProjectURI().appendSegment(sourceFileName));
if(targetFileURI == null) {
// User cancelled
return;
}
// Copy the file, its associated library, and external documents.
try {
importFileProject(sourceFile, targetFileURI);
ProjectUtils.importFileProject(sourceFileURI, targetFileURI);
} catch(IOException e) {
error(getDefault(), "Error, cannot import AF3 project!", e);
showErrorInUIThread("Error, cannot import AF3 project!",
......
/*-------------------------------------------------------------------------+
| Copyright 2011 fortiss GmbH |
| |
| 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 org.fortiss.af3.project.ui.utils;
import java.io.File;
import org.eclipse.jface.dialogs.InputDialog;
import org.eclipse.jface.window.Window;
import org.eclipse.swt.widgets.Display;
import org.fortiss.af3.project.storage.LocationProvider;
/**
* Provides methods for creating new AF3 projects.
*
* @author becker
* @author diewald
*/
public class NewProjectUiUtils {
/**
* Asks the user for another filename, if a file with the current name
* already exists.
*
* @param AF3Project
* the current file handler
* @return the new unique name
*/
static public String requestFileNameDialog(File AF3Project) {
String fileName = AF3Project.getName();
fileName = fileName.replace("." + LocationProvider.CURRENT_AF3_PROJECT_FILES_EXTENSION, "");
fileName = fileName.replace(" ", "_");
InputDialog dlg = new InputDialog(Display.getCurrent().getActiveShell(), "",
"The project already exists, please choose a different name", fileName,
new FileNameValidatorUtil());
// If user clicked OK, update the file name to be unique.
if(dlg.open() == Window.OK) {
return dlg.getValue() + "." + LocationProvider.CURRENT_AF3_PROJECT_FILES_EXTENSION;
}
return null;
}
}
NewLibraryWizard.java 34d538b36b03e0541578490d70d64edf4cf1da26 GREEN
NewLibraryWizard.java 9c5ea77e1dd62f864d016321352ae90141634391 YELLOW
NewProjectWizard.java 77a6292ff791c55c1abcfc5533fb6544a6488fcb GREEN
......@@ -16,8 +16,8 @@
package org.fortiss.af3.project.ui.wizard;
import static org.eclipse.jface.dialogs.MessageDialog.openError;
import static org.fortiss.af3.project.utils.FileUtils.saveFileLibrary;
import static org.fortiss.af3.project.utils.LibraryModelElementFactory.createFileLibrary;
import static org.fortiss.af3.project.utils.ProjectUtils.saveFileLibrary;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.wizard.Wizard;
......
ExternalFileUtils.java 4171a41060407cd4f19c99fcaf1ebfb9a390134a GREEN
FileUtils.java 2a3ebb512583c18c81ae64be03733b07079e1eb9 YELLOW
FileUtils.java 70e81c85ae7c2d8076e190ebad7eae648451b645 YELLOW
FunctionScopeUtils.java 054c92406affa0c0ad8ac7a067de9608d0bbca2b GREEN
LibraryModelElementFactory.java a5a30a0e6b274d220c397d9bf0f14756d49bd19f GREEN
NewProjectUtils.java f89b4631468f8824499e23eb0f58f354e90930db YELLOW
ProjectModelElementFactory.java 2963d3a1fe651025bae3ae4dc0b6e0b77bbaadd5 GREEN
ProjectUtils.java e11d8af800fbda94d017c23b6dd1918c08413b82 GREEN
ProjectUtils.java 1fc8e504afa825593b652b85aaa4cbe658e4a171 YELLOW
TypeScopeUtils.java 54f2397f0d6720c90948ccc295f1a63b7319daac GREEN
VariableScopeUtils.java 031591687c2461a990d0534cf4ed4d6df39b1551 GREEN
......@@ -15,43 +15,20 @@
+--------------------------------------------------------------------------*/
package org.fortiss.af3.project.utils;
import static org.conqat.lib.commons.filesystem.FileSystemUtils.copyFile;
import static org.eclipse.core.resources.ResourcesPlugin.getWorkspace;
import static org.eclipse.emf.common.util.URI.createFileURI;
import static org.fortiss.af3.project.AF3Project.AF3_PRJ_SUFFIX;
import static org.fortiss.af3.project.AF3ProjectActivator.getDefault;
import static org.fortiss.af3.project.utils.ExternalFileUtils.doCopyExternalDocuments;
import static org.fortiss.tooling.kernel.utils.EMFResourceUtils.createNewEclipseWorkspaceProject;
import static org.fortiss.tooling.kernel.utils.EMFResourceUtils.loadModelFromFileWithUnknownFeatures;
import static org.fortiss.tooling.kernel.utils.LoggingUtils.error;
import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.URL;
import java.util.Map;
import org.conqat.lib.commons.collections.Pair;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IFolder;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IWorkspaceRoot;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.core.runtime.Path;
import org.eclipse.emf.common.util.URI;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.xml.type.AnyType;
import org.fortiss.af3.project.AF3ProjectActivator;
import org.fortiss.af3.project.model.FileLibrary;
import org.fortiss.af3.project.model.FileProject;
import org.fortiss.af3.project.storage.LibraryLocationProvider;
import org.fortiss.tooling.kernel.extension.data.ITopLevelElement;
import org.fortiss.tooling.kernel.service.IPersistencyService;
import org.fortiss.tooling.kernel.service.listener.IPersistencyServiceListener;
/**
* Usable to load files that are contained in the plugin.
......@@ -145,125 +122,4 @@ public class FileUtils {
static public URI getDefaultGeneralProjectURI() {
return createFileURI(getDefaultGeneralProjectPath());
}
/**
* Saves a {@link FileLibrary}.
*
* @param lib
* the FileLibrary object
* @return boolean information if successful (true) or not (false)
* @throws IOException
* @throws CoreException
*/
public static boolean saveFileLibrary(FileLibrary lib) throws IOException, CoreException {
IWorkspaceRoot workspaceRoot = getWorkspace().getRoot();
IProject project = getAF3EclipseProject();
IFolder folder = project.getFolder(FileUtils.LIBRARY_LOCATION);
if(!project.exists())
project.create(null);
if(!project.isOpen())
project.open(null);
if(!folder.exists()) {
folder.create(IResource.NONE, true, null);
}
String libraryFileName =
lib.getName() + "." + LibraryLocationProvider.AF3_PROJECT_FILES_EXTENSION;
IFile file = workspaceRoot.getFile(new Path(AF3_PROJECT_DIRECTORY_NAME + File.separator +
LIBRARY_LOCATION + File.separator + libraryFileName));
if(file.exists()) {
return false;
}
createNewEclipseWorkspaceProject(file, lib);
return true;
}
/**
* Imports the given {@link File} to the given {@link URI}. Resources that are referenced from
* the project are also copied to the target location.
* NOTE: Libraries are ignored during the import.
*
* @param sourceFile
* {@link File} that will be imported.
* @param targetFileURI
* {@link URI} pointing to the target location. No File may exist there.
* @throws IOException
* if any error occurred when writing to the target location or inconsistencies in
* the project file w.r.t. to referenced files were detected.
*/
// NOTE: A more appropriate place for this code would be the kernel resource and model
// utilities. Then, the heuristic detection for the renaming coulld also be appropriately
// implemented by comparing file locations of the ModelContext that is retrieved as an input of
// the persistency service listener.
public static void importFileProject(File sourceFile, URI targetFileURI) throws IOException {
// Load project to check external references (and adapt project name).
Pair<EObject, Map<EObject, AnyType>> loadOutcome =
loadModelFromFileWithUnknownFeatures(createFileURI(sourceFile.toString()));
FileProject sourceFileProject = (FileProject)loadOutcome.getFirst();
// TODO #3996: Readd the library handling here. Adapt the method documentation after
// implementing this feature.
// Copy referenced external documents along accordingly.
doCopyExternalDocuments(sourceFileProject, createFileURI(sourceFile.getAbsolutePath()),
targetFileURI);
// Perform the copy to the actual target (workspace) for the kernel to perform the import.
copyFile(sourceFile, new File(targetFileURI.toFileString()));
// Using the Java-API is not directly possible, as we would need to load the file before,
// which could destroy some element references. Using listeners we can avoid the problem by
// setting the name after the project was imported by the kernel.
IPersistencyService.getInstance().addTopLevelElementListener(
createRenamingListener(sourceFileProject.getName(), targetFileURI));
}
/**
* Creates a one-time listener that renames an inported project after it has been loaded by the
* kernel.
*/
private static IPersistencyServiceListener createRenamingListener(String originalName,
URI targetFileURI) {
// delete file extension ".af3_2x" from file-name and set this as project name
String newName =
targetFileURI.lastSegment().replace("." + targetFileURI.fileExtension(), "");
return new IPersistencyServiceListener() {
@Override
public void topLevelElementAdded(ITopLevelElement element) {
if(element.getRootModelElement() instanceof FileProject) {
FileProject fp = (FileProject)element.getRootModelElement();
if(element.getSaveableName().equals(newName + AF3_PRJ_SUFFIX) &&
fp.getName().equals(originalName)) {
element.runAsCommand(() -> fp.setName(newName));
try {
element.doSave(new NullProgressMonitor());
} catch(IOException | CoreException e) {
error(getDefault(), "Failed to rename the imported Project.", e);
} finally {
IPersistencyService.getInstance().removeTopLevelElementListener(this);
}
}
}
}
@Override
public void topLevelElementRemoved(ITopLevelElement element) {
// Unused
}
@Override
public void topLevelElementLoaded(ITopLevelElement element) {
// Unused
}
@Override
public void topLevelElementContentChanged(ITopLevelElement element) {
// Not needed
}
};
}
}
......@@ -14,18 +14,15 @@
| limitations under the License. |
+--------------------------------------------------------------------------*/
package org.fortiss.af3.project.ui.utils;
package org.fortiss.af3.project.utils;
import static org.eclipse.jface.dialogs.MessageDialog.openError;
import static org.eclipse.jface.dialogs.MessageDialog.openInformation;
import static org.eclipse.ui.PlatformUI.getWorkbench;
import static org.eclipse.ui.ide.undo.WorkspaceUndoUtil.getUIInfoAdapter;
import static org.fortiss.af3.project.utils.FileUtils.AF3_PROJECT_DIRECTORY_NAME;
import static org.fortiss.af3.project.utils.FileUtils.PROJECT_DIR;
import static org.fortiss.af3.project.utils.FileUtils.WORKSPACE_ROOT;
import static org.fortiss.af3.project.utils.FileUtils.generalProjectExists;
import static org.fortiss.tooling.kernel.utils.EMFResourceUtils.refreshWorkspace;
import static org.fortiss.tooling.kernel.utils.LoggingUtils.error;
import static org.fortiss.tooling.kernel.utils.LoggingUtils.info;
import static org.fortiss.tooling.kernel.utils.LoggingUtils.showError;
import java.io.BufferedWriter;
import java.io.File;
......@@ -35,23 +32,17 @@ import org.eclipse.core.internal.resources.Project;
import org.eclipse.core.internal.resources.Workspace;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IProjectDescription;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IWorkspace;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.core.runtime.Path;
import org.eclipse.jface.dialogs.InputDialog;
import org.eclipse.jface.window.Window;
import org.eclipse.swt.widgets.Display;
import org.eclipse.ui.ide.undo.CreateProjectOperation;
import org.eclipse.ui.internal.wizards.newresource.ResourceMessages;
import org.fortiss.af3.project.AF3ProjectActivator;
import org.fortiss.af3.project.storage.LocationProvider;
import org.fortiss.af3.project.utils.FileUtils;
/**
* Provides methods for creating new AF3 projects.
* Provides methods for creating new AF3 projects using UI-elements for user input.
*
* @author becker
* @author diewald
*/
@SuppressWarnings("restriction")
public class NewProjectUtils {
......@@ -100,18 +91,8 @@ public class NewProjectUtils {
final IProjectDescription description =
WORKSPACE_ROOT.getWorkspace().newProjectDescription(newProject.getName());
// Define the operation that stores the project in the file system and
// adds it to the current workspace.
CreateProjectOperation op =
new CreateProjectOperation(description, ResourceMessages.NewProject_windowTitle);
try {
// Execute operation to make project persistent in file system
// (see bug https://bugs.eclipse.org/bugs/show_bug.cgi?id=219901
// directly execute the operation so that the undo state is
// not preserved. Making this undoable resulted in too many
// accidental file deletions).
op.execute(null/* monitor */, getUIInfoAdapter(Display.getDefault().getActiveShell()));
newProject.create(description, new NullProgressMonitor());
// If the general project folder was already existent, but it was
// deleted manually from file system, the upper 'op.execute'
......@@ -127,56 +108,30 @@ public class NewProjectUtils {
out.write(defaultGeneralProjectFile);
}
// Refresh the workspace.
WORKSPACE_ROOT.refreshLocal(IResource.DEPTH_INFINITE, null);
// Refreshing the workspace doesn't help here. The project
// doesn't appear in package explorer, if the general
// project directory was deleted before by hand from file
// system.
// Hence, restart AF3. After that the new projects are shown!
info(AF3ProjectActivator.getDefault(),
"Restarting AF3 in order to be able to show the new project in project explorer.\n" +
"This might be caused by you have probably deleted the AF3 workspace directory manually from file system before?");
openInformation(Display.getDefault().getActiveShell(),
"Problems during project creation",
"You may have deleted the " + AF3_PROJECT_DIRECTORY_NAME +
" manually from file system?" +</