/*******************************************************************************
* Copyright (c) 2011 IBM Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* IBM Corporation - initial API and implementation
*******************************************************************************/
package org.eclipse.orion.tools;
import java.io.*;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* A command line tool for cleaning up unused projects
*/
public class ProjectCleanup {
private static final String METADATA_DIR = ".metadata/.plugins/org.eclipse.orion.server.core/.settings/";
private static final String PROJECT_PREFS = METADATA_DIR + "Projects.prefs";
private static final String WORKSPACE_PREFS = METADATA_DIR + "Workspaces.prefs";
private static final String USER_PREFS = METADATA_DIR + "Users.prefs";
private boolean help = false;
private boolean purge = false;
private boolean permissions = false;
public static void main(String[] arguments) {
new ProjectCleanup(arguments).run();
}
public ProjectCleanup(String[] arguments) {
parseArgs(arguments);
}
/**
* Returns the recursive size of a file system location.
*/
private long computeSize(File file) {
long size = 0;
if (file.isDirectory()) {
File[] children = file.listFiles();
if (children != null)
for (File child : children)
size += computeSize(child);
} else {
size += file.length();
}
return size;
}
/**
* Deletes all files at a given location.
* @return <code>true</code> if the files were deleted, and <code>false</code> otherwise.
*/
private boolean deleteFiles(File location) {
File[] children = location.listFiles();
if (children != null)
for (File child : children)
deleteFiles(child);
//this will fail if any child deletion failed
return location.delete();
}
/**
* Remove all metadata for deleted projects from the project metadata properties file.
*/
private boolean deleteProjectMetadata(Set<String> deletedProjects) throws IOException {
Properties projects = new Properties();
File prefFile = new File(PROJECT_PREFS).getAbsoluteFile();
BufferedInputStream inStream = new BufferedInputStream(new FileInputStream(prefFile));
try {
projects.load(inStream);
} finally {
safeClose(inStream);
}
for (Iterator<Object> it = projects.keySet().iterator(); it.hasNext();) {
//each entry is of the form <projectId>/<attributeKey>
String key = (String) it.next();
String[] splits = key.split("/");
if (splits.length == 2) {
String projectId = splits[0];
if (deletedProjects.contains(projectId))
it.remove();
}
}
BufferedOutputStream outStream = new BufferedOutputStream(new FileOutputStream(prefFile));
try {
projects.store(outStream, null);
} finally {
safeClose(outStream);
}
return true;
}
/**
* Returns a set of all known projects.
*/
private Map<String, ProjectInfo> findAllProjects() throws IOException {
Map<String, ProjectInfo> allProjects = findProjectsInMetadata();
findProjectsInContentLocation(allProjects);
return allProjects;
}
/**
* Finds all projects by scanning the default project content location.
*/
private void findProjectsInContentLocation(Map<String, ProjectInfo> allProjects) {
File root = new File("").getAbsoluteFile();
//first level is abbreviated user name
File[] shortNames = root.listFiles();
if (shortNames == null)
return;
for (File shortName : shortNames) {
if (".metadata".equals(shortName.getName()))
continue;
//second level is full user name
File[] userNames = shortName.listFiles();
if (userNames == null)
continue;
for (File user : userNames) {
//third level is project id
File[] projects = user.listFiles();
if (projects == null)
continue;
//add an entry for each project using file name as project id
for (File project : projects) {
String id = project.getName();
if (".metadata".equals(id))
continue;
ProjectInfo info = new ProjectInfo(id);
info.setLocation(project);
allProjects.put(id, info);
}
}
}
}
private Map<String, List<String>> findUsersInMetadata() throws IOException {
//map of users to list of workspaces
Map<String, List<String>> allUsers = new HashMap<String, List<String>>();
Properties userProps = new Properties();
BufferedInputStream inStream = new BufferedInputStream(new FileInputStream(new File(USER_PREFS)));
try {
userProps.load(inStream);
} finally {
safeClose(inStream);
}
// workspace list is of the form [{"Id":"<workspaceId>","LastModified":<number>},...]
Pattern workspacePattern = Pattern.compile("(\\{\"Id\":\")(\\w)+(\",\"LastModified\":)(\\d)+(\\})");
for (Object key : userProps.keySet()) {
//each entry is of the form <userId>/<attributeKey>
String keyString = (String) key;
String[] splits = keyString.split("/");
if (splits.length == 2) {
String id = splits[0];
List<String> workspaces = allUsers.get(id);
if (workspaces == null) {
workspaces = new ArrayList<String>();
allUsers.put(id, workspaces);
}
if ("Workspaces".equals(splits[1])) {
Matcher matcher = workspacePattern.matcher(userProps.getProperty(keyString));
String workspaceList = matcher.replaceAll("$2");
//remove surrounding square brackets
workspaceList = workspaceList.substring(1, workspaceList.length() - 1);
workspaces.addAll(Arrays.asList(workspaceList.split(",")));
}
}
}
return allUsers;
}
private Map<String, ProjectInfo> findProjectsInMetadata() throws IOException, FileNotFoundException {
Map<String, ProjectInfo> allProjects = new HashMap<String, ProjectInfo>();
Properties projects = new Properties();
BufferedInputStream inStream = new BufferedInputStream(new FileInputStream(new File(PROJECT_PREFS)));
try {
projects.load(inStream);
} finally {
safeClose(inStream);
}
for (Object key : projects.keySet()) {
//each entry is of the form <projectId>/<attributeKey>
String keyString = (String) key;
String[] splits = keyString.split("/");
if (splits.length == 2) {
String id = splits[0];
ProjectInfo info = allProjects.get(id);
if (info == null) {
info = new ProjectInfo(id);
allProjects.put(id, info);
}
if ("ContentLocation".equals(splits[1])) {
File location;
String locationString = projects.getProperty(keyString);
if (locationString.startsWith("file:")) {
location = new File(locationString.substring(5));
} else {
location = new File(locationString).getAbsoluteFile();
}
info.setLocation(location);
}
}
}
return allProjects;
}
/**
* Returns a mapping of workspaces to projects as specified in the workspace metadata.
*/
private Map<String, List<String>> findWorkspacesInMetadata() throws FileNotFoundException, IOException {
Map<String, List<String>> workspacesToProjects = new HashMap<String, List<String>>();
Properties props = new Properties();
File workspacePrefs = new File(WORKSPACE_PREFS).getAbsoluteFile();
props.load(new BufferedInputStream(new FileInputStream(workspacePrefs)));
Set<Object> keys = props.keySet();
for (Object key : keys) {
//keys are of form Id/Attribute
String keyString = (String) key;
String[] splits = keyString.split("/");
if (splits.length == 2 && "Projects".equals(splits[1])) {
String projectArray = props.getProperty(keyString);
//break up JSON array into objects
String[] projects = projectArray.split(",");
String workspace = splits[0];
List<String> projectList = new ArrayList<String>();
for (String each : projects) {
//break up JSON object into attributes
String[] attrs = each.split("\"");
if (attrs.length > 2)
projectList.add(attrs[3]);
}
workspacesToProjects.put(workspace, projectList);
}
}
return workspacesToProjects;
}
/**
* Returns a list of projects that are currently in user workspaces.
*/
private Set<String> markProjects() throws FileNotFoundException, IOException {
Set<String> allProjects = new HashSet<String>();
Map<String, List<String>> workspacesToProjects = findWorkspacesInMetadata();
for (List<String> projectList : workspacesToProjects.values())
allProjects.addAll(projectList);
return allProjects;
}
/**
* Parse the project cleanup application command line arguments.
*/
private void parseArgs(String[] arguments) {
for (String arg : arguments) {
if ("-purge".equals(arg))
purge = true;
else if ("-help".equals(arg) || "-?".equals(arg))
help = true;
else if ("-perm".equals(arg)) {
permissions = true;
}
}
}
/**
* Executes the project cleanup utility.
*/
public void run() {
if (help) {
System.out.println("Usage: ProjectCleanup [-help] [-purge] [-perm]");
return;
}
try {
Set<String> markSet = markProjects();
sweepProjects(markSet);
Map<String, List<String>> usersToPermissions = markPermissions();
if (permissions)
sweepPermissions(usersToPermissions);
} catch (IOException e) {
System.out.println("Failed to cleanup projects due to I/O exception:");
e.printStackTrace();
}
}
private void sweepPermissions(Map<String, List<String>> usersToPermissions) throws IOException {
Properties userProps = new Properties();
BufferedInputStream inStream = new BufferedInputStream(new FileInputStream(new File(USER_PREFS)));
try {
userProps.load(inStream);
} finally {
safeClose(inStream);
}
for (String user : usersToPermissions.keySet()) {
List<String> userPermissions = usersToPermissions.get(user);
String permissionJSON = "[";
for (String perm : userPermissions) {
permissionJSON += "{\"Method\":15,\"Uri\":\"" + perm + "\"},";
}
//remove trailing comma
permissionJSON = permissionJSON.substring(0, permissionJSON.length() - 1) + "]";
userProps.put(user + "/UserRights", permissionJSON);
}
BufferedOutputStream outStream = new BufferedOutputStream(new FileOutputStream(new File(USER_PREFS)));
try {
userProps.store(outStream, "Cleaned up properties");
} finally {
safeClose(outStream);
}
}
/**
* Returns a mapping from users to the permissions they should have based
* on their current workspaces and projects.
*/
private Map<String, List<String>> markPermissions() throws IOException {
//map of user name to list of permissions for that user
Map<String, List<String>> usersToPermissions = new HashMap<String, List<String>>();
Map<String, List<String>> usersToWorkspaces = findUsersInMetadata();
for (String user : usersToWorkspaces.keySet()) {
//don't mess with permissions of administrator
if (user.equals("admin"))
continue;
List<String> newPermissions = new ArrayList<String>();
//each user has access to their own profile page
newPermissions.add("/users/" + user);
//do for each workspace owned by current user
List<String> workspaceList = usersToWorkspaces.get(user);
if (workspaceList == null)
continue;
for (String workspace : workspaceList) {
//user has access to their own workspace
newPermissions.add("/workspace/" + workspace);
newPermissions.add("/workspace/" + workspace + "/*");
//access to project contents
newPermissions.add("/file/" + workspace);
newPermissions.add("/file/" + workspace + "/*");
}
usersToPermissions.put(user, newPermissions);
System.out.println("New permissions for " + user + ": " + newPermissions);
}
return usersToPermissions;
}
/**
* Close a stream, suppressing any secondary exception.
*/
private void safeClose(Closeable stream) {
try {
stream.close();
} catch (IOException e) {
//ignore
}
}
/**
* List and/or delete projects that are not in user workspaces.
*/
private void sweepProjects(Set<String> markSet) throws IOException {
Map<String, ProjectInfo> allProjects = findAllProjects();
System.out.println("Used projects: " + markSet);
System.out.println("All projects: " + allProjects.keySet());
Set<String> deletedProjects = new HashSet<String>();
long totalSize = 0;
for (ProjectInfo project : allProjects.values()) {
if (!markSet.contains(project.getId())) {
File projectLocation = project.getLocation();
System.out.println("Found unused project: " + project.getId() + " Location: " + projectLocation);
if (projectLocation == null || !projectLocation.exists()) {
System.out.println("\tNot found on disk");
deletedProjects.add(project.getId());
continue;
}
long size = computeSize(projectLocation) / 1024L;
totalSize += size;
System.out.println("\tSize: " + size + "KB");
if (purge) {
System.out.print("\tDeleting files... ");
boolean success = deleteFiles(projectLocation);
System.out.println(success ? "Done!" : "Failed!");
if (success)
deletedProjects.add(project.getId());
}
}
}
if (purge && !deletedProjects.isEmpty()) {
System.out.print("Deleting metadata for removed projects...");
boolean success = deleteProjectMetadata(deletedProjects);
System.out.println(success ? "Done!" : "Failed!");
}
System.out.println("Total unused projects: " + totalSize + "KB");
}
}