diff --git a/appinventor/appengine/war/WEB-INF/test.html b/appinventor/appengine/war/WEB-INF/test.html
new file mode 100644
index 00000000000..4e6b2ff4104
--- /dev/null
+++ b/appinventor/appengine/war/WEB-INF/test.html
@@ -0,0 +1,93 @@
+
+
+
+
+ Global Asset Upload Tester
+
+
+
+ Global Asset Upload Tester
+
+
+ ⚠️ Important: Edit the form action
URL in this file before each upload.
+ The upload will go to the path:
+
+ http://localhost:8888/ode/upload/globalasset/_global_/your_folder/your_file.ext
+
+
+
+
+
+
+ This sends a POST request with the file field named uploadUserFile
as required by UploadServlet.
+
+
+
+ Currently uploading to path:
+ (loading...)
+
+
+
+
+
diff --git a/appinventor/appengine/war/WEB-INF/web.xml b/appinventor/appengine/war/WEB-INF/web.xml
index 8a5f6942b92..5db1e94b1aa 100644
--- a/appinventor/appengine/war/WEB-INF/web.xml
+++ b/appinventor/appengine/war/WEB-INF/web.xml
@@ -245,4 +245,30 @@
componentService
+
+ GlobalAssetService
+ com.google.appinventor.server.GlobalAssetServiceImpl
+
+
+ GlobalAssetService
+ /ode/globalassets
+
+
+ odeAuthFilter
+ GlobalAssetService
+
+
+
+ GlobalAssetDownloadServlet
+ com.google.appinventor.server.GlobalAssetServiceImpl
+
+
+ GlobalAssetDownloadServlet
+ /download/globalasset/*
+
+
+ odeAuthFilter
+ GlobalAssetDownloadServlet
+
+
diff --git a/appinventor/appengine/war/test.html b/appinventor/appengine/war/test.html
new file mode 100644
index 00000000000..b37d56cd61c
--- /dev/null
+++ b/appinventor/appengine/war/test.html
@@ -0,0 +1,121 @@
+
+
+
+ Global Asset Upload
+
+
+
+ Upload Global Asset
+
+
+
+
+
+
+
diff --git a/appinventor/components/src/com/google/appinventor/components/runtime/util/AppInvHTTPD.java b/appinventor/components/src/com/google/appinventor/components/runtime/util/AppInvHTTPD.java
index 4df2a730e98..f935d6251df 100644
--- a/appinventor/components/src/com/google/appinventor/components/runtime/util/AppInvHTTPD.java
+++ b/appinventor/components/src/com/google/appinventor/components/runtime/util/AppInvHTTPD.java
@@ -317,7 +317,21 @@ public void run() {
}
}
- return serveFile( uri, header, rootDir, true );
+ // Since rootDir already ends with "/assets/", we need to strip "assets/" from the beginning of URI
+ String adjustedUri = uri;
+ Log.d(LOG_TAG, "=== ASSET DEBUG === Original URI: " + uri);
+ Log.d(LOG_TAG, "Root directory: " + rootDir.getAbsolutePath());
+
+ if (adjustedUri.startsWith("/assets/")) {
+ adjustedUri = adjustedUri.substring(8); // Remove "/assets/" (8 characters)
+ Log.d(LOG_TAG, "Stripped /assets/, adjusted URI: " + adjustedUri);
+ } else if (adjustedUri.startsWith("assets/")) {
+ adjustedUri = adjustedUri.substring(7); // Remove "assets/" (7 characters)
+ Log.d(LOG_TAG, "Stripped assets/, adjusted URI: " + adjustedUri);
+ }
+
+ Log.d(LOG_TAG, "Final adjusted URI: " + adjustedUri + " ===");
+ return serveFile( adjustedUri, header, rootDir, true );
}
private boolean copyFile(File infile, File outfile) {
diff --git a/appinventor/components/src/com/google/appinventor/components/runtime/util/AssetFetcher.java b/appinventor/components/src/com/google/appinventor/components/runtime/util/AssetFetcher.java
index 6609f34c4b5..4aecdc683a3 100644
--- a/appinventor/components/src/com/google/appinventor/components/runtime/util/AssetFetcher.java
+++ b/appinventor/components/src/com/google/appinventor/components/runtime/util/AssetFetcher.java
@@ -283,6 +283,8 @@ public void run() {
* @return The destination file for the asset
*/
private static File getDestinationFile(Form form, String asset) {
+ Log.d(LOG_TAG, "*** ASSET FETCHER DEBUG *** asset: " + asset);
+
if (asset.contains("/external_comps/")
&& Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
File dest = new File(form.getCacheDir(), asset.substring("assets/".length()));
@@ -300,9 +302,19 @@ private static File getDestinationFile(Form form, String asset) {
String[] parts = asset.split("/");
filename = parts[parts.length - 1];
}
+ Log.d(LOG_TAG, "External comps path: " + new File(parent, filename).getAbsolutePath());
return new File(parent, filename);
}
- return new File(QUtil.getReplAssetPath(form, true), asset.substring("assets/".length()));
+
+ String adjustedAsset = asset.substring("assets/".length());
+ String replAssetPath = QUtil.getReplAssetPath(form, true);
+ File result = new File(replAssetPath, adjustedAsset);
+
+ Log.d(LOG_TAG, "Adjusted asset (after removing assets/): " + adjustedAsset);
+ Log.d(LOG_TAG, "ReplAssetPath: " + replAssetPath);
+ Log.d(LOG_TAG, "Final destination file: " + result.getAbsolutePath() + " ***");
+
+ return result;
}
private static String byteArray2Hex(final byte[] hash) {
diff --git a/appinventor/components/src/com/google/appinventor/components/runtime/util/MediaUtil.java b/appinventor/components/src/com/google/appinventor/components/runtime/util/MediaUtil.java
index 343fcf1fee3..1385ec46c76 100644
--- a/appinventor/components/src/com/google/appinventor/components/runtime/util/MediaUtil.java
+++ b/appinventor/components/src/com/google/appinventor/components/runtime/util/MediaUtil.java
@@ -108,16 +108,23 @@ static String fileUrlToFilePath(String mediaPath) throws IOException {
*/
@SuppressLint("SdCardPath")
private static MediaSource determineMediaSource(Form form, String mediaPath) {
+ Log.d(LOG_TAG, "*** MEDIA SOURCE DEBUG *** mediaPath: " + mediaPath);
+ Log.d(LOG_TAG, "Form type: " + form.getClass().getSimpleName());
+
if (mediaPath.startsWith(QUtil.getExternalStoragePath(form))
|| mediaPath.startsWith("/sdcard/")) {
+ Log.d(LOG_TAG, "Determined source: SDCARD");
return MediaSource.SDCARD;
} else if (mediaPath.startsWith("content://contacts/")) {
+ Log.d(LOG_TAG, "Determined source: CONTACT_URI");
return MediaSource.CONTACT_URI;
} else if (mediaPath.startsWith("content://")) {
+ Log.d(LOG_TAG, "Determined source: CONTENT_URI");
return MediaSource.CONTENT_URI;
} else if (mediaPath.startsWith("/data/")) {
+ Log.d(LOG_TAG, "Determined source: PRIVATE_DATA");
return MediaSource.PRIVATE_DATA;
}
@@ -126,11 +133,14 @@ private static MediaSource determineMediaSource(Form form, String mediaPath) {
// It's a well formed URL.
if (mediaPath.startsWith("file:")) {
if (url.getPath().startsWith("/android_asset/")) {
+ Log.d(LOG_TAG, "Determined source: ASSET");
return MediaSource.ASSET;
}
+ Log.d(LOG_TAG, "Determined source: FILE_URL");
return MediaSource.FILE_URL;
}
+ Log.d(LOG_TAG, "Determined source: URL");
return MediaSource.URL;
} catch (MalformedURLException e) {
@@ -138,12 +148,16 @@ private static MediaSource determineMediaSource(Form form, String mediaPath) {
}
if (form instanceof ReplForm) {
- if (((ReplForm)form).isAssetsLoaded())
+ if (((ReplForm)form).isAssetsLoaded()) {
+ Log.d(LOG_TAG, "Determined source: REPL_ASSET (assets loaded) ***");
return MediaSource.REPL_ASSET;
- else
+ } else {
+ Log.d(LOG_TAG, "Determined source: ASSET (assets not loaded) ***");
return MediaSource.ASSET;
+ }
}
+ Log.d(LOG_TAG, "Determined source: ASSET (default) ***");
return MediaSource.ASSET;
}
@@ -295,7 +309,13 @@ private static InputStream openMedia(Form form, String mediaPath, MediaSource me
form.assertPermission(READ_EXTERNAL_STORAGE);
}
try {
- return new FileInputStream(new java.io.File(URI.create(form.getAssetPath(mediaPath))));
+ // Strip "assets/" prefix if present, since getAssetPath() will add the appropriate path
+ String assetName = mediaPath;
+ if (mediaPath.startsWith("assets/")) {
+ assetName = mediaPath.substring("assets/".length());
+ Log.d(LOG_TAG, "Stripped assets/ prefix from " + mediaPath + " -> " + assetName);
+ }
+ return new FileInputStream(new java.io.File(URI.create(form.getAssetPath(assetName))));
} catch (Exception e) {
// URI.create can throw IllegalArgumentException under certain cirumstances
// on certain platforms. This crashes the Companion, which makes our crash
@@ -728,7 +748,12 @@ public static int loadSoundPool(SoundPool soundPool, Form form, String mediaPath
if (RUtil.needsFilePermission(form, mediaPath, null)) {
form.assertPermission(READ_EXTERNAL_STORAGE);
}
- return soundPool.load(fileUrlToFilePath(form.getAssetPath(mediaPath)), 1);
+ // Strip "assets/" prefix if present, since getAssetPath() will add the appropriate path
+ String assetName = mediaPath;
+ if (mediaPath.startsWith("assets/")) {
+ assetName = mediaPath.substring("assets/".length());
+ }
+ return soundPool.load(fileUrlToFilePath(form.getAssetPath(assetName)), 1);
case SDCARD:
if (RUtil.needsFilePermission(form, mediaPath, null)) {
@@ -788,7 +813,12 @@ public static void loadMediaPlayer(MediaPlayer mediaPlayer, Form form, String me
if (RUtil.needsFilePermission(form, mediaPath, null)) {
form.assertPermission(READ_EXTERNAL_STORAGE);
}
- mediaPlayer.setDataSource(fileUrlToFilePath(form.getAssetPath(mediaPath)));
+ // Strip "assets/" prefix if present, since getAssetPath() will add the appropriate path
+ String assetName = mediaPath;
+ if (mediaPath.startsWith("assets/")) {
+ assetName = mediaPath.substring("assets/".length());
+ }
+ mediaPlayer.setDataSource(fileUrlToFilePath(form.getAssetPath(assetName)));
return;
case SDCARD:
@@ -852,7 +882,12 @@ public static void loadVideoView(VideoView videoView, Form form, String mediaPat
if (RUtil.needsFilePermission(form, mediaPath, null)) {
form.assertPermission(READ_EXTERNAL_STORAGE);
}
- videoView.setVideoPath(fileUrlToFilePath(form.getAssetPath(mediaPath)));
+ // Strip "assets/" prefix if present, since getAssetPath() will add the appropriate path
+ String assetName = mediaPath;
+ if (mediaPath.startsWith("assets/")) {
+ assetName = mediaPath.substring("assets/".length());
+ }
+ videoView.setVideoPath(fileUrlToFilePath(form.getAssetPath(assetName)));
return;
case SDCARD: