Skip to content

Commit cb3a196

Browse files
feat: add configurable compression level for zip downloads (Folder Share page, downloadPageFolder.html) (#179)
* feat: Add image thumbnail preview on row hover (downloadPageFolder) - Display thumbnail preview when hovering over image rows - Preview follows cursor movement with offset - Apply hover effect on entire row for better UX * feat: add configurable compression level for zip downloads (Folder Share page, downloadPageFolder.html) Added the ability to customize zip file compression levels: - Added new ArozZipFileWithCompressionLevel function that accepts compression level parameter - Added compression_level query parameter support to share downloads - Added UI checkbox to toggle between compressed/uncompressed downloads - Auto-detect when folder contains mostly media files (>= 50% in size) and pre-check uncompressed option - Updated compression help text to provide guidance on media files - Default compression level remains at flate.DefaultCompression (-1) The changes allow users to choose no compression for media-heavy folders to improve zipping speed, while maintaining regular compression for other content types. * lazy bug fix
1 parent f9f4ec7 commit cb3a196

File tree

3 files changed

+95
-11
lines changed

3 files changed

+95
-11
lines changed

src/mod/filesystem/fileOpr.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -332,6 +332,11 @@ To use it with local file system, pass in nil in fsh for each item in filelist,
332332
filesystem.ArozZipFile([]*filesystem.FileSystemHandler{nil}, []string{zippingSource}, nil, targetZipFilename, false)
333333
*/
334334
func ArozZipFile(sourceFshs []*FileSystemHandler, filelist []string, outputFsh *FileSystemHandler, outputfile string, includeTopLevelFolder bool) error {
335+
// Call the new function with default compression level (e.g., 6)
336+
return ArozZipFileWithCompressionLevel(sourceFshs, filelist, outputFsh, outputfile, includeTopLevelFolder, flate.DefaultCompression)
337+
}
338+
339+
func ArozZipFileWithCompressionLevel(sourceFshs []*FileSystemHandler, filelist []string, outputFsh *FileSystemHandler, outputfile string, includeTopLevelFolder bool, compressionLevel int) error {
335340
//Create the target zip file
336341
var file arozfs.File
337342
var err error
@@ -347,6 +352,9 @@ func ArozZipFile(sourceFshs []*FileSystemHandler, filelist []string, outputFsh *
347352
defer file.Close()
348353

349354
writer := zip.NewWriter(file)
355+
writer.RegisterCompressor(zip.Deflate, func(out io.Writer) (io.WriteCloser, error) {
356+
return flate.NewWriter(out, compressionLevel)
357+
})
350358
defer writer.Close()
351359

352360
for i, srcpath := range filelist {

src/mod/share/share.go

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ package share
88
*/
99

1010
import (
11+
"compress/flate"
1112
"encoding/json"
1213
"errors"
1314
"fmt"
@@ -267,6 +268,17 @@ func (s *Manager) HandleShareAccess(w http.ResponseWriter, r *http.Request) {
267268
directServe := false
268269
relpath := ""
269270

271+
compressionLevel := flate.DefaultCompression
272+
if compressionStr := r.URL.Query().Get("compression_level"); compressionStr != "" {
273+
if val, err := strconv.Atoi(compressionStr); err == nil {
274+
// Validate compression level range (-2 to 9)
275+
if val >= -2 && val <= 9 {
276+
compressionLevel = val
277+
}
278+
// Optional: else could return an error or just silently use default value
279+
}
280+
}
281+
270282
id, err := utils.GetPara(r, "id")
271283
if err != nil {
272284
//ID is not defined in the URL paramter. New ID defination is based on the subpath content
@@ -615,7 +627,7 @@ func (s *Manager) HandleShareAccess(w http.ResponseWriter, r *http.Request) {
615627
}
616628

617629
//Build a filelist
618-
err := filesystem.ArozZipFile([]*filesystem.FileSystemHandler{zippingSourceFsh}, []string{zippingSource}, nil, targetZipFilename, false)
630+
err := filesystem.ArozZipFileWithCompressionLevel([]*filesystem.FileSystemHandler{zippingSourceFsh}, []string{zippingSource}, nil, targetZipFilename, false, compressionLevel)
619631
if err != nil {
620632
//Failed to create zip file
621633
w.WriteHeader(http.StatusInternalServerError)

src/system/share/downloadPageFolder.html

Lines changed: 74 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -114,12 +114,28 @@ <h3>{{filename}}</h3>
114114
</tr>
115115
</tbody>
116116
</table>
117-
<a href="{{downloadurl}}"><button class="button-primary">Download All</button></a>
118-
<button id="sharebtn" onclick="share();">Share</button>
119-
<p style="font-size: 80%;"><b>Depending on folder size, zipping might take a while to complete.</b></p>
120-
<p>Request File ID: {{reqid}}<br>
121-
Request Timestamp: {{reqtime}}</p>
122-
<small>📂 Double click any item in the list to open or download</small>
117+
<a href="{{downloadurl}}" id="downloadLink"><button class="button-primary">Download All</button></a>
118+
<button id="sharebtn" onclick="share();">Share</button>
119+
<div style="margin-top: 10px;">
120+
<input type="checkbox" id="uncompressedCheck" name="uncompressed">
121+
<label for="uncompressedCheck" style="display: inline">Uncompressed</label>
122+
</div>
123+
<p style="font-size: 80%;"><b>Zipping duration depends on folder size and compression settings. Media files are best zipped without compression for faster processing.</b></p>
124+
<p>Request File ID: {{reqid}}<br>
125+
Request Timestamp: {{reqtime}}</p>
126+
<small>📂 Double click any item in the list to open or download</small>
127+
128+
<script>
129+
document.getElementById('uncompressedCheck').addEventListener('change', function() {
130+
const downloadLink = document.getElementById('downloadLink');
131+
if (this.checked) {
132+
downloadLink.href = '{{downloadurl}}?compression_level=0';
133+
} else {
134+
downloadLink.href = '{{downloadurl}}';
135+
}
136+
});
137+
</script>
138+
123139

124140
</div>
125141
<div class="one-half column" id="filelistWrapper" style="overflow-y: auto; padding-right: 0.5em; min-height: 400px;">
@@ -149,7 +165,14 @@ <h3>{{filename}}</h3>
149165
var downloadUUID = `{{downloaduuid}}`;
150166
var currentViewingRoot = ".";
151167
var selectedFile = null;
152-
renderFileList(treeFileList["."]);
168+
var stats = renderFileList(treeFileList["."]);
169+
170+
// most files are already compressed media...
171+
if (stats.totalCompressedMediaSize / stats.totalFileSize >= 0.5) {
172+
document.getElementById('uncompressedCheck').checked = true;
173+
const downloadLink = document.getElementById('downloadLink');
174+
downloadLink.href = '{{downloadurl}}?compression_level=0';
175+
}
153176

154177
handleWindowResize();
155178
$(window).on("resize", function(e){
@@ -188,6 +211,32 @@ <h3>{{filename}}</h3>
188211
}
189212
}
190213

214+
function convertToBytes(sizeString) {
215+
// Remove any spaces and convert to uppercase
216+
sizeString = sizeString.replace(/\s+/g, '').toUpperCase();
217+
218+
// Regular expression to match number and unit
219+
const matches = sizeString.match(/^([\d.]+)([KMGT]?B)$/i);
220+
221+
if (!matches) {
222+
throw new Error('Invalid format');
223+
}
224+
225+
const size = parseFloat(matches[1]);
226+
const unit = matches[2];
227+
228+
// Conversion factors
229+
const units = {
230+
'B': 1,
231+
'KB': 1024,
232+
'MB': 1024 ** 2,
233+
'GB': 1024 ** 3,
234+
'TB': 1024 ** 4
235+
};
236+
237+
return Math.round(size * units[unit]);
238+
}
239+
191240

192241
function renderFileList(filelist){
193242
$("#folderList").html("");
@@ -216,26 +265,37 @@ <h3>{{filename}}</h3>
216265
`);
217266
}
218267

268+
var totalCompressedMediaSize = 0;
269+
var totalFileSize = 0;
219270
filelist.forEach(file => {
220271
var filetype = "File";
221272
var displayName = "";
222273
var isImage = false;
274+
223275
if (file.IsDir == true){
224276
//Folder
225277
filetype = "Folder";
226278
displayName = "📁 " + file.Filename;
227279
}else{
228280
//File
281+
totalFileSize += convertToBytes(file.Filesize);
229282
var ext = file.Filename.split(".").pop();
230283
var icon = "📄"
231284
ext = ext.toLowerCase();
232-
if (ext == "mp3" || ext == "wav" || ext == "flac" || ext == "aac" || ext == "ogg" || ext == ""){
285+
if (ext == "mp3" || ext == "wav" || ext == "flac" || ext == "alac" || ext == "wma" || ext == "aac" || ext == "ogg" || ext == ""){
233286
icon = "🎵";
234-
}else if (ext == "mp4" || ext == "avi" || ext == "webm" || ext == "mkv" || ext == "mov" || ext == "rvmb"){
287+
if (ext != "wav") {
288+
totalCompressedMediaSize += convertToBytes(file.Filesize);
289+
}
290+
}else if (ext == "mp4" || ext == "avi" || ext == "webm" || ext == "mkv" || ext == "wmv" || ext == "mov" || ext == "rmvb" || ext == "rm"){
235291
icon = "🎞️";
236-
}else if (ext == "png" || ext == "jpeg" || ext == "jpg" || ext == "bmp" || ext == "gif"){
292+
totalCompressedMediaSize += convertToBytes(file.Filesize);
293+
}else if (ext == "png" || ext == "jpeg" || ext == "jpg" || ext == "bmp" || ext == "gif" || ext == "webp" || ext == "avif"){
237294
icon = "🖼️";
238295
isImage = true;
296+
if (ext != "bmp") {
297+
totalCompressedMediaSize += convertToBytes(file.Filesize);
298+
}
239299
}
240300

241301
displayName = icon + " " + file.Filename;
@@ -293,6 +353,10 @@ <h3>{{filename}}</h3>
293353
.fileobject:hover { background-color: rgba(0,0,0,0.05); }
294354
`)
295355
.appendTo("head");
356+
357+
return {
358+
totalCompressedMediaSize, totalFileSize
359+
}
296360
}
297361

298362
//Went up one level

0 commit comments

Comments
 (0)