Commit 183dcb18 authored by Jeffrey I. Schiller's avatar Jeffrey I. Schiller

Ensure asset names are unique on a case insenstive file system.

Change-Id: Idd7c4d8f3dc0bbb1e3ddaa1dcd49693d37f2e7f9
parent a433e3ab
...@@ -905,10 +905,10 @@ public interface OdeMessages extends Messages { ...@@ -905,10 +905,10 @@ public interface OdeMessages extends Messages {
@Description("Error message reported when a file was not selected.") @Description("Error message reported when a file was not selected.")
String noFileSelected(); String noFileSelected();
@DefaultMessage("A file named {0} already exists in this project. Do you want to overwrite " + @DefaultMessage("A file named {0} already exists in this project. Do you want to remove " +
"the old file?") "this old file? This will also remove any other files whose names conflict with {1}.")
@Description("Confirmation message shown when a file is about to be overwritten.") @Description("Confirmation message shown when conflicting files are about to be deleted.")
String confirmOverwrite(String filename); String confirmOverwrite(String newFile, String existingFile);
// Used in wizards/KeystoreUploadWizard.java // Used in wizards/KeystoreUploadWizard.java
......
...@@ -4,6 +4,8 @@ package com.google.appinventor.client.wizards; ...@@ -4,6 +4,8 @@ package com.google.appinventor.client.wizards;
import static com.google.appinventor.client.Ode.MESSAGES; import static com.google.appinventor.client.Ode.MESSAGES;
import java.io.File;
import com.google.appinventor.client.ErrorReporter; import com.google.appinventor.client.ErrorReporter;
import com.google.appinventor.client.Ode; import com.google.appinventor.client.Ode;
import com.google.appinventor.client.OdeAsyncCallback; import com.google.appinventor.client.OdeAsyncCallback;
...@@ -27,6 +29,7 @@ import com.google.gwt.user.client.rpc.AsyncCallback; ...@@ -27,6 +29,7 @@ import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwt.user.client.ui.FileUpload; import com.google.gwt.user.client.ui.FileUpload;
import com.google.gwt.user.client.ui.VerticalPanel; import com.google.gwt.user.client.ui.VerticalPanel;
/** /**
* Wizard for uploading individual files. * Wizard for uploading individual files.
* *
...@@ -87,12 +90,32 @@ public class FileUploadWizard extends Wizard { ...@@ -87,12 +90,32 @@ public class FileUploadWizard extends Wizard {
Window.alert(MESSAGES.filenameBadSize()); Window.alert(MESSAGES.filenameBadSize());
return; return;
} }
if (fileAlreadyExists(folderNode, filename)) { String fn = conflictingExistingFile(folderNode, filename);
if (!confirmOverwrite(folderNode, filename)) { if (fn != null && !confirmOverwrite(folderNode, fn, filename)) {
return; return;
} else {
String fileId = folderNode.getFileId() + "/" + filename;
// We delete all the conflicting files.
for (ProjectNode child : folderNode.getChildren()) {
if (fileId.equalsIgnoreCase(child.getFileId()) && !fileId.equals(child.getFileId())) {
final ProjectNode node = child;
Ode ode = Ode.getInstance();
ode.getEditorManager().closeFileEditor(node.getProjectId(), node.getFileId());
ode.getProjectService().deleteFile(
node.getProjectId(), node.getFileId(),
new OdeAsyncCallback<Long>(
// message on failure
MESSAGES.deleteFileError()) {
@Override
public void onSuccess(Long date) {
Ode.getInstance().getProjectManager().getProject(node).deleteNode(node);
Ode.getInstance().updateModificationDate(node.getProjectId(), date);
}
});
}
} }
} }
ErrorReporter.reportInfo(MESSAGES.fileUploadingMessage(filename)); ErrorReporter.reportInfo(MESSAGES.fileUploadingMessage(filename));
// Use the folderNode's project id and file id in the upload URL so that the file is // Use the folderNode's project id and file id in the upload URL so that the file is
...@@ -102,25 +125,25 @@ public class FileUploadWizard extends Wizard { ...@@ -102,25 +125,25 @@ public class FileUploadWizard extends Wizard {
folderNode.getFileId() + "/" + filename; folderNode.getFileId() + "/" + filename;
Uploader.getInstance().upload(upload, uploadUrl, Uploader.getInstance().upload(upload, uploadUrl,
new OdeAsyncCallback<UploadResponse>(MESSAGES.fileUploadError()) { new OdeAsyncCallback<UploadResponse>(MESSAGES.fileUploadError()) {
@Override @Override
public void onSuccess(UploadResponse uploadResponse) { public void onSuccess(UploadResponse uploadResponse) {
switch (uploadResponse.getStatus()) { switch (uploadResponse.getStatus()) {
case SUCCESS: case SUCCESS:
ErrorReporter.hide(); ErrorReporter.hide();
onUploadSuccess(folderNode, filename, uploadResponse.getModificationDate(), onUploadSuccess(folderNode, filename, uploadResponse.getModificationDate(),
fileUploadedCallback); fileUploadedCallback);
break; break;
case FILE_TOO_LARGE: case FILE_TOO_LARGE:
// The user can resolve the problem by // The user can resolve the problem by
// uploading a smaller file. // uploading a smaller file.
ErrorReporter.reportInfo(MESSAGES.fileTooLargeError()); ErrorReporter.reportInfo(MESSAGES.fileTooLargeError());
break; break;
default: default:
ErrorReporter.reportError(MESSAGES.fileUploadError()); ErrorReporter.reportError(MESSAGES.fileUploadError());
break; break;
} }
} }
}); });
} else { } else {
Window.alert(MESSAGES.noFileSelected()); Window.alert(MESSAGES.noFileSelected());
new FileUploadWizard(folderNode, fileUploadedCallback).show(); new FileUploadWizard(folderNode, fileUploadedCallback).show();
...@@ -150,18 +173,24 @@ public class FileUploadWizard extends Wizard { ...@@ -150,18 +173,24 @@ public class FileUploadWizard extends Wizard {
return filename; return filename;
} }
private boolean fileAlreadyExists(FolderNode folderNode, String filename) { private String conflictingExistingFile(FolderNode folderNode, String filename) {
String fileId = folderNode.getFileId() + "/" + filename; String fileId = folderNode.getFileId() + "/" + filename;
for (ProjectNode child : folderNode.getChildren()) { for (ProjectNode child : folderNode.getChildren()) {
if (fileId.equals(child.getFileId())) { if (fileId.equalsIgnoreCase(child.getFileId())) {
return true; // we want to return kitty.png rather than assets/kitty.png
return lastPathComponent(child.getFileId());
} }
} }
return false; return null;
}
private String lastPathComponent (String path) {
String [] pieces = path.split("/");
return pieces[pieces.length - 1];
} }
private boolean confirmOverwrite(FolderNode folderNode, String filename) { private boolean confirmOverwrite(FolderNode folderNode, String newFile, String existingFile) {
return Window.confirm(MESSAGES.confirmOverwrite(filename)); return Window.confirm(MESSAGES.confirmOverwrite(newFile, existingFile));
} }
private void onUploadSuccess(final FolderNode folderNode, final String filename, private void onUploadSuccess(final FolderNode folderNode, final String filename,
......
...@@ -27,6 +27,7 @@ import java.io.FileInputStream; ...@@ -27,6 +27,7 @@ import java.io.FileInputStream;
import java.io.FilterInputStream; import java.io.FilterInputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.lang.reflect.Array;
import java.net.MalformedURLException; import java.net.MalformedURLException;
import java.net.URL; import java.net.URL;
import java.util.HashMap; import java.util.HashMap;
...@@ -116,11 +117,46 @@ public class MediaUtil { ...@@ -116,11 +117,46 @@ public class MediaUtil {
return MediaSource.ASSET; return MediaSource.ASSET;
} }
private static String findCaseinsensitivePath(Form form, String mediaPath)
throws IOException{
String[] mediaPathlist = form.getAssets().list("");
int l = Array.getLength(mediaPathlist);
for (int i=0; i<l; i++){
String temp = mediaPathlist[i];
if (temp.equalsIgnoreCase(mediaPath)){
return temp;
}
}
return null;
}
/**
* find path of an asset from a mediaPath using case-insensitive comparison,
* return type InputStream.
* Throws IOException if there is no matching path
* @param form the Form
* @param mediaPath the path to the media
*/
private static InputStream getAssetsIgnoreCaseInputStream(Form form, String mediaPath)
throws IOException{
try {
return form.getAssets().open(mediaPath);
} catch (IOException e) {
if (findCaseinsensitivePath(form, mediaPath) == null){
throw e;
} else {
String path = findCaseinsensitivePath(form, mediaPath);
return form.getAssets().open(path);
}
}
}
private static InputStream openMedia(Form form, String mediaPath, MediaSource mediaSource) private static InputStream openMedia(Form form, String mediaPath, MediaSource mediaSource)
throws IOException { throws IOException {
switch (mediaSource) { switch (mediaSource) {
case ASSET: case ASSET:
return form.getAssets().open(mediaPath); return getAssetsIgnoreCaseInputStream(form,mediaPath);
case REPL_ASSET: case REPL_ASSET:
return new FileInputStream(replAssetPath(mediaPath)); return new FileInputStream(replAssetPath(mediaPath));
...@@ -319,6 +355,28 @@ public class MediaUtil { ...@@ -319,6 +355,28 @@ public class MediaUtil {
// SoundPool related methods // SoundPool related methods
/**
* find path of an asset from a mediaPath using case-insensitive comparison,
* return AssetFileDescriptor of that asset
* Throws IOException if there is no matching path
* @param form the Form
* @param mediaPath the path to the media
*/
private static AssetFileDescriptor getAssetsIgnoreCaseAfd(Form form, String mediaPath)
throws IOException{
try {
return form.getAssets().openFd(mediaPath);
} catch (IOException e) {
if (findCaseinsensitivePath(form, mediaPath) == null){
throw e;
} else {
String path = findCaseinsensitivePath(form, mediaPath);
return form.getAssets().openFd(path);
}
}
}
/** /**
* Loads the audio specified by mediaPath into the given SoundPool and * Loads the audio specified by mediaPath into the given SoundPool and
* returns the sound id. * returns the sound id.
...@@ -336,7 +394,7 @@ public class MediaUtil { ...@@ -336,7 +394,7 @@ public class MediaUtil {
MediaSource mediaSource = determineMediaSource(form, mediaPath); MediaSource mediaSource = determineMediaSource(form, mediaPath);
switch (mediaSource) { switch (mediaSource) {
case ASSET: case ASSET:
return soundPool.load(form.getAssets().openFd(mediaPath), 1); return soundPool.load(getAssetsIgnoreCaseAfd(form,mediaPath), 1);
case REPL_ASSET: case REPL_ASSET:
return soundPool.load(replAssetPath(mediaPath), 1); return soundPool.load(replAssetPath(mediaPath), 1);
...@@ -374,7 +432,7 @@ public class MediaUtil { ...@@ -374,7 +432,7 @@ public class MediaUtil {
MediaSource mediaSource = determineMediaSource(form, mediaPath); MediaSource mediaSource = determineMediaSource(form, mediaPath);
switch (mediaSource) { switch (mediaSource) {
case ASSET: case ASSET:
AssetFileDescriptor afd = form.getAssets().openFd(mediaPath); AssetFileDescriptor afd = getAssetsIgnoreCaseAfd(form,mediaPath);
try { try {
FileDescriptor fd = afd.getFileDescriptor(); FileDescriptor fd = afd.getFileDescriptor();
long offset = afd.getStartOffset(); long offset = afd.getStartOffset();
...@@ -385,6 +443,7 @@ public class MediaUtil { ...@@ -385,6 +443,7 @@ public class MediaUtil {
} }
return; return;
case REPL_ASSET: case REPL_ASSET:
mediaPlayer.setDataSource(replAssetPath(mediaPath)); mediaPlayer.setDataSource(replAssetPath(mediaPath));
return; return;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment