Et2File: Improvements for handling larger files

This commit is contained in:
nathan 2025-02-18 14:47:18 -07:00
parent 8beae0adee
commit fce8540da5
4 changed files with 73 additions and 19 deletions

View File

@ -4,6 +4,9 @@ export default css`
:host {
}
:host([loading]) .file__button et2-image {
display: none;
}
.file--single > div {
display: flex;
flex-direction: row;

View File

@ -98,6 +98,8 @@ export class Et2File extends Et2InputWidget(LitElement)
@property({type: Object}) uploadOptions : {};
/** Chunk size can be altered by the server */
@property({type: Number}) chunkSize = 1020 * 1024;
@property({type: Function}) onStart : Function;
@property({type: Function}) onFinishOne : Function;
@ -106,7 +108,7 @@ export class Et2File extends Et2InputWidget(LitElement)
@state() files : FileInfo[] = [];
protected resumable : Resumable = null;
private __value : { [tempName : string] : FileInfo };
private __value : { [tempName : string] : FileInfo } = {};
/** Files already uploaded */
@property({type: Object})
@ -197,11 +199,14 @@ export class Et2File extends Et2InputWidget(LitElement)
request_id: this.getInstanceManager()?.etemplate_exec_id,
widget_id: this.id,
},
chunkSize: 1024 * 1024,
chunkSize: this.chunkSize,
// Checking for already uploaded chunks - resumable uploads
testChunks: true,
testTarget: this.egw().ajaxUrl("EGroupware\\Api\\Etemplate\\Widget\\File::ajax_test_chunk")
testTarget: this.egw().ajaxUrl("EGroupware\\Api\\Etemplate\\Widget\\File::ajax_test_chunk"),
// Failure & retry
//xhrTimeout: 10000
};
if(this.accept)
{
@ -279,6 +284,11 @@ export class Et2File extends Et2InputWidget(LitElement)
if(fileItem && file.progress())
{
fileItem.progress = file.progress() * 100;
// Show indeterminate while processing
if(fileItem.progress == 100)
{
delete fileItem.progress;
}
fileItem.requestUpdate("progress");
}
}
@ -489,14 +499,27 @@ export class Et2File extends Et2InputWidget(LitElement)
handleFileRemove(info : FileInfo)
{
const index = this.files.indexOf(info);
let index = this.files.indexOf(info);
if(index === -1)
{
const source = <FileInfo[]>Object.values(this.value);
index = source.indexOf(info);
delete this.value[Object.keys(this.value)[index]];
}
else
{
this.files.splice(index, 1);
}
if(index === -1)
{
return;
}
const fileInfo = this.files[index];
if(info && typeof info.progress == "function" && typeof info.abort == "function" && info.progress() < 1)
{
info.abort();
}
this.files.splice(index, 1);
this.requestUpdate();
this.updateComplete.then(() =>
@ -528,21 +551,24 @@ export class Et2File extends Et2InputWidget(LitElement)
fileItemTemplate(fileInfo : FileInfo, index)
{
const label = (fileInfo.accepted ? fileInfo.file.name : fileInfo.warning) ?? fileInfo['name'];
let icon = fileInfo.icon ?? (fileInfo.warning ? "exclamation-triangle" : undefined);
// Pull thumbnail from file if we can
const type = fileInfo.file?.type ?? fileInfo["type"];
let thumbnail;
if(!icon && fileInfo.file && fileInfo.file.type?.startsWith("image/"))
if(!icon && fileInfo.file && type?.startsWith("image/"))
{
thumbnail = URL.createObjectURL(fileInfo.file);
}
const closable = !this.readonly && (fileInfo.accepted || Object.values(this.value).indexOf(fileInfo) !== -1)
return html`
<et2-file-item
display=${this.display}
size=${fileInfo.accepted ? fileInfo.file.size : nothing}
variant=${fileInfo.accepted && !fileInfo.warning ? "default" : "warning"}
?closable=${fileInfo.accepted}
?closable=${closable}
?loading=${fileInfo.loading}
image=${ifDefined(icon)}
progress=${typeof fileInfo.progress == "function" ? fileInfo.progress() : (fileInfo.progress ?? nothing)}
@ -560,19 +586,19 @@ export class Et2File extends Et2InputWidget(LitElement)
this.handleFileRemove(fileInfo);
}}
>
${!icon && thumbnail || !fileInfo.file.type ? html`
${!icon && thumbnail || !type ? html`
<et2-image slot="image"
src=${thumbnail ?? "upload"}
/>
` : nothing}
${!icon && !thumbnail && fileInfo.file.type ? html`
${!icon && !thumbnail && type ? html`
<et2-vfs-mime
slot="image"
mime=${fileInfo.file.type}
.value=${{mime: fileInfo.file.type}}
mime=${type}
.value=${{mime: type}}
></et2-vfs-mime>` : nothing
}
${fileInfo.accepted ? fileInfo.file.name : fileInfo.warning}
${label}
</et2-file-item>
`;
}

View File

@ -296,9 +296,15 @@ class File extends Etemplate\Widget
}
// create the final destination file
set_time_limit($total_files / 100);
if (($fp = fopen($new_file, 'w')) !== false) {
for ($i=1; $i<=$total_files; $i++) {
fwrite($fp, file_get_contents($temp_dir.'/'.$fileName.'.part'.$i));
$chunk = fopen($temp_dir . '/' . $fileName . '.part' . $i, 'r');
while(!feof($chunk))
{
fwrite($fp, fread($chunk, 1024 * 1024));
}
fclose($chunk);
}
fclose($fp);
} else {
@ -413,9 +419,21 @@ class File extends Etemplate\Widget
$unit = strtolower(substr($upload_max_filesize, -1));
$upload_max_filesize = (float)$upload_max_filesize;
if (!is_numeric($unit)) $upload_max_filesize *= $unit === 'm' ? 1024*1024 : 1024;
if ($upload_max_filesize > 1024*1024)
$current_max_chunk = self::getElementAttribute($form_name, 'chunkSize') ?? $this->attrs['chunkSize'];
if($current_max_chunk)
{
self::setElementAttribute($form_name, 'chunk_size', ($upload_max_filesize-1024*1024)/2);
$unit = strtolower(substr($current_max_chunk, -1));
$current_max_chunk = (float)$current_max_chunk;
if(!is_numeric($unit))
{
$current_max_chunk *= $unit === 'm' ? 1024 * 1024 : 1024;
}
$upload_max_filesize = min($upload_max_filesize, $current_max_chunk);
}
if($upload_max_filesize != 1024 * 1024)
{
self::setElementAttribute($form_name, 'chunkSize', ($upload_max_filesize - 1024 * 1024) / 2);
}
}
}

View File

@ -485,12 +485,19 @@ export class filemanagerAPP extends EgwApp
if(_file_count && _event.detail)
{
let widget = _event.target;
widget.loading = true;
_event.detail.accepted = false; // Turn off removable, it's too late now
let value = widget.getValue();
value.conflict = _conflict;
widget.requestUpdate("loading");
egw.json(_target, ['upload', value, _path, {ui_path: this.egw.window.location.pathname}],
this._upload_callback, this, true, this
).sendRequest();
).sendRequest().finally(() =>
{
widget.loading = false;
widget.value = {};
widget.requestUpdate("loading", true);
});
}
}