diff --git a/build.sh b/build.sh new file mode 100755 index 0000000..6774901 --- /dev/null +++ b/build.sh @@ -0,0 +1,16 @@ +#!/bin/bash +echo "Building Trashbox CGI scripts..." + +echo "Building index.cgi..." +gcc -Wall -O2 -o index.cgi index.c -DBASE_WWW_PATH='"/home/romkazvo/www"' -DTEMPLATE_PATH='"/home/romkazvo/www/cgi-bin/template.html"' + +echo "Building style.cgi..." +gcc -Wall -O2 -o style.cgi style.c -DCSS_PATH='"/home/romkazvo/www/cgi-bin/style.css"' + +echo "Building status.cgi..." +gcc -Wall -O2 -o status.cgi status.c + +echo "Setting permissions..." +chmod +x *.cgi + +echo "Build complete" diff --git a/index.c b/index.c index 7ae5bdc..90c0089 100755 --- a/index.c +++ b/index.c @@ -1,5 +1,4 @@ // /home/romkazvo/www/cgi-bin/index.c -// Основной файловый сервер #include #include #include @@ -23,6 +22,7 @@ typedef struct { char file_url[1024]; } entry_t; +// Быстрый буферизированный вывод typedef struct { char *data; size_t size; @@ -90,6 +90,47 @@ const char* get_file_icon(const char* filename) { return "📄"; } +// Проверяет, можно ли открыть файл в браузере +int is_previewable(const char* filename) { + const char *ext = strrchr(filename, '.'); + if (!ext) return 0; + + ext++; + + // Изображения + if (strcasecmp(ext, "jpg") == 0 || strcasecmp(ext, "jpeg") == 0 || + strcasecmp(ext, "png") == 0 || strcasecmp(ext, "gif") == 0 || + strcasecmp(ext, "webp") == 0 || strcasecmp(ext, "bmp") == 0 || + strcasecmp(ext, "svg") == 0 || strcasecmp(ext, "ico") == 0) + return 1; + + // Текстовые файлы + if (strcasecmp(ext, "txt") == 0 || strcasecmp(ext, "md") == 0 || + strcasecmp(ext, "html") == 0 || strcasecmp(ext, "htm") == 0 || + strcasecmp(ext, "css") == 0 || strcasecmp(ext, "js") == 0 || + strcasecmp(ext, "json") == 0 || strcasecmp(ext, "xml") == 0 || + strcasecmp(ext, "csv") == 0) + return 1; + + // PDF + if (strcasecmp(ext, "pdf") == 0) + return 1; + + // Аудио + if (strcasecmp(ext, "mp3") == 0 || strcasecmp(ext, "wav") == 0 || + strcasecmp(ext, "ogg") == 0 || strcasecmp(ext, "flac") == 0 || + strcasecmp(ext, "m4a") == 0 || strcasecmp(ext, "aac") == 0) + return 1; + + // Видео + if (strcasecmp(ext, "mp4") == 0 || strcasecmp(ext, "webm") == 0 || + strcasecmp(ext, "ogv") == 0 || strcasecmp(ext, "mov") == 0 || + strcasecmp(ext, "avi") == 0 || strcasecmp(ext, "mkv") == 0) + return 1; + + return 0; +} + int compare_entries(const void *a, const void *b) { const entry_t *entryA = (const entry_t *)a; const entry_t *entryB = (const entry_t *)b; @@ -251,6 +292,7 @@ void print_content(buffer_t *buf, const char *base_path, const char *display_pat } } + // Предварительная обработка записей while ((entry = readdir(dir)) != NULL && entry_count < MAX_ENTRIES) { if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0) continue; @@ -268,6 +310,7 @@ void print_content(buffer_t *buf, const char *base_path, const char *display_pat entries[entry_count].size = 0; strcpy(entries[entry_count].icon, "📁"); + // Предварительно кодируем путь для папок char new_path[2048]; snprintf(new_path, sizeof(new_path), "%s%s%s", display_path, display_path[0] ? "/" : "", entries[entry_count].name); @@ -279,6 +322,7 @@ void print_content(buffer_t *buf, const char *base_path, const char *display_pat entries[entry_count].size = file_stat.st_size; strcpy(entries[entry_count].icon, get_file_icon(entry->d_name)); + // Предварительно формируем URL для файлов snprintf(entries[entry_count].file_url, sizeof(entries[0].file_url), "%s%s%s", display_path, display_path[0] ? "/" : "", entries[entry_count].name); @@ -305,6 +349,7 @@ void print_content(buffer_t *buf, const char *base_path, const char *display_pat buffer_append(buf, "\n"); + // Статистика char total_size_str[32]; format_size(total_size, total_size_str); char stats[256]; @@ -394,6 +454,7 @@ void print_template(const char *base_path, const char *display_path) { } fclose(file); + // Один быстрый вывод вместо множества printf fwrite(output.data, 1, output.size, stdout); buffer_free(&output); } diff --git a/style.css b/style.css index 2225857..fb2caf3 100755 --- a/style.css +++ b/style.css @@ -187,32 +187,48 @@ li:hover { background-color: var(--hover-bg); } -a { - text-decoration: none; - flex-grow: 1; - display: flex; - align-items: center; - min-width: 0; - padding-right: 0.5rem; +/* Стили для строки файла */ +.file-row { + display: flex; + align-items: center; + justify-content: space-between; + flex-grow: 1; + min-width: 0; + gap: 1rem; } -.file-link { - color: var(--accent-blue); - font-weight: 500; +.file-link { + color: var(--accent-blue); + font-weight: 500; + text-decoration: none; + display: flex; + align-items: center; + flex-grow: 1; + min-width: 0; } -.dir-link { - color: var(--accent-green); - font-weight: 600; +.dir-link { + color: var(--accent-green); + font-weight: 600; + text-decoration: none; + display: flex; + align-items: center; + flex-grow: 1; } -.size { - color: var(--text-secondary); - font-size: 0.85rem; - margin-left: auto; - padding-left: 0.5rem; - flex-shrink: 0; - white-space: nowrap; +.file-controls { + display: flex; + align-items: center; + gap: 0.75rem; + flex-shrink: 0; +} + +.size { + color: var(--text-secondary); + font-size: 0.85rem; + white-space: nowrap; + min-width: 70px; + text-align: right; } .file-icon { @@ -299,6 +315,186 @@ a { text-decoration: underline; } +/* Стили для кнопки предпросмотра */ +.preview-btn { + background: transparent; + color: var(--text-secondary); + border: 1px solid var(--border-color); + border-radius: 6px; + width: 32px; + height: 32px; + cursor: pointer; + font-size: 0.85rem; + display: flex; + align-items: center; + justify-content: center; + transition: all 0.2s ease; + opacity: 0.7; +} + +.preview-btn:hover { + background: var(--accent-blue); + color: white; + border-color: var(--accent-blue); + opacity: 1; + transform: scale(1.05); +} + +/* Модальное окно предпросмотра */ +.preview-modal { + display: none; + position: fixed; + z-index: 2000; + left: 0; + top: 0; + width: 100%; + height: 100%; + background-color: rgba(0,0,0,0.8); + backdrop-filter: blur(5px); +} + +.preview-content { + background: var(--bg-secondary); + margin: 2% auto; + padding: 0; + border-radius: 8px; + width: 90%; + height: 90%; + max-width: 1200px; + box-shadow: 0 10px 30px rgba(0,0,0,0.3); + display: flex; + flex-direction: column; + overflow: hidden; + transition: all 0.3s ease; +} + +/* Размеры для разных типов контента */ +.preview-content.image-type { + max-width: 90%; + max-height: 90%; + width: auto; + height: auto; +} + +.preview-content.text-type { + max-width: 800px; + max-height: 80%; +} + +.preview-content.audio-type { + max-width: 500px; + max-height: 300px; +} + +.preview-content.video-type { + max-width: 90%; + max-height: 90%; +} + +.preview-content.pdf-type { + max-width: 90%; + max-height: 90%; +} + +.preview-content.default-type { + max-width: 90%; + max-height: 90%; +} + +.preview-header { + display: flex; + justify-content: space-between; + align-items: center; + padding: 1rem; + border-bottom: 1px solid var(--border-color); + background: var(--bg-primary); + flex-shrink: 0; +} + +.preview-title { + font-weight: 600; + color: var(--text-primary); + margin: 0; + flex-grow: 1; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + padding-right: 1rem; +} + +.close-preview { + background: #dc3545; + color: white; + border: none; + border-radius: 4px; + width: 36px; + height: 36px; + cursor: pointer; + font-size: 1.2rem; + flex-shrink: 0; + display: flex; + align-items: center; + justify-content: center; + transition: background 0.2s ease; +} + +.close-preview:hover { + background: #c82333; +} + +.preview-iframe { + flex-grow: 1; + border: none; + background: white; + min-height: 0; +} + +.preview-iframe.pdf { + background: var(--bg-primary); +} + +/* Адаптивность */ +@media (max-width: 768px) { + .preview-content { + width: 95%; + height: 95%; + margin: 2.5% auto; + } + + .preview-content.image-type { + max-width: 95%; + max-height: 95%; + } + + .preview-content.text-type { + max-width: 95%; + max-height: 80%; + } + + .preview-content.audio-type { + max-width: 95%; + max-height: 300px; + } + + .preview-content.video-type { + max-width: 95%; + max-height: 95%; + } + + .preview-content.pdf-type { + max-width: 95%; + max-height: 95%; + } + + .preview-header { + padding: 0.75rem; + } + + .preview-title { + font-size: 0.9rem; + } +} + @media (max-width: 480px) { .theme-toggle { top: 5px; @@ -331,9 +527,15 @@ a { li { padding: 0.75rem 0.25rem; } + .file-row { + gap: 0.5rem; + } + .file-controls { + gap: 0.5rem; + } .size { font-size: 0.8rem; - padding-left: 0.25rem; + min-width: 60px; } .file-icon { margin-right: 0.75rem; @@ -352,6 +554,34 @@ a { padding: 0.75rem; font-size: 0.85rem; } + .preview-btn { + width: 28px; + height: 28px; + font-size: 0.8rem; + } + .preview-content { + width: 98%; + height: 98%; + margin: 1% auto; + border-radius: 4px; + } + + .preview-content.image-type, + .preview-content.video-type, + .preview-content.pdf-type { + max-width: 98%; + max-height: 98%; + } + + .preview-content.text-type { + max-width: 98%; + max-height: 80%; + } + + .preview-content.audio-type { + max-width: 98%; + max-height: 250px; + } } @media (max-width: 360px) { @@ -368,4 +598,19 @@ a { .size { font-size: 0.75rem; } + .file-row { + flex-direction: column; + align-items: flex-start; + gap: 0.25rem; + } + .file-controls { + align-self: flex-end; + } + .file-link { + width: 100%; + } + + .preview-content.audio-type { + max-height: 200px; + } } \ No newline at end of file diff --git a/template.html b/template.html index 10c283d..15b8407 100755 --- a/template.html +++ b/template.html @@ -80,6 +80,17 @@ + +
+
+
+

Предпросмотр файла

+ +
+ +
+
+