Repository: gradio-app/gradio
Pull request: Support large files try 2
Rewrote large file issue fixer with @freddyaboulton's approach. When a file is uploaded to FileUpload, it is automatically sent to the backend, and then the frontend receives a reference to the name of this new file. Much cleaner, thanks @freddyaboulton!

Fixes: #2421

Problem statement:
Large input files freeze/crash the interface
### Describe the bug

Hi, In my usecase a user uploads a rather large file (300-500mb) and it freezes the app for several seconds which makes for a very bad user experience. It happens after selecting a large file to a gradio File block. It freezes again when pressing a button that processes this data. Is there a way to circumvent this behaviour? It is running locally, as far as a I know are those sizes not supported anyway non-locally right? Currently am hosting my app on AWS. Would be cool if you could fix this behaviour, very much appreciated :)

### Is there an existing issue for this?

- [X] I have searched the existing issues

### Reproduction
```
def test():
    return

with gr.Blocks() as demo:
    inp = gr.File(label="Input")
    vis_out=gr.File(label="output")
    with gr.Row():
        btn = gr.Button("Run")
    btn.click(fn=test, inputs=[inp], outputs=[vis_out])
demo.launch() 
```
Then just add a large file as the input.

### Screenshot

_No response_

### Logs

```shell
no logs
```


### System Info

```shell
gradio v 3.4.0, occurs with different os on which the app is hosted,e.g. linux and mac 
also across browsers, chrome takes a long time, mozilla even crashes
```


### Severity

serious, but I can work around it

Hints from issue discussion:
Could you give some more info about what you'd like to do with these large files when you have them? How we handle the files that are loaded by users dictates what you can do with it. 

I haven't looked into this too much but I'm guessing that this is happening because we load the file into memory, in full. If the file were bigger 1GB+ it would probably consistently crash the browser. 
Thanks for the fast reply! it is a medical file which I want to upload and perform inference (classification/segmentation) on, it does not need to be stored in memory for this i guess.
Also it takes very long until this file is uploaded, not sure if this is related. The demo i am doing will be used in a clinical study and speed really matters. Would be awesome if you could help me out there :) would be also happy to promote gradio in some way!
In this case we can (probably) chunk/ stream the data from the client without needing to store the whole thing in memory at any point. This would resolve the browser issues. I think it would still take a while to upload but maybe something else is going on that is slowing that down too. 

The challenge is figuring out when to hold things in memory and when not to. There are only circumstance in which _not_ holding things in memory makes sense, from gradio's point of view.
Maybe you could add a load_to_memory flag that is default True to the File class? So that  the user can decide? Would be great! For the upload I am not sure what the bottleneck is, directly uploading to AWS server via scp command takes a few seconds, over gradio up to 2 minutes. Not sure what the bottleneck there is.
any feedback on this @pngwn ?
or @abidlabs ? would be very much appreciated
We haven't had the chance to look into it yet, we'll update here when we have more to add.
I worked around that and the fact that I cant upload files larger than 500mb by creating my own upload to aws s3 via html, but it took me quite a while.. fixing this would be a major upgrade!
+1 to the request! Tried implementing a model uploader with `gradio.File`, but it unsurprisingly crashes the app when uploading 5GB model checkpoints :sweat_smile:

Commit message: changes

Patch to review:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index a1b1190ff9..be821ff653 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -38,6 +38,7 @@ By [@freddyaboulton](https://github.com/freddyaboulton) in [PR 3157](https://git
 * Fixes `gr.utils.delete_none` to only remove props whose values are `None` from the config by [@abidlabs](https://github.com/abidlabs) in [PR 3188](https://github.com/gradio-app/gradio/pull/3188) 
 * Fix bug where embedded demos were not loading files properly by [@freddyaboulton](https://github.com/freddyaboulton) in [PR 3177](https://github.com/gradio-app/gradio/pull/3177)
 * The `change` event is now triggered when users click the 'Clear All' button of the multiselect DropDown component by [@freddyaboulton](https://github.com/freddyaboulton) in [PR 3195](https://github.com/gradio-app/gradio/pull/3195)
+* Stops File component from freezing when a large file is uploaded by [@aliabid94](https://github.com/aliabid94) in [PR 3191](https://github.com/gradio-app/gradio/pull/3191)
 * Support Chinese pinyin in Dataframe by [@aliabid94](https://github.com/aliabid94) in [PR 3206](https://github.com/gradio-app/gradio/pull/3206)
 
 ## Documentation Changes:
diff --git a/demo/zip_files/.gitignore b/demo/zip_files/.gitignore
new file mode 100644
index 0000000000..8e9b8f2027
--- /dev/null
+++ b/demo/zip_files/.gitignore
@@ -0,0 +1 @@
+tmp.zip
\ No newline at end of file
diff --git a/demo/zip_files/run.ipynb b/demo/zip_files/run.ipynb
index f8f70a15cb..21810513c7 100644
--- a/demo/zip_files/run.ipynb
+++ b/demo/zip_files/run.ipynb
@@ -1 +1 @@
-{"cells": [{"cell_type": "markdown", "id": 302934307671667531413257853548643485645, "metadata": {}, "source": ["# Gradio Demo: zip_files"]}, {"cell_type": "code", "execution_count": null, "id": 272996653310673477252411125948039410165, "metadata": {}, "outputs": [], "source": ["!pip install -q gradio "]}, {"cell_type": "code", "execution_count": null, "id": 288918539441861185822528903084949547379, "metadata": {}, "outputs": [], "source": ["# Downloading files from the demo repo\n", "import os\n", "os.mkdir('files')\n", "!wget -q -O files/titanic.csv https://github.com/gradio-app/gradio/raw/main/demo/zip_files/files/titanic.csv\n", "!wget -q https://github.com/gradio-app/gradio/raw/main/demo/zip_files/tmp.zip"]}, {"cell_type": "code", "execution_count": null, "id": 44380577570523278879349135829904343037, "metadata": {}, "outputs": [], "source": ["import os\n", "from zipfile import ZipFile\n", "\n", "import gradio as gr\n", "\n", "\n", "def zip_files(files):\n", "    with ZipFile(\"tmp.zip\", \"w\") as zipObj:\n", "        for idx, file in enumerate(files):\n", "            zipObj.write(file.name, \"file\" + str(idx))\n", "    return \"tmp.zip\"\n", "\n", "demo = gr.Interface(\n", "    zip_files,\n", "    gr.File(file_count=\"multiple\", file_types=[\"text\", \".json\", \".csv\"]),\n", "    \"file\",\n", "    examples=[[[os.path.join(os.path.abspath(''),\"files/titanic.csv\"), \n", "    os.path.join(os.path.abspath(''),\"files/titanic.csv\"), \n", "    os.path.join(os.path.abspath(''),\"files/titanic.csv\")]]], \n", "    cache_examples=True\n", ")\n", "\n", "if __name__ == \"__main__\":\n", "    demo.launch()\n"]}], "metadata": {}, "nbformat": 4, "nbformat_minor": 5}
\ No newline at end of file
+{"cells": [{"cell_type": "markdown", "id": 302934307671667531413257853548643485645, "metadata": {}, "source": ["# Gradio Demo: zip_files"]}, {"cell_type": "code", "execution_count": null, "id": 272996653310673477252411125948039410165, "metadata": {}, "outputs": [], "source": ["!pip install -q gradio "]}, {"cell_type": "code", "execution_count": null, "id": 288918539441861185822528903084949547379, "metadata": {}, "outputs": [], "source": ["# Downloading files from the demo repo\n", "import os\n", "os.mkdir('files')\n", "!wget -q -O files/titanic.csv https://github.com/gradio-app/gradio/raw/main/demo/zip_files/files/titanic.csv"]}, {"cell_type": "code", "execution_count": null, "id": 44380577570523278879349135829904343037, "metadata": {}, "outputs": [], "source": ["import os\n", "from zipfile import ZipFile\n", "\n", "import gradio as gr\n", "\n", "\n", "def zip_files(files):\n", "    with ZipFile(\"tmp.zip\", \"w\") as zipObj:\n", "        for idx, file in enumerate(files):\n", "            zipObj.write(file.name, file.name.split(\"/\")[-1])\n", "    return \"tmp.zip\"\n", "\n", "demo = gr.Interface(\n", "    zip_files,\n", "    gr.File(file_count=\"multiple\", file_types=[\"text\", \".json\", \".csv\"]),\n", "    \"file\",\n", "    examples=[[[os.path.join(os.path.abspath(''),\"files/titanic.csv\"), \n", "    os.path.join(os.path.abspath(''),\"files/titanic.csv\"), \n", "    os.path.join(os.path.abspath(''),\"files/titanic.csv\")]]], \n", "    cache_examples=True\n", ")\n", "\n", "if __name__ == \"__main__\":\n", "    demo.launch()\n"]}], "metadata": {}, "nbformat": 4, "nbformat_minor": 5}
\ No newline at end of file
diff --git a/demo/zip_files/run.py b/demo/zip_files/run.py
index 191bf4aab4..1ae6436c67 100644
--- a/demo/zip_files/run.py
+++ b/demo/zip_files/run.py
@@ -7,7 +7,7 @@
 def zip_files(files):
     with ZipFile("tmp.zip", "w") as zipObj:
         for idx, file in enumerate(files):
-            zipObj.write(file.name, "file" + str(idx))
+            zipObj.write(file.name, file.name.split("/")[-1])
     return "tmp.zip"
 
 demo = gr.Interface(
diff --git a/demo/zip_files/tmp.zip b/demo/zip_files/tmp.zip
deleted file mode 100644
index c98df4f931..0000000000
Binary files a/demo/zip_files/tmp.zip and /dev/null differ
diff --git a/gradio/processing_utils.py b/gradio/processing_utils.py
index e228d96b2f..7837d9628c 100644
--- a/gradio/processing_utils.py
+++ b/gradio/processing_utils.py
@@ -15,8 +15,10 @@
 from pathlib import Path
 from typing import Dict, Set, Tuple
 
+import aiofiles
 import numpy as np
 import requests
+from fastapi import UploadFile
 from ffmpy import FFmpeg, FFprobe, FFRuntimeError
 from PIL import Image, ImageOps, PngImagePlugin
 
@@ -403,6 +405,19 @@ def make_temp_copy_if_needed(self, file_path: str) -> str:
         self.temp_files.add(full_temp_file_path)
         return full_temp_file_path
 
+    async def save_uploaded_file(self, file: UploadFile, upload_dir: str) -> str:
+        prefix, extension = self.get_prefix_and_extension(file.filename)
+        output_file_obj = tempfile.NamedTemporaryFile(
+            delete=False, dir=upload_dir, suffix=f"{extension}", prefix=f"{prefix}_"
+        )
+        async with aiofiles.open(output_file_obj.name, "wb") as output_file:
+            while True:
+                content = await file.read(100 * 1024 * 1024)
+                if not content:
+                    break
+                await output_file.write(content)
+        return str(utils.abspath(output_file_obj.name))
+
     def download_temp_copy_if_needed(self, url: str) -> str:
         """Downloads a file and makes a temporary file path for a copy if does not already
         exist. Otherwise returns the path to the existing temp file."""
diff --git a/gradio/routes.py b/gradio/routes.py
index 1570e1e4b8..89babb0732 100644
--- a/gradio/routes.py
+++ b/gradio/routes.py
@@ -10,6 +10,7 @@
 import os
 import posixpath
 import secrets
+import tempfile
 import traceback
 from collections import defaultdict
 from copy import deepcopy
@@ -21,7 +22,7 @@
 import markupsafe
 import orjson
 import pkg_resources
-from fastapi import Depends, FastAPI, HTTPException, WebSocket, status
+from fastapi import Depends, FastAPI, File, HTTPException, UploadFile, WebSocket, status
 from fastapi.middleware.cors import CORSMiddleware
 from fastapi.responses import (
     FileResponse,
@@ -43,6 +44,7 @@
 from gradio.data_classes import PredictBody, ResetBody
 from gradio.documentation import document, set_documentation_group
 from gradio.exceptions import Error
+from gradio.processing_utils import TempFileManager
 from gradio.queueing import Estimation, Event
 from gradio.utils import cancel_tasks, run_coro_in_background, set_task_name
 
@@ -109,6 +111,7 @@ def __init__(self, **kwargs):
         self.lock = asyncio.Lock()
         self.queue_token = secrets.token_urlsafe(32)
         self.startup_events_triggered = False
+        self.uploaded_file_dir = str(utils.abspath(tempfile.mkdtemp()))
         super().__init__(**kwargs)
 
     def configure_app(self, blocks: gradio.Blocks) -> None:
@@ -292,17 +295,16 @@ async def file(path_or_url: str, request: fastapi.Request):
                 )
             abs_path = str(utils.abspath(path_or_url))
             in_app_dir = utils.abspath(app.cwd) in utils.abspath(path_or_url).parents
-            created_by_app = str(utils.abspath(path_or_url)) in set().union(
-                *blocks.temp_file_sets
-            )
+            created_by_app = abs_path in set().union(*blocks.temp_file_sets)
             in_file_dir = any(
                 (
                     utils.abspath(dir) in utils.abspath(path_or_url).parents
                     for dir in blocks.file_directories
                 )
             )
+            was_uploaded = abs_path.startswith(app.uploaded_file_dir)
 
-            if in_app_dir or created_by_app or in_file_dir:
+            if in_app_dir or created_by_app or in_file_dir or was_uploaded:
                 range_val = request.headers.get("Range", "").strip()
                 if range_val.startswith("bytes=") and "-" in range_val:
                     range_val = range_val[6:]
@@ -509,6 +511,20 @@ async def join_queue(
         async def get_queue_status():
             return app.get_blocks()._queue.get_estimation()
 
+        @app.post("/upload", dependencies=[Depends(login_check)])
+        async def upload_file(
+            files: List[UploadFile] = File(...),
+        ):
+            output_files = []
+            file_manager = TempFileManager()
+            for input_file in files:
+                output_files.append(
+                    await file_manager.save_uploaded_file(
+                        input_file, app.uploaded_file_dir
+                    )
+                )
+            return output_files
+
         @app.on_event("startup")
         @app.get("/startup-events")
         async def startup_events():
diff --git a/test/test_files/alphabet.txt b/test/test_files/alphabet.txt
new file mode 100644
index 0000000000..e85d5b4528
--- /dev/null
+++ b/test/test_files/alphabet.txt
@@ -0,0 +1 @@
+abcdefghijklmnopqrstuvwxyz
\ No newline at end of file
diff --git a/test/test_routes.py b/test/test_routes.py
index 8934113f6e..3d4b05ef3b 100644
--- a/test/test_routes.py
+++ b/test/test_routes.py
@@ -54,6 +54,15 @@ def test_get_config_route(self, test_client):
         response = test_client.get("/config/")
         assert response.status_code == 200
 
+    def test_upload_route(self, test_client):
+        response = test_client.post(
+            "/upload", files={"files": open("test/test_files/alphabet.txt", "r")}
+        )
+        assert response.status_code == 200
+        files = response.json()
+        with open(files[0]) as saved_file:
+            assert saved_file.read() == "abcdefghijklmnopqrstuvwxyz"
+
     def test_predict_route(self, test_client):
         response = test_client.post(
             "/api/predict/", json={"data": ["test"], "fn_index": 0}
diff --git a/ui/packages/app/src/api.ts b/ui/packages/app/src/api.ts
index ce7605d900..1d27a28745 100644
--- a/ui/packages/app/src/api.ts
+++ b/ui/packages/app/src/api.ts
@@ -36,6 +36,10 @@ interface PostResponse {
 	error?: string;
 	[x: string]: any;
 }
+export interface UploadResponse {
+	error?: string;
+	files?: Array<string>;
+}
 const QUEUE_FULL_MSG = "This application is too busy. Keep trying!";
 const BROKEN_CONNECTION_MSG = "Connection errored out.";
 
@@ -55,6 +59,27 @@ export async function post_data(
 	const output: PostResponse = await response.json();
 	return [output, response.status];
 }
+
+export async function upload_files(
+	root: string,
+	files: Array<File>
+): Promise<UploadResponse> {
+	const formData = new FormData();
+	files.forEach((file) => {
+		formData.append("files", file);
+	});
+	try {
+		var response = await fetch(`${root}upload`, {
+			method: "POST",
+			body: formData
+		});
+	} catch (e) {
+		return { error: BROKEN_CONNECTION_MSG };
+	}
+	const output: UploadResponse["files"] = await response.json();
+	return { files: output };
+}
+
 interface UpdateOutput {
 	__type__: string;
 	[key: string]: unknown;
diff --git a/ui/packages/app/src/components/Dataset/ExampleComponents/File.svelte b/ui/packages/app/src/components/Dataset/ExampleComponents/File.svelte
index 4ea8f985cc..79d869a605 100644
--- a/ui/packages/app/src/components/Dataset/ExampleComponents/File.svelte
+++ b/ui/packages/app/src/components/Dataset/ExampleComponents/File.svelte
@@ -1,5 +1,5 @@
 <script lang="ts">
-	import type { FileData } from "../../File/types";
+	import type { FileData } from "@gradio/upload";
 
 	export let value: FileData;
 	export let type: "gallery" | "table";
diff --git a/ui/packages/app/src/components/File/File.svelte b/ui/packages/app/src/components/File/File.svelte
index 897d06a9b3..cac8ad40cd 100644
--- a/ui/packages/app/src/components/File/File.svelte
+++ b/ui/packages/app/src/components/File/File.svelte
@@ -1,10 +1,11 @@
 <script lang="ts">
 	import { createEventDispatcher } from "svelte";
-	import { File, FileUpload } from "@gradio/file";
-	import type { FileData } from "@gradio/upload";
+	import { File as FileComponent, FileUpload } from "@gradio/file";
+	import { blobToBase64, FileData } from "@gradio/upload";
 	import { normalise_file } from "@gradio/upload";
 	import { Block } from "@gradio/atoms";
 	import UploadText from "../UploadText.svelte";
+	import { upload_files } from "../../api";
 
 	import StatusTracker from "../StatusTracker/StatusTracker.svelte";
 	import type { LoadingStatus } from "../StatusTracker/types";
@@ -26,20 +27,56 @@
 
 	export let loading_status: LoadingStatus;
 
-	let _value: null | FileData | Array<FileData>;
 	$: _value = normalise_file(value, root, root_url);
 
 	let dragging = false;
+	let pending_upload = false;
 
 	const dispatch = createEventDispatcher<{
 		change: undefined;
+		error: string;
 	}>();
 
 	$: {
-		if (value !== old_value) {
-			old_value = value;
+		if (_value !== old_value) {
+			old_value = _value;
+			if (_value === null) {
+				dispatch("change");
+				pending_upload = false;
+			} else if (mode === "dynamic") {
+				let files = (Array.isArray(_value) ? _value : [_value]).map(
+					(file_data) => file_data.blob!
+				);
+				let upload_value = _value;
+				pending_upload = true;
+				upload_files(root, files).then((response) => {
+					if (upload_value !== _value) {
+						// value has changed since upload started
+						return;
+					}
 
-			dispatch("change");
+					pending_upload = false;
+					if (response.error) {
+						(Array.isArray(_value) ? _value : [_value]).forEach(
+							async (file_data, i) => {
+								file_data.data = await blobToBase64(file_data.blob!);
+							}
+						);
+					} else {
+						(Array.isArray(_value) ? _value : [_value]).forEach(
+							(file_data, i) => {
+								if (response.files) {
+									file_data.orig_name = file_data.name;
+									file_data.name = response.files[i];
+									file_data.is_file = true;
+								}
+							}
+						);
+						_value = normalise_file(value, root, root_url);
+					}
+					dispatch("change");
+				});
+			}
 		}
 	}
 </script>
@@ -51,7 +88,12 @@
 	padding={false}
 	{elem_id}
 >
-	<StatusTracker {...loading_status} />
+	<StatusTracker
+		{...loading_status}
+		status={pending_upload
+			? "generating"
+			: loading_status?.status || "complete"}
+	/>
 
 	{#if mode === "dynamic"}
 		<FileUpload
@@ -68,6 +110,6 @@
 			<UploadText type="file" />
 		</FileUpload>
 	{:else}
-		<File value={_value} {label} {show_label} />
+		<FileComponent value={_value} {label} {show_label} />
 	{/if}
 </Block>
diff --git a/ui/packages/app/src/components/File/types.ts b/ui/packages/app/src/components/File/types.ts
deleted file mode 100644
index 97f5abcdc2..0000000000
--- a/ui/packages/app/src/components/File/types.ts
+++ /dev/null
@@ -1,6 +0,0 @@
-export interface FileData {
-	name: string;
-	size: number;
-	data: string;
-	is_example: false;
-}
diff --git a/ui/packages/app/src/components/UploadButton/UploadButton.svelte b/ui/packages/app/src/components/UploadButton/UploadButton.svelte
index bd6e391070..5b60a04b64 100644
--- a/ui/packages/app/src/components/UploadButton/UploadButton.svelte
+++ b/ui/packages/app/src/components/UploadButton/UploadButton.svelte
@@ -3,21 +3,34 @@
 	import type { Styles } from "@gradio/utils";
 	import type { FileData } from "@gradio/upload";
 	import { UploadButton } from "@gradio/upload-button";
+	import { upload_files } from "../../api";
+	import { blobToBase64 } from "@gradio/upload";
 	import { _ } from "svelte-i18n";
 
 	export let style: Styles = {};
 	export let elem_id: string = "";
 	export let visible: boolean = true;
 	export let label: string;
-	export let value: null | FileData | Array<FileData>;
+	export let value: null | FileData;
 	export let file_count: string;
 	export let file_types: Array<string> = ["file"];
+	export let root: string;
 
 	async function handle_upload({ detail }: CustomEvent<FileData>) {
 		value = detail;
+		console.log(detail);
 		await tick();
-		dispatch("change", value);
-		dispatch("upload", detail);
+		upload_files(root, [detail.blob!]).then(async (response) => {
+			if (response.error) {
+				detail.data = await blobToBase64(detail.blob!);
+			} else {
+				detail.orig_name = detail.name;
+				detail.name = response.files![0];
+				detail.is_file = true;
+			}
+			dispatch("change", value);
+			dispatch("upload", detail);
+		});
 	}
 
 	const dispatch = createEventDispatcher<{
diff --git a/ui/packages/file/src/File.svelte b/ui/packages/file/src/File.svelte
index fc8440a17f..4befa7a401 100644
--- a/ui/packages/file/src/File.svelte
+++ b/ui/packages/file/src/File.svelte
@@ -21,6 +21,6 @@
 
 <style>
 	.file-preview {
-		overflow-x: scroll;
+		overflow-x: auto;
 	}
 </style>
diff --git a/ui/packages/file/src/FilePreview.svelte b/ui/packages/file/src/FilePreview.svelte
index 6c0e6edeb8..cbd8be224b 100644
--- a/ui/packages/file/src/FilePreview.svelte
+++ b/ui/packages/file/src/FilePreview.svelte
@@ -2,11 +2,7 @@
 	import type { FileData } from "@gradio/upload";
 	import { Download } from "@gradio/icons";
 	import { IconButton } from "@gradio/atoms";
-	import {
-		display_file_name,
-		download_files,
-		display_file_size
-	} from "./utils";
+	import { display_file_name, display_file_size } from "./utils";
 
 	export let value: FileData | FileData[];
 </script>
@@ -24,13 +20,19 @@
 				</td>
 
 				<td class="download">
-					<a
-						href={download_files(file)}
-						target={window.__is_colab__ ? "_blank" : null}
-						download={window.__is_colab__ ? null : file.orig_name || file.name}
-					>
-						Download
-					</a>
+					{#if file.data}
+						<a
+							href={file.data}
+							target={window.__is_colab__ ? "_blank" : null}
+							download={window.__is_colab__
+								? null
+								: file.orig_name || file.name}
+						>
+							Download
+						</a>
+					{:else}
+						Uploading...
+					{/if}
 				</td>
 			</tr>
 		{/each}
@@ -52,7 +54,7 @@
 		margin-bottom: var(--size-7);
 		width: var(--size-full);
 		max-height: var(--size-60);
-		overflow-y: scroll;
+		overflow-y: auto;
 		color: var(--color-text-body);
 	}
 	.file {
diff --git a/ui/packages/file/src/FileUpload.svelte b/ui/packages/file/src/FileUpload.svelte
index 0f195e6c05..2089245444 100644
--- a/ui/packages/file/src/FileUpload.svelte
+++ b/ui/packages/file/src/FileUpload.svelte
@@ -14,7 +14,9 @@
 	export let file_count: string = "single";
 	export let file_types: string[] | null = null;
 
-	async function handle_upload({ detail }: CustomEvent<FileData>) {
+	async function handle_upload({
+		detail
+	}: CustomEvent<FileData | Array<FileData>>) {
 		value = detail;
 		await tick();
 		dispatch("change", value);
@@ -28,10 +30,10 @@
 	}
 
 	const dispatch = createEventDispatcher<{
-		change: FileData | null;
+		change: Array<FileData> | FileData | null;
 		clear: undefined;
 		drag: boolean;
-		upload: FileData;
+		upload: Array<FileData> | FileData;
 		error: string;
 	}>();
 
@@ -55,16 +57,17 @@
 
 <BlockLabel {show_label} Icon={File} label={label || "File"} />
 
-{#if value === null}
+{#if value}
+	<ModifyUpload on:clear={handle_clear} absolute />
+	<FilePreview {value} />
+{:else}
 	<Upload
 		on:load={handle_upload}
 		filetype={accept_file_types}
+		parse_to_data_url={false}
 		{file_count}
 		bind:dragging
 	>
 		<slot />
 	</Upload>
-{:else}
-	<ModifyUpload on:clear={handle_clear} absolute />
-	<FilePreview {value} />
 {/if}
diff --git a/ui/packages/file/src/utils.ts b/ui/packages/file/src/utils.ts
index 30401457e3..5fc7753eee 100644
--- a/ui/packages/file/src/utils.ts
+++ b/ui/packages/file/src/utils.ts
@@ -19,10 +19,6 @@ export const display_file_name = (value: FileData): string => {
 	} else return str;
 };
 
-export const download_files = (value: FileData): string => {
-	return value.data;
-};
-
 export const display_file_size = (
 	value: FileData | Array<FileData>
 ): string => {
diff --git a/ui/packages/model3D/src/Model3D.svelte b/ui/packages/model3D/src/Model3D.svelte
index 430c04be31..bac77b6b17 100644
--- a/ui/packages/model3D/src/Model3D.svelte
+++ b/ui/packages/model3D/src/Model3D.svelte
@@ -2,7 +2,6 @@
 	import type { FileData } from "@gradio/upload";
 	import { BlockLabel, IconButton } from "@gradio/atoms";
 	import { File, Download } from "@gradio/icons";
-	import { download_files } from "./utils";
 
 	export let value: FileData | null;
 	export let clearColor: Array<number> = [0, 0, 0, 0];
@@ -81,7 +80,7 @@
 	<div class="model3D">
 		<div class="download">
 			<a
-				href={download_files(value)}
+				href={value.data}
 				target={window.__is_colab__ ? "_blank" : null}
 				download={window.__is_colab__ ? null : value.orig_name || value.name}
 			>
diff --git a/ui/packages/model3D/src/utils.ts b/ui/packages/model3D/src/utils.ts
index 30401457e3..5fc7753eee 100644
--- a/ui/packages/model3D/src/utils.ts
+++ b/ui/packages/model3D/src/utils.ts
@@ -19,10 +19,6 @@ export const display_file_name = (value: FileData): string => {
 	} else return str;
 };
 
-export const download_files = (value: FileData): string => {
-	return value.data;
-};
-
 export const display_file_size = (
 	value: FileData | Array<FileData>
 ): string => {
diff --git a/ui/packages/upload-button/package.json b/ui/packages/upload-button/package.json
index 75f8271093..57d56835af 100644
--- a/ui/packages/upload-button/package.json
+++ b/ui/packages/upload-button/package.json
@@ -9,6 +9,7 @@
 	"private": true,
 	"dependencies": {
 		"@gradio/button": "workspace:^0.0.1",
-		"@gradio/utils": "workspace:^0.0.1"
+		"@gradio/utils": "workspace:^0.0.1",
+		"@gradio/upload": "workspace:^0.0.1"
 	}
 }
diff --git a/ui/packages/upload-button/src/UploadButton.svelte b/ui/packages/upload-button/src/UploadButton.svelte
index 664dde7346..f1c060a8d2 100644
--- a/ui/packages/upload-button/src/UploadButton.svelte
+++ b/ui/packages/upload-button/src/UploadButton.svelte
@@ -2,7 +2,7 @@
 	import { Button } from "@gradio/button";
 	import type { Styles } from "@gradio/utils";
 	import { createEventDispatcher } from "svelte";
-	import type { FileData } from "./types";
+	import type { FileData } from "@gradio/upload";
 	import { type } from "@testing-library/user-event/dist/type";
 
 	export let style: Styles = {};
@@ -35,33 +35,30 @@
 
 	const loadFiles = (files: FileList) => {
 		let _files: Array<File> = Array.from(files);
-		if (!files.length || !window.FileReader) {
+		if (!files.length) {
 			return;
 		}
 		if (file_count === "single") {
 			_files = [files[0]];
 		}
-		var all_file_data: Array<FileData | string> = [];
+		var all_file_data: Array<FileData | File> = [];
 		_files.forEach((f, i) => {
-			let ReaderObj = new FileReader();
-			ReaderObj.readAsDataURL(f);
-			ReaderObj.onloadend = function () {
-				all_file_data[i] = include_file_metadata
-					? {
-							name: f.name,
-							size: f.size,
-							data: this.result as string
-					  }
-					: (this.result as string);
-				if (
-					all_file_data.filter((x) => x !== undefined).length === files.length
-				) {
-					dispatch(
-						"load",
-						file_count == "single" ? all_file_data[0] : all_file_data
-					);
-				}
-			};
+			all_file_data[i] = include_file_metadata
+				? {
+						name: f.name,
+						size: f.size,
+						data: "",
+						blob: f
+				  }
+				: f;
+			if (
+				all_file_data.filter((x) => x !== undefined).length === files.length
+			) {
+				dispatch(
+					"load",
+					file_count == "single" ? all_file_data[0] : all_file_data
+				);
+			}
 		});
 	};
 
diff --git a/ui/packages/upload-button/src/types.ts b/ui/packages/upload-button/src/types.ts
deleted file mode 100644
index d747ba8492..0000000000
--- a/ui/packages/upload-button/src/types.ts
+++ /dev/null
@@ -1,7 +0,0 @@
-export interface FileData {
-	name: string;
-	orig_name?: string;
-	size?: number;
-	data: string;
-	is_file?: boolean;
-}
diff --git a/ui/packages/upload/src/Upload.svelte b/ui/packages/upload/src/Upload.svelte
index b2162734f5..1cfab91ae9 100644
--- a/ui/packages/upload/src/Upload.svelte
+++ b/ui/packages/upload/src/Upload.svelte
@@ -1,6 +1,7 @@
 <script lang="ts">
 	import { createEventDispatcher } from "svelte";
 	import type { FileData } from "./types";
+	import { blobToBase64 } from "./utils";
 
 	export let filetype: string | null = null;
 	export let include_file_metadata = true;
@@ -10,6 +11,7 @@
 	export let flex: boolean = true;
 	export let file_count: string = "single";
 	export let disable_click = false;
+	export let parse_to_data_url = true;
 
 	let hidden_upload: HTMLInputElement;
 
@@ -25,7 +27,7 @@
 		hidden_upload.click();
 	};
 
-	const loadFiles = (files: FileList) => {
+	const loadFiles = async (files: FileList) => {
 		let _files: Array<File> = Array.from(files);
 		if (!files.length || !window.FileReader) {
 			return;
@@ -33,41 +35,55 @@
 		if (file_count === "single") {
 			_files = [files[0]];
 		}
-		var all_file_data: Array<FileData | string> = [];
-		_files.forEach((f, i) => {
-			let ReaderObj = new FileReader();
-			ReaderObj.readAsDataURL(f);
-			ReaderObj.onloadend = function () {
-				all_file_data[i] = include_file_metadata
-					? {
-							name: f.name,
-							size: f.size,
-							data: this.result as string
-					  }
-					: (this.result as string);
-				if (
-					all_file_data.filter((x) => x !== undefined).length === files.length
-				) {
-					dispatch(
-						"load",
-						file_count == "single" ? all_file_data[0] : all_file_data
-					);
-				}
-			};
-		});
+
+		if (include_file_metadata) {
+			var file_metadata: Array<{ name: string; size: number }> = _files.map(
+				(f) => ({
+					name: f.name,
+					size: f.size
+				})
+			);
+		}
+		var load_file_data = [];
+		var file_data: Array<string> | Array<File> = [];
+		if (parse_to_data_url) {
+			file_data = await Promise.all(_files.map((f) => blobToBase64(f)));
+		} else {
+			file_data = _files;
+		}
+		if (include_file_metadata) {
+			if (parse_to_data_url) {
+				load_file_data = file_data.map((data, i) => ({
+					data,
+					...file_metadata[i]
+				}));
+			} else {
+				load_file_data = file_data.map((data, i) => ({
+					data: "",
+					blob: data,
+					...file_metadata[i]
+				}));
+			}
+		} else {
+			load_file_data = file_data;
+		}
+		dispatch(
+			"load",
+			file_count === "single" ? load_file_data[0] : load_file_data
+		);
 	};
 
-	const loadFilesFromUpload = (e: Event) => {
+	const loadFilesFromUpload = async (e: Event) => {
 		const target = e.target as HTMLInputElement;
 
 		if (!target.files) return;
-		loadFiles(target.files);
+		await loadFiles(target.files);
 	};
 
-	const loadFilesFromDrop = (e: DragEvent) => {
+	const loadFilesFromDrop = async (e: DragEvent) => {
 		dragging = false;
 		if (!e.dataTransfer?.files) return;
-		loadFiles(e.dataTransfer.files);
+		await loadFiles(e.dataTransfer.files);
 	};
 </script>
 
diff --git a/ui/packages/upload/src/types.ts b/ui/packages/upload/src/types.ts
index d747ba8492..6c61c6a631 100644
--- a/ui/packages/upload/src/types.ts
+++ b/ui/packages/upload/src/types.ts
@@ -3,5 +3,6 @@ export interface FileData {
 	orig_name?: string;
 	size?: number;
 	data: string;
+	blob?: File;
 	is_file?: boolean;
 }
diff --git a/ui/packages/upload/src/utils.ts b/ui/packages/upload/src/utils.ts
index 7ed63c225c..5147784b5d 100644
--- a/ui/packages/upload/src/utils.ts
+++ b/ui/packages/upload/src/utils.ts
@@ -35,3 +35,13 @@ export function normalise_file(
 	}
 	return file;
 }
+
+export const blobToBase64 = (blob: File): Promise<string> => {
+	const reader = new FileReader();
+	reader.readAsDataURL(blob);
+	return new Promise((resolve) => {
+		reader.onloadend = () => {
+			resolve(reader.result as string);
+		};
+	});
+};
diff --git a/ui/pnpm-lock.yaml b/ui/pnpm-lock.yaml
index 0c2f69fc78..f8b65421a6 100644
--- a/ui/pnpm-lock.yaml
+++ b/ui/pnpm-lock.yaml
@@ -371,9 +371,11 @@ importers:
   packages/upload-button:
     specifiers:
       '@gradio/button': workspace:^0.0.1
+      '@gradio/upload': workspace:^0.0.1
       '@gradio/utils': workspace:^0.0.1
     dependencies:
       '@gradio/button': link:../button
+      '@gradio/upload': link:../upload
       '@gradio/utils': link:../utils
 
   packages/utils:


Code review comments:
diff_hunk: @@ -292,17 +295,16 @@ async def file(path_or_url: str, request: fastapi.Request):
                 )
             abs_path = str(utils.abspath(path_or_url))
             in_app_dir = utils.abspath(app.cwd) in utils.abspath(path_or_url).parents
-            created_by_app = str(utils.abspath(path_or_url)) in set().union(
-                *blocks.temp_file_sets
-            )
+            created_by_app = abs_path in set().union(*blocks.temp_file_sets)
             in_file_dir = any(
                 (
                     utils.abspath(dir) in utils.abspath(path_or_url).parents
                     for dir in blocks.file_directories
                 )
             )
+            was_uploaded = abs_path.startswith(app.uploaded_file_dir)
path: gradio/routes.py
text: @user1:
Is `startswith()` robust? I think using the `in` operator with two `Path`s is more robust

@author:
No, it's the opposite - the `in` keyword is not robust. For example, if the upload dir is "a/b", and a user tries to access file "c/d/a/b", the `in` keyword will allow access to this. This is not secure. We should modify the other lines to use `startswith` as well - I can do this is in this PR?

@user1:
Not `in` with two strings, but `in` with two `pathlib.Path` objects. See here: https://stackoverflow.com/a/34236245/5209347

@author:
oh yep you're right