Skip to content

Commit

Permalink
Allow for a subset of projects to be exported (mit-cml#2051)
Browse files Browse the repository at this point in the history
Co-authored-by: Aaron Traylor <[email protected]>
  • Loading branch information
bartmathijssen and attraylor authored Feb 26, 2020
1 parent 83eacf4 commit 127c961
Show file tree
Hide file tree
Showing 7 changed files with 131 additions and 9 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -476,6 +476,10 @@ public interface OdeMessages extends Messages, AutogeneratedOdeMessages {
@Description("Name of Export Project menuitem")
String exportProjectMenuItem();

@DefaultMessage("Export {0} selected projects")
@Description("Name of Export Selected Projects menuitem")
String exportSelectedProjectsMenuItem(int numSelectedProjects);

@DefaultMessage("Export all projects")
@Description("Name of Export all Project menuitem")
String exportAllProjectsMenuItem();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -186,8 +186,11 @@ public void updateMenuState(int numSelectedProjects, int numProjects) {
boolean allowDelete = !isReadOnly && numSelectedProjects > 0;
boolean allowExport = numSelectedProjects > 0;
boolean allowExportAll = numProjects > 0;
String exportProjectLabel = numSelectedProjects > 1 ?
MESSAGES.exportSelectedProjectsMenuItem(numSelectedProjects) : MESSAGES.exportProjectMenuItem();
fileDropDown.setItemHtmlById(WIDGET_NAME_EXPORTPROJECT, exportProjectLabel);
fileDropDown.setItemEnabled(MESSAGES.deleteProjectMenuItem(), allowDelete);
fileDropDown.setItemEnabled(MESSAGES.exportProjectMenuItem(), allowExport);
fileDropDown.setItemEnabledById(WIDGET_NAME_EXPORTPROJECT, allowExport);
fileDropDown.setItemEnabled(MESSAGES.exportAllProjectsMenuItem(), allowExportAll);
}

Expand Down Expand Up @@ -572,6 +575,8 @@ public void execute() {
//If we are in the projects view
if (selectedProjects.size() == 1) {
exportProject(selectedProjects.get(0));
} else if (selectedProjects.size() > 1) {
exportSelectedProjects(selectedProjects);
} else {
// The user needs to select only one project.
ErrorReporter.reportInfo(MESSAGES.wrongNumberProjectsSelected());
Expand All @@ -584,10 +589,24 @@ public void execute() {

private void exportProject(Project project) {
Tracking.trackEvent(Tracking.PROJECT_EVENT,
Tracking.PROJECT_ACTION_DOWNLOAD_PROJECT_SOURCE_YA, project.getProjectName());
Tracking.PROJECT_ACTION_DOWNLOAD_PROJECT_SOURCE_YA, project.getProjectName());

Downloader.getInstance().download(ServerLayout.DOWNLOAD_SERVLET_BASE +
ServerLayout.DOWNLOAD_PROJECT_SOURCE + "/" + project.getProjectId());
ServerLayout.DOWNLOAD_PROJECT_SOURCE + "/" + project.getProjectId());
}

private void exportSelectedProjects(List<Project> projects) {
Tracking.trackEvent(Tracking.PROJECT_EVENT,
Tracking.PROJECT_ACTION_DOWNLOAD_SELECTED_PROJECTS_SOURCE_YA);

String selectedProjPath = ServerLayout.DOWNLOAD_SERVLET_BASE +
ServerLayout.DOWNLOAD_SELECTED_PROJECTS_SOURCE + "/";

for (Project project : projects) {
selectedProjPath += project.getProjectId() + "-";
}

Downloader.getInstance().download(selectedProjPath);
}
}

Expand Down Expand Up @@ -1069,7 +1088,7 @@ public void updateFileMenuButtons(int view) {
Ode.getInstance().getProjectManager().getProjects() == null);
fileDropDown.setItemEnabled(MESSAGES.exportAllProjectsMenuItem(),
Ode.getInstance().getProjectManager().getProjects().size() > 0);
fileDropDown.setItemEnabled(MESSAGES.exportProjectMenuItem(), false);
fileDropDown.setItemEnabledById(WIDGET_NAME_EXPORTPROJECT, false);
fileDropDown.setItemEnabled(MESSAGES.saveMenuItem(), false);
fileDropDown.setItemEnabled(MESSAGES.saveAsMenuItem(), false);
fileDropDown.setItemEnabled(MESSAGES.checkpointMenuItem(), false);
Expand All @@ -1079,7 +1098,7 @@ public void updateFileMenuButtons(int view) {
fileDropDown.setItemEnabled(MESSAGES.deleteProjectButton(), true);
fileDropDown.setItemEnabled(MESSAGES.exportAllProjectsMenuItem(),
Ode.getInstance().getProjectManager().getProjects().size() > 0);
fileDropDown.setItemEnabled(MESSAGES.exportProjectMenuItem(), true);
fileDropDown.setItemEnabledById(WIDGET_NAME_EXPORTPROJECT, true);
fileDropDown.setItemEnabled(MESSAGES.saveMenuItem(), true);
fileDropDown.setItemEnabled(MESSAGES.saveAsMenuItem(), true);
fileDropDown.setItemEnabled(MESSAGES.checkpointMenuItem(), true);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@ public class Tracking {
"DownloadProjectSource-YA";
public static final String PROJECT_ACTION_DOWNLOAD_FILE_YA = PROJECT_ACTION_PREFIX +
"DownloadFile-YA";
public static final String PROJECT_ACTION_DOWNLOAD_SELECTED_PROJECTS_SOURCE_YA =
PROJECT_ACTION_PREFIX + "DownloadSelectedProjectsSource-YA";
public static final String PROJECT_ACTION_DOWNLOAD_ALL_PROJECTS_SOURCE_YA =
PROJECT_ACTION_PREFIX + "DownloadAllProjectsSource-YA";
public static final String PROJECT_ACTION_SAVE_YA = PROJECT_ACTION_PREFIX +
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.NoSuchElementException;
import java.util.logging.Logger;

Expand All @@ -42,7 +44,7 @@ public class DownloadServlet extends OdeServlet {
// Constants for accessing split URI
/*
* Download kind can be: "project-output", "project-source",
* "all-projects-source", "file", or "userfile".
* "selected-projects-source", "all-projects-source", "file", or "userfile".
* Constants for these are defined in ServerLayout.
*/
private static final int DOWNLOAD_KIND_INDEX = 3;
Expand Down Expand Up @@ -164,14 +166,14 @@ public void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOExc
projectName = storageIo.getProjectName(projectUserId, projectId);
} catch (NumberFormatException e) {
// assume we got a name instead
for (Long pid: storageIo.getProjects(projectUserId)) {
for (Long pid : storageIo.getProjects(projectUserId)) {
if (storageIo.getProjectName(projectUserId, pid).equals(projectIdOrName)) {
projectId = pid;
}
}
if (projectId == 0) {
// didn't find project by name
throw new IllegalArgumentException("Can't find a project named "
throw new IllegalArgumentException("Can't find a project named "
+ projectIdOrName + " for user id " + projectUserId);
} else {
projectName = projectIdOrName;
Expand All @@ -186,7 +188,15 @@ public void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOExc
ProjectSourceZip zipFile = fileExporter.exportProjectSourceZip(projectUserId,
projectId, /* include history*/ true, /* include keystore */ true, zipName, true, true, false, false);
downloadableFile = zipFile.getRawFile();

} else if (downloadKind.equals(ServerLayout.DOWNLOAD_SELECTED_PROJECTS_SOURCE)) {
String[] projectIdStrings = uriComponents[PROJECT_ID_INDEX].split("-");
List<Long> projectIds = new ArrayList<Long>();
for (String projectId : projectIdStrings) {
projectIds.add(Long.valueOf(projectId));
}
ProjectSourceZip zipFile = fileExporter.exportSelectedProjectsSourceZip(
userId, "selected-projects.zip", projectIds);
downloadableFile = zipFile.getRawFile();
} else if (downloadKind.equals(ServerLayout.DOWNLOAD_ALL_PROJECTS_SOURCE)) {
// Download all project source files as a zip of zips.
ProjectSourceZip zipFile = fileExporter.exportAllProjectsSourceZip(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import com.google.appinventor.shared.rpc.project.RawFile;

import java.io.IOException;
import java.util.List;

import javax.annotation.Nullable;

Expand Down Expand Up @@ -57,6 +58,19 @@ ProjectSourceZip exportProjectSourceZip(String userId, long projectId,
boolean includeScreenShots,
boolean fatalError, boolean forGallery) throws IOException;

/**
* Exports projects selected by the user as a zip of zips.
*
* @param userId the userId
* @param zipName the desired name for the zip
* @param projectIds the list of project ids corresponding to selected projects
* @return the name, contents, and number of files in the zip
* @throws IllegalArgumentException if download request cannot be fulfilled
* (no projects)
* @throws IOException if files cannot be written
*/
ProjectSourceZip exportSelectedProjectsSourceZip(String userId, String zipName, List<Long> projectIds) throws IOException;

/**
* Exports all of the user's projects' source files as a zip of zips.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,73 @@ public ProjectSourceZip exportProjectSourceZip(String userId, long projectId,
}
}

@Override
public ProjectSourceZip exportSelectedProjectsSourceZip(String userId,
String zipName, List<Long> projectIds) throws IOException {
// Create a zip file for each project's sources.
if (projectIds.size() == 0) {
throw new IllegalArgumentException("No projects to download");
}

ByteArrayOutputStream zipFile = new ByteArrayOutputStream();
ZipOutputStream out = new ZipOutputStream(zipFile);
int count = 0;
String metadata = "";
for (Long projectId : projectIds) {
try {
ProjectSourceZip projectSourceZip =
exportProjectSourceZip(userId, projectId, false, false, null, false, false, false, false);
byte[] data = projectSourceZip.getContent();
String name = projectSourceZip.getFileName();

// If necessary, renae duplicate projects
while (true) {
try {
out.putNextEntry(new ZipEntry(name));
break;
} catch (IOException e) {
name = "duplicate-" + name;
}
}
metadata += projectSourceZip.getMetadata() + "\n";

out.write(data, 0, data.length);
out.closeEntry();
count++;
} catch (IllegalArgumentException e) {
System.err.println("No files found for userid: " + userId +
" for projectid: " + projectId);
} catch (IOException e) {
System.err.println("IOException while reading files found for userid: " +
userId + " for projectid: " + projectId);
continue;
}
}
if (count == 0) {
throw new IllegalArgumentException("No files to download");
}

List<String> userFiles = storageIo.getUserFiles(userId);
if (userFiles.contains(StorageUtil.ANDROID_KEYSTORE_FILENAME)) {
byte[] androidKeystoreBytes =
storageIo.downloadRawUserFile(userId, StorageUtil.ANDROID_KEYSTORE_FILENAME);
if (androidKeystoreBytes.length > 0) {
out.putNextEntry(new ZipEntry(StorageUtil.ANDROID_KEYSTORE_FILENAME));
out.write(androidKeystoreBytes, 0, androidKeystoreBytes.length);
out.closeEntry();
count++;
}
}

out.close();

// Package the big zip file up as a ProjectSourceZip and return it.
byte[] content = zipFile.toByteArray();
ProjectSourceZip projectSourceZip = new ProjectSourceZip(zipName, content, count);
projectSourceZip.setMetadata(metadata);
return projectSourceZip;
}

@Override
public ProjectSourceZip exportAllProjectsSourceZip(String userId,
String zipName) throws IOException {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,12 @@ public class ServerLayout {
*/
public static final String DOWNLOAD_PROJECT_SOURCE = "project-source";

/**
* Relative path within {@link com.google.appinventor.server.DownloadServlet}
* for downloading selected of a user's projects' sources.
*/
public static final String DOWNLOAD_SELECTED_PROJECTS_SOURCE = "selected-projects-source";

/**
* Relative path within {@link com.google.appinventor.server.DownloadServlet}
* for downloading all of a user's projects' sources.
Expand Down

0 comments on commit 127c961

Please sign in to comment.