Final modifications.

This commit is contained in:
Lars Jung 2012-04-19 00:57:43 +02:00
parent de92767e5a
commit ade6cf8e0b
24 changed files with 316 additions and 275 deletions

View file

@ -43,9 +43,9 @@ h5ai is provided under the terms of the [MIT License](http://github.com/lrsjng/h
* adds an info page at `/_h5ai` * adds an info page at `/_h5ai`
* sort order is preserved while browsing * sort order is preserved while browsing
* removes PHP error messages on thumbnail generation * removes PHP error messages on thumbnail generation
* fixes PHP problems with zipped download * fixes PHP some problems with packed download
* changes crumb image for folders with an index file
* adds support for tarred downloads * adds support for tarred downloads
* changes crumb image for folders with an index file
* adds `index.php` to use h5ai in non-Apache environments * adds `index.php` to use h5ai in non-Apache environments
* switches from [Datejs](http://www.datejs.com) to [Moment.js](http://momentjs.com) * switches from [Datejs](http://www.datejs.com) to [Moment.js](http://momentjs.com)
* adds [underscore.js](http://underscorejs.org) * adds [underscore.js](http://underscorejs.org)

View file

@ -3,7 +3,7 @@ custom = true
# project # project
project.name = h5ai project.name = h5ai
project.version = 0.19-pre project.version = 0.19
# src # src

View file

@ -69,11 +69,16 @@ var H5AI_CONFIG = {
}, },
/* /*
* EXPLICITLY: USE "shell" ON YOUR OWN RISK.
*
* Requires PHP on the server. * Requires PHP on the server.
* Enable packaged download of selected entries. Supported formats: "tar", "zip". * Enable packaged download of selected entries.
* Execution: "php", "shell".
* Supported formats: "tar", "zip".
*/ */
"download": { "download": {
"enabled": true, "enabled": true,
"execution": "shell",
"format": "zip" "format": "zip"
}, },
@ -108,16 +113,11 @@ var H5AI_CONFIG = {
* *
* Optionally try to use browser language, falls back to previous * Optionally try to use browser language, falls back to previous
* specified language. * specified language.
*
* Date format in detailed view, for example: "YYYY-MM-DD HH:mm:ss"
* Syntax as specified by Moment.js (http://momentjs.com)
* This might be overidden by "dateFormat" in a lang specification.
*/ */
"l10n": { "l10n": {
"enabled": true, "enabled": true,
"lang": "en", "lang": "en",
"useBrowserLang": true, "useBrowserLang": true
"defaultDateFormat": "YYYY-MM-DD HH:mm"
}, },
/* /*
@ -136,7 +136,7 @@ var H5AI_CONFIG = {
}, },
/* /*
* Make entries selectable. At the moment only needed for zipped download. * Make entries selectable. At the moment only needed for packaged download.
*/ */
"select": { "select": {
"enabled": true "enabled": true
@ -265,6 +265,9 @@ var H5AI_CONFIG = {
/* /*
* Available translations. "en" in first place as a reference, otherwise in alphabetical order. * Available translations. "en" in first place as a reference, otherwise in alphabetical order.
*
* Date format is used in detailed view, for example: "YYYY-MM-DD HH:mm:ss"
* Syntax as specified by Moment.js (http://momentjs.com)
*/ */
"langs": { "langs": {
@ -280,7 +283,8 @@ var H5AI_CONFIG = {
"folders": "folders", "folders": "folders",
"files": "files", "files": "files",
"download": "download", "download": "download",
"noMatch": "no match" "noMatch": "no match",
"dateFormat": "YYYY-MM-DD HH:mm"
}, },
"bg": { "bg": {
@ -293,9 +297,7 @@ var H5AI_CONFIG = {
"parentDirectory": "Предходна директория", "parentDirectory": "Предходна директория",
"empty": "празно", "empty": "празно",
"folders": "папки", "folders": "папки",
"files": "файлове", "files": "файлове"
"download": "download",
"noMatch": "no match"
}, },
"cs": { "cs": {
@ -308,9 +310,7 @@ var H5AI_CONFIG = {
"parentDirectory": "Nadřazený adresář", "parentDirectory": "Nadřazený adresář",
"empty": "prázdný", "empty": "prázdný",
"folders": "složek", "folders": "složek",
"files": "souborů", "files": "souborů"
"download": "download",
"noMatch": "no match"
}, },
"de": { "de": {
@ -340,8 +340,7 @@ var H5AI_CONFIG = {
"empty": "vacío", "empty": "vacío",
"folders": "Directorios", "folders": "Directorios",
"files": "Archivos", "files": "Archivos",
"download": "Descargar", "download": "Descargar"
"noMatch": "no match"
}, },
"fr": { "fr": {
@ -384,9 +383,7 @@ var H5AI_CONFIG = {
"parentDirectory": "Cartella Superiore", "parentDirectory": "Cartella Superiore",
"empty": "vuota", "empty": "vuota",
"folders": "cartelle", "folders": "cartelle",
"files": "file", "files": "file"
"download": "download",
"noMatch": "no match"
}, },
"ja": { "ja": {
@ -442,11 +439,7 @@ var H5AI_CONFIG = {
"lastModified": "Laatste wijziging", "lastModified": "Laatste wijziging",
"size": "Grootte", "size": "Grootte",
"parentDirectory": "Bovenliggende map", "parentDirectory": "Bovenliggende map",
"empty": "lege", "empty": "lege"
"folders": "folders",
"files": "files",
"download": "download",
"noMatch": "no match"
}, },
"pl": { "pl": {
@ -459,9 +452,7 @@ var H5AI_CONFIG = {
"parentDirectory": "Katalog nadrzędny", "parentDirectory": "Katalog nadrzędny",
"empty": "pusty", "empty": "pusty",
"folders": "foldery", "folders": "foldery",
"files": "pliki", "files": "pliki"
"download": "download",
"noMatch": "no match"
}, },
"pt": { "pt": {
@ -474,9 +465,7 @@ var H5AI_CONFIG = {
"parentDirectory": "Diretório superior", "parentDirectory": "Diretório superior",
"empty": "vazio", "empty": "vazio",
"folders": "pastas", "folders": "pastas",
"files": "arquivos", "files": "arquivos"
"download": "download",
"noMatch": "no match"
}, },
"ro": { "ro": {
@ -504,9 +493,7 @@ var H5AI_CONFIG = {
"parentDirectory": "Главная директория", "parentDirectory": "Главная директория",
"empty": "пусто", "empty": "пусто",
"folders": "папки", "folders": "папки",
"files": "файлы", "files": "файлы"
"download": "download",
"noMatch": "no match"
}, },
"sk": { "sk": {
@ -519,9 +506,7 @@ var H5AI_CONFIG = {
"parentDirectory": "Nadriadený priečinok", "parentDirectory": "Nadriadený priečinok",
"empty": "prázdny", "empty": "prázdny",
"folders": "priečinkov", "folders": "priečinkov",
"files": "súborov", "files": "súborov"
"download": "download",
"noMatch": "no match"
}, },
"sr": { "sr": {
@ -547,11 +532,7 @@ var H5AI_CONFIG = {
"lastModified": "Senast ändrad", "lastModified": "Senast ändrad",
"size": "Filstorlek", "size": "Filstorlek",
"parentDirectory": "Till överordnad mapp", "parentDirectory": "Till överordnad mapp",
"empty": "tom", "empty": "tom"
"folders": "folders",
"files": "files",
"download": "download",
"noMatch": "no match"
}, },
"tr": { "tr": {
@ -565,8 +546,7 @@ var H5AI_CONFIG = {
"empty": "boş", "empty": "boş",
"folders": "klasörler", "folders": "klasörler",
"files": "dosyalar", "files": "dosyalar",
"download": "indir", "download": "indir"
"noMatch": "no match"
}, },
"zh-cn": { "zh-cn": {
@ -580,8 +560,7 @@ var H5AI_CONFIG = {
"empty": "空文件夹", "empty": "空文件夹",
"folders": "文件夹", "folders": "文件夹",
"files": "文件", "files": "文件",
"download": "下载", "download": "下载"
"noMatch": "no match"
}, },
"zh-tw": { "zh-tw": {
@ -595,8 +574,7 @@ var H5AI_CONFIG = {
"empty": "空資料夾", "empty": "空資料夾",
"folders": "資料夾", "folders": "資料夾",
"files": "檔案", "files": "檔案",
"download": "下載", "download": "下載"
"noMatch": "no match"
} }
} }
}; };

View file

@ -36,6 +36,7 @@
border-right: none; border-right: none;
overflow: hidden; overflow: hidden;
white-space: nowrap; white-space: nowrap;
font-size: 16px;
a, a:active, a:visited { a, a:active, a:visited {
display: block; display: block;

View file

@ -10,7 +10,6 @@
border-top: 1px solid rgb(210,210,210); border-top: 1px solid rgb(210,210,210);
color: #999; color: #999;
font-size: @smaller-font;
text-align: center; text-align: center;
a, a:active, a:visited { a, a:active, a:visited {

View file

@ -2,6 +2,7 @@
#content { #content {
max-width: 960px; max-width: 960px;
margin: 50px auto; margin: 50px auto;
font-size: 16px;
} }

View file

@ -0,0 +1,30 @@
#download {
display: none;
.topbar-right;
.transition(all 0.2s ease-in-out);
&.failed {
background-color: rgba(255,0,0,0.5);
}
}
#download-auth {
display: none;
position: fixed;
z-index: 5;
left: 0;
top: 0;
.vert-gradient(rgb(241,241,241), rgb(228,228,228));
border: 1px solid rgb(210,210,210);
input {
display: block;
margin: 4px 6px;
border: 1px solid rgb(210,210,210);
font-family: Ubuntu, sans-serif;
color: #555;
background-color: rgba(255,255,255,1);
width: 100px;
}
}

View file

@ -13,6 +13,8 @@
clear: both; clear: both;
&.header { &.header {
font-size: 13px;
a, a:active, a:visited { a, a:active, a:visited {
padding-bottom: 18px; padding-bottom: 18px;
color: #555; color: #555;
@ -42,7 +44,6 @@
} }
} }
&.entry { &.entry {
a, a:active, a:visited { a, a:active, a:visited {
display: block; display: block;
color: #555; color: #555;

View file

@ -0,0 +1,19 @@
#filter {
.topbar-right;
input {
display: none;
border: none;
font-family: Ubuntu, sans-serif;
color: #555;
background-color: rgba(0,0,0,0);
width: 100px;
}
&.current {
input {
display: inline;
}
}
}

View file

@ -56,4 +56,8 @@ body#h5ai-info {
} }
} }
} }
#bottombar {
font-size: 13px;
}
} }

View file

@ -5,32 +5,31 @@
width: 100%; width: 100%;
left: 0; left: 0;
top: 0; top: 0;
font-size: @smaller-font;
.vert-gradient(rgb(241,241,241), rgb(228,228,228)); .vert-gradient(rgb(241,241,241), rgb(228,228,228));
border-bottom: 1px solid rgb(210,210,210); border-bottom: 1px solid rgb(210,210,210);
} }
.nav-highlight { .topbar-highlight {
background-color: rgba(255,255,255,0.5); background-color: rgba(255,255,255,0.5);
opacity: 1.0; opacity: 1.0;
} }
.nav-hover { .topbar-hover {
.nav-highlight; .topbar-highlight;
color: #e80; color: #e80;
} }
@nav-sep-border: 1px solid rgba(0,0,0,0.05); @topbar-sep-border: 1px solid rgba(0,0,0,0.05);
.nav-left { .topbar-left {
float: left; float: left;
border-right: @nav-sep-border; border-right: @topbar-sep-border;
} }
.nav-right { .topbar-right {
float: right; float: right;
border-left: @nav-sep-border; border-left: @topbar-sep-border;
} }
@ -53,12 +52,12 @@
padding: 0 10px; padding: 0 10px;
&:hover, &.hover { &:hover, &.hover {
.nav-hover; .topbar-hover;
} }
} }
.current { .current {
a, span.element { a, span.element {
.nav-highlight; .topbar-highlight;
} }
} }
img { img {
@ -71,7 +70,7 @@
margin-left: 6px; margin-left: 6px;
} }
.crumb { .crumb {
.nav-left; .topbar-left;
.hint { .hint {
margin-left: 8px; margin-left: 8px;
font-style: italic; font-style: italic;
@ -84,58 +83,6 @@
} }
} }
.view { .view {
.nav-right; .topbar-right;
}
}
#filter {
.nav-right;
input {
display: none;
border: none;
font-family: Ubuntu, sans-serif;
color: #555;
background-color: rgba(0,0,0,0);
width: 100px;
}
&.current {
input {
display: inline;
}
}
}
#download {
display: none;
.nav-right;
.transition(all 0.2s ease-in-out);
&.failed {
background-color: rgba(255,0,0,0.5);
}
}
#download-auth {
display: none;
position: fixed;
z-index: 5;
left: 0;
top: 0;
font-size: @smaller-font;
.vert-gradient(rgb(241,241,241), rgb(228,228,228));
border: 1px solid rgb(210,210,210);
input {
display: block;
margin: 4px 6px;
border: 1px solid rgb(210,210,210);
font-family: Ubuntu, sans-serif;
color: #555;
background-color: rgba(255,255,255,1);
width: 100px;
} }
} }

View file

@ -7,7 +7,6 @@
height: 100%; height: 100%;
z-index: 3; z-index: 3;
overflow: auto; overflow: auto;
font-size: @smaller-font;
padding: 8px; padding: 8px;
background-color: rgb(241,241,241); background-color: rgb(241,241,241);
border-right: 2px solid rgb(221,221,221); border-right: 2px solid rgb(221,221,221);

View file

@ -7,25 +7,31 @@
body { body {
font-family: Ubuntu, sans-serif; font-family: Ubuntu, sans-serif;
font-size: 16px; font-size: 13px;
color: #555; color: #555;
background-color: #fff; background-color: #fff;
margin: 30px; margin: 30px;
} }
@smaller-font: 15px;
@import "inc/topbar"; @import "inc/topbar";
@import "inc/download";
@import "inc/filter";
@import "inc/content"; @import "inc/content";
@import "inc/extended"; @import "inc/extended";
@import "inc/bottombar"; @import "inc/bottombar";
@import "inc/l10n"; @import "inc/l10n";
@import "inc/tree"; @import "inc/tree";
@import "inc/context"; @import "inc/context";
@import "inc/apache-autoindex-table"; @import "inc/apache-autoindex-table";
@import "inc/responsive"; @import "inc/responsive";
@import "inc/h5ai-info";
html.js .hideOnJs, html.no-js .hideOnNoJs { html.js .hideOnJs, html.no-js .hideOnNoJs {
display: none; display: none;
@ -39,5 +45,3 @@ html.oldie {
} }
} }
@import "inc/h5ai-info";

View file

@ -22,11 +22,13 @@
version %BUILD_VERSION% version %BUILD_VERSION%
<h2>server supports</h2> <h2>server supports</h2>
<ul id="tests"> <ul id="tests">
<li id="test-php"><span class="test-label">php version</span><span class="test-result">?</span></li> <li class="test" data-id="php"><span class="test-label">php version</span><span class="test-result">?</span></li>
<li id="test-cache"><span class="test-label">cache</span><span class="test-result">?</span></li> <li class="test" data-id="cache"><span class="test-label">cache</span><span class="test-result">?</span></li>
<li id="test-thumbs"><span class="test-label">thumbnails</span><span class="test-result">?</span></li> <li class="test" data-id="thumbs"><span class="test-label">thumbnails</span><span class="test-result">?</span></li>
<li id="test-temp"><span class="test-label">temp directory</span><span class="test-result">?</span></li> <li class="test" data-id="temp"><span class="test-label">temp directory</span><span class="test-result">?</span></li>
<li id="test-download"><span class="test-label">packaged downloads</span><span class="test-result">?</span></li> <li class="test" data-id="archive"><span class="test-label">php tar and zip</span><span class="test-result">?</span></li>
<li class="test" data-id="tar"><span class="test-label">shell tar</span><span class="test-result">?</span></li>
<li class="test" data-id="zip"><span class="test-label">shell zip</span><span class="test-result">?</span></li>
</ul> </ul>
<div id="bottombar" class="clearfix"> <div id="bottombar" class="clearfix">
<span class="left"> <span class="left">

View file

@ -3,7 +3,7 @@ module.define('core/settings', [H5AI_CONFIG], function (config) {
var defaults = { var defaults = {
rootAbsHref: '/', rootAbsHref: '/',
h5aiAbsHref: '/_h5ai/', h5aiAbsHref: '/_h5ai/'
}; };
return _.extend({}, defaults, config.options); return _.extend({}, defaults, config.options);
@ -31,7 +31,7 @@ module.define('core/types', [H5AI_CONFIG], function (config) {
} else { } else {
fileNames[match] = type; fileNames[match] = type;
} }
}) });
}); });
}, },
@ -61,5 +61,33 @@ module.define('core/types', [H5AI_CONFIG], function (config) {
module.define('core/langs', [H5AI_CONFIG], function (config) { module.define('core/langs', [H5AI_CONFIG], function (config) {
return _.extend({}, config.langs); var defaults = {
lang: 'unknown',
details: 'details',
icons: 'icons',
name: 'Name',
lastModified: 'Last modified',
size: 'Size',
parentDirectory: 'Parent Directory',
empty: 'empty',
folders: 'folders',
files: 'files',
download: 'download',
noMatch: 'no match',
dateFormat: 'YYYY-MM-DD HH:mm'
},
translations = {},
parse = function (langs) {
_.each(langs, function (trans, lang) {
translations[lang] = _.extend({}, defaults, trans);
});
};
parse(_.extend({}, config.langs));
return translations;
}); });

View file

@ -3,10 +3,11 @@ module.define('ext/download', [jQuery, 'core/settings', 'core/resource', 'core/e
var defaults = { var defaults = {
enabled: false, enabled: false,
format: 'tar' execution: 'php',
format: 'zip'
}, },
settings = _.extend({}, defaults, allsettings['download']), settings = _.extend({}, defaults, allsettings.download),
formats = ['tar', 'zip'], formats = ['tar', 'zip'],
@ -38,7 +39,7 @@ module.define('ext/download', [jQuery, 'core/settings', 'core/resource', 'core/e
$img.attr('src', resource.image('download')); $img.attr('src', resource.image('download'));
if (response) { if (response) {
if (response.status === 'ok') { if (response.code === 0) {
setTimeout(function () { // wait here so the img above can be updated in time setTimeout(function () { // wait here so the img above can be updated in time
window.location = resource.api() + '?action=getarchive&id=' + response.id + '&as=h5ai-selection.' + settings.format; window.location = resource.api() + '?action=getarchive&id=' + response.id + '&as=h5ai-selection.' + settings.format;
@ -68,6 +69,7 @@ module.define('ext/download', [jQuery, 'core/settings', 'core/resource', 'core/e
url: resource.api(), url: resource.api(),
data: { data: {
action: 'archive', action: 'archive',
execution: settings.execution,
format: settings.format, format: settings.format,
hrefs: hrefsStr hrefs: hrefsStr
}, },
@ -95,15 +97,13 @@ module.define('ext/download', [jQuery, 'core/settings', 'core/resource', 'core/e
onSelection = function (entries) { onSelection = function (entries) {
var $download = $('#download').appendTo('#navbar');
selectedHrefsStr = ''; selectedHrefsStr = '';
if (entries.length) { if (entries.length) {
selectedHrefsStr = _.map(entries, function (entry) { selectedHrefsStr = _.map(entries, function (entry) {
return entry.absHref; return entry.absHref;
}).join(':'); }).join(':');
$download.show(); $download.appendTo('#navbar').show();
} else { } else {
$download.hide(); $download.hide();
$downloadAuth.hide(); $downloadAuth.hide();
@ -117,13 +117,13 @@ module.define('ext/download', [jQuery, 'core/settings', 'core/resource', 'core/e
} }
$download = $(downloadBtnTemplate) $download = $(downloadBtnTemplate)
.appendTo('#navbar')
.find('a').on('click', function (event) { .find('a').on('click', function (event) {
event.preventDefault(); event.preventDefault();
$downloadAuth.hide(); $downloadAuth.hide();
requestArchive(selectedHrefsStr); requestArchive(selectedHrefsStr);
}); }).end()
.appendTo('#navbar');
$img = $download.find('img'); $img = $download.find('img');
$downloadAuth = $(authTemplate).appendTo('body'); $downloadAuth = $(authTemplate).appendTo('body');

View file

@ -4,8 +4,7 @@ module.define('ext/l10n', [jQuery, 'core/settings', 'core/langs', 'core/format',
var defaults = { var defaults = {
enabled: true, enabled: true,
lang: 'en', lang: 'en',
useBrowserLang: true, useBrowserLang: true
defaultDateFormat: 'YYYY-MM-DD HH:mm'
}, },
settings = _.extend({}, defaults, allsettings.l10n), settings = _.extend({}, defaults, allsettings.l10n),
@ -52,7 +51,7 @@ module.define('ext/l10n', [jQuery, 'core/settings', 'core/langs', 'core/format',
$('.langOption.' + lang).addClass('current'); $('.langOption.' + lang).addClass('current');
} }
format.setDefaultDateFormat(currentLang.dateFormat || settings.defaultDateFormat); format.setDefaultDateFormat(currentLang.dateFormat);
$('#extended .entry .date').each(function () { $('#extended .entry .date').each(function () {

View file

@ -7,8 +7,9 @@ module.define('ext/select', [jQuery, 'core/settings', 'core/event'], function ($
settings = _.extend({}, defaults, allsettings.select), settings = _.extend({}, defaults, allsettings.select),
x = 0, x = 0, y = 0,
y = 0, l = 0, t = 0, w = 0, h = 0,
shrink = 1/3,
$document = $(document), $document = $(document),
$selectionRect = $('<div id="selection-rect"></div>'), $selectionRect = $('<div id="selection-rect"></div>'),
@ -24,16 +25,17 @@ module.define('ext/select', [jQuery, 'core/settings', 'core/event'], function ($
selectionUpdate = function (event) { selectionUpdate = function (event) {
var l = Math.min(x, event.pageX), l = Math.min(x, event.pageX);
t = Math.min(y, event.pageY), t = Math.min(y, event.pageY);
w = Math.abs(x - event.pageX), w = Math.abs(x - event.pageX);
h = Math.abs(y - event.pageY), h = Math.abs(y - event.pageY);
selRect;
event.preventDefault(); event.preventDefault();
$selectionRect.css({left: l, top: t, width: w, height: h}); $selectionRect
.stop(true, true)
.css({left: l, top: t, width: w, height: h});
selRect = $selectionRect.fracs('rect'); var selRect = $selectionRect.fracs('rect');
$('#extended .entry').removeClass('selecting').each(function () { $('#extended .entry').removeClass('selecting').each(function () {
var $entry = $(this), var $entry = $(this),
@ -49,10 +51,13 @@ module.define('ext/select', [jQuery, 'core/settings', 'core/event'], function ($
event.preventDefault(); event.preventDefault();
$document.off('mousemove', selectionUpdate); $document.off('mousemove', selectionUpdate);
$selectionRect.fadeOut(300);
$('#extended .entry.selecting.selected').removeClass('selecting').removeClass('selected'); $('#extended .entry.selecting.selected').removeClass('selecting').removeClass('selected');
$('#extended .entry.selecting').removeClass('selecting').addClass('selected'); $('#extended .entry.selecting').removeClass('selecting').addClass('selected');
publish(); publish();
$selectionRect
.stop(true, true)
.animate({left: l + w * 0.5 * shrink, top: t + h * 0.5 * shrink, width: w * (1 - shrink), height: h * (1 - shrink), opacity: 0}, 300);
}, },
selectionStart = function (event) { selectionStart = function (event) {
@ -61,8 +66,13 @@ module.define('ext/select', [jQuery, 'core/settings', 'core/event'], function ($
x = event.pageX; x = event.pageX;
y = event.pageY; y = event.pageY;
l = x;
t = y;
w = 0;
h = 0;
// only on left button and don't block the scrollbars // only on left button and don't block the scrollbars
if (event.button !== 0 || x >= view.right || y >= view.bottom) { if (event.button !== 0 || l >= view.right || t >= view.bottom) {
return; return;
} }
@ -72,7 +82,10 @@ module.define('ext/select', [jQuery, 'core/settings', 'core/event'], function ($
$('#extended .entry').removeClass('selected'); $('#extended .entry').removeClass('selected');
publish(); publish();
} }
$selectionRect.show().css({left: x, top: y, width: 0, height: 0}); $selectionRect
.stop(true, true)
.css({left: l, top: t, width: w, height: h, opacity: 1})
.show();
$document $document
.on('mousemove', selectionUpdate) .on('mousemove', selectionUpdate)

View file

@ -27,14 +27,14 @@ module.define('ext/thumbnails', [jQuery, 'core/settings', 'core/resource', 'core
var $imgBig = entry.$extended.find('.icon.big img'); var $imgBig = entry.$extended.find('.icon.big img');
requestThumb($imgSmall, { requestThumb($imgSmall, {
action: 'thumbsrc', action: 'getthumbsrc',
href: entry.absHref, href: entry.absHref,
width: 16, width: 16,
height: 16, height: 16,
mode: 'square' mode: 'square'
}); });
requestThumb($imgBig, { requestThumb($imgBig, {
action: 'thumbsrc', action: 'getthumbsrc',
href: entry.absHref, href: entry.absHref,
width: 100, width: 100,
height: 48, height: 48,

View file

@ -14,10 +14,10 @@ module.define('h5ai-info', [jQuery, 'core/resource'], function ($, resource) {
handleChecksResponse = function (response) { handleChecksResponse = function (response) {
_.each(['php', 'cache', 'thumbs', 'temp', 'download'], function (test) { $('.test').each(function () {
setCheckResult('#test-' + test, response && response[test]); setCheckResult(this, response && response[$(this).data('id')]);
}) });
}, },
checks = function () { checks = function () {
@ -25,7 +25,7 @@ module.define('h5ai-info', [jQuery, 'core/resource'], function ($, resource) {
$.ajax({ $.ajax({
url: resource.api(), url: resource.api(),
data: { data: {
action: 'checks' action: 'getchecks'
}, },
type: 'POST', type: 'POST',
dataType: 'json', dataType: 'json',

View file

@ -7,85 +7,41 @@ $h5ai = new H5ai();
$options = $h5ai->getOptions(); $options = $h5ai->getOptions();
function fail($code, $msg, $cond = true) { function json_exit($obj) {
$obj["code"] = 0;
echo json_encode($obj);
exit;
}
function json_fail($code, $msg = "", $cond = true) {
if ($cond) { if ($cond) {
echo "$code: $msg"; echo json_encode(array("code" => $code, "msg" => $msg));
exit; exit;
} }
} }
function checkKeys($keys) { function check_keys($keys) {
$values = array(); $values = array();
foreach ($keys as $key) { foreach ($keys as $key) {
fail(1, "parameter '$key' is missing", !array_key_exists($key, $_REQUEST)); json_fail(101, "parameter '$key' is missing", !array_key_exists($key, $_REQUEST));
$values[] = $_REQUEST[$key]; $values[] = $_REQUEST[$key];
} }
return $values; return $values;
} }
list($action) = checkKeys(array("action")); list($action) = check_keys(array("action"));
if ($action === "httpcodes") { if ($action === "getthumbsrc") {
list($hrefs) = checkKeys(array("hrefs"));
function getHttpCodes($h5ai, $hrefs) {
$codes = array();
foreach ($hrefs as $href) {
$href = trim($href);
if (strlen($href) > 0) {
$codes[$href] = $h5ai->getHttpCode($href);
}
}
return $codes;
}
$hrefs = preg_split("/;/", $hrefs);
$codes = getHttpCodes($h5ai, $hrefs);
echo count($codes) === 0 ? "{}" : json_encode($codes);
}
else if ($action === "thumb") {
fail(0, "thumbs are disabled", !$options["thumbnails"]["enabled"]);
list($srcAbsHref, $width, $height, $mode) = checkKeys(array("href", "width", "height", "mode"));
H5ai::req_once("/php/inc/Thumbnail.php");
H5ai::req_once("/php/inc/Image.php");
$srcAbsPath = $h5ai->getRootAbsPath() . rawurldecode($srcAbsHref);
if (!Thumbnail::isUsable()) {
Image::showImage($srcAbsPath);
exit;
}
$thumbnail = new Thumbnail($h5ai, $srcAbsHref, $mode, $width, $height);
$thumbnail->create(1);
if (file_exists($thumbnail->getPath())) {
Image::showImage($thumbnail->getPath());
} else {
$image = new Image();
$image->setSource($srcAbsPath);
$image->thumb($mode, $width, $height);
$image->showDest();
}
}
else if ($action === "thumbsrc") {
if (!$options["thumbnails"]["enabled"]) { if (!$options["thumbnails"]["enabled"]) {
echo json_encode(array("code" => 1, "absHref" => null)); json_fail(1, "thumbnails disabled");
exit;
} }
list($srcAbsHref, $width, $height, $mode) = checkKeys(array("href", "width", "height", "mode")); list($srcAbsHref, $width, $height, $mode) = check_keys(array("href", "width", "height", "mode"));
H5ai::req_once("/php/inc/Thumbnail.php"); H5ai::req_once("/php/inc/Thumbnail.php");
H5ai::req_once("/php/inc/Image.php"); H5ai::req_once("/php/inc/Image.php");
@ -93,75 +49,77 @@ else if ($action === "thumbsrc") {
$srcAbsPath = $h5ai->getRootAbsPath() . rawurldecode($srcAbsHref); $srcAbsPath = $h5ai->getRootAbsPath() . rawurldecode($srcAbsHref);
if (!Thumbnail::isUsable()) { if (!Thumbnail::isUsable()) {
echo json_encode(array("code" => 2, "absHref" => null)); json_fail(2, "thumbnails not supported");
exit;
} }
$thumbnail = new Thumbnail($h5ai, $srcAbsHref, $mode, $width, $height); $thumbnail = new Thumbnail($h5ai, $srcAbsHref, $mode, $width, $height);
$thumbnail->create(1); $thumbnail->create(1);
if (!file_exists($thumbnail->getPath())) { if (!file_exists($thumbnail->getPath())) {
echo json_encode(array("code" => 3, "absHref" => null)); json_fail(3, "thumbnail creation failed");
exit;
} }
echo json_encode(array("code" => 0, "absHref" => $thumbnail->getHref())); json_exit(array("absHref" => $thumbnail->getHref()));
} }
else if ($action === "archive") { else if ($action === "archive") {
fail(0, "download is disabled", !$options["download"]["enabled"]); json_fail(1, "downloads disabled", !$options["download"]["enabled"]);
list($format, $hrefs) = checkKeys(array("format", "hrefs")); list($execution, $format, $hrefs) = check_keys(array("execution", "format", "hrefs"));
H5ai::req_once("/php/inc/Archive.php"); H5ai::req_once("/php/inc/Archive.php");
$archive = new Archive($h5ai); $archive = new Archive($h5ai);
$hrefs = explode(":", trim($hrefs)); $hrefs = explode(":", trim($hrefs));
$target = $archive->create($format, $hrefs); $target = $archive->create($execution, $format, $hrefs);
if (is_string($target)) { if (!is_string($target)) {
$response = array('status' => 'ok', 'id' => basename($target), 'size' => filesize($target)); json_fail($target, "package creation failed");
} else {
$response = array('status' => 'failed', 'code' => $target);
} }
echo json_encode($response);
json_exit(array("id" => basename($target), "size" => filesize($target)));
} }
else if ($action === "getarchive") { else if ($action === "getarchive") {
fail(0, "download is disabled", !$options["download"]["enabled"]); json_fail(1, "downloads disabled", !$options["download"]["enabled"]);
list($id,$as) = checkKeys(array("id", "as")); list($id, $as) = check_keys(array("id", "as"));
fail(1, "file not found: " . $id, !preg_match("/^h5ai-selection-/", $id)); json_fail(2, "file not found", !preg_match("/^h5ai-selection-/", $id));
$target = H5ai::normalize_path(sys_get_temp_dir(), true) . $id; $target = H5ai::normalize_path(sys_get_temp_dir(), true) . $id;
fail(2, "file not found: " . $id, !file_exists($target)); json_fail(3, "file not found", !file_exists($target));
header("Content-Disposition: attachment; filename=\"$as\"");
header("Content-Type: application/octet-stream"); header("Content-Type: application/octet-stream");
header("Content-Length: " . filesize($target)); header("Content-Length: " . filesize($target));
header("Content-Disposition: attachment; filename=\"$as\"");
header("Connection: close"); header("Connection: close");
readfile($target); readfile($target);
} }
else if ($action === "checks") { else if ($action === "getchecks") {
$response = array( $php = $h5ai->checks["php"];
'php' => $h5ai->checks["php"], $cache = $php && $h5ai->checks["cache"];
'cache' => $h5ai->checks["php"] && $h5ai->checks["cache"], $temp = $php && $h5ai->checks["temp"];
'thumbs' => $h5ai->checks["php"] && $h5ai->checks["cache"] && $h5ai->checks["gd"],
'temp' => $h5ai->checks["php"] && $h5ai->checks["temp"], json_exit(array(
'download' => $h5ai->checks["php"] && $h5ai->checks["temp"] && $h5ai->checks["archive"] "php" => $php,
); "cache" => $cache,
echo json_encode($response); "thumbs" => $cache && $h5ai->checks["gd"],
"temp" => $temp,
"archive" => $temp && $h5ai->checks["archive"],
"tar" => $temp && $h5ai->checks["tar"],
"zip" => $temp && $h5ai->checks["zip"]
));
} }
else { else {
fail(1, "unsupported 'action' specified"); json_fail(100, "unsupported action");
} }

View file

@ -2,7 +2,11 @@
class Archive { class Archive {
private $h5ai; private static $TAR_CMD = "$(cd [ROOTDIR] && tar --no-recursion -cf [TARGET] [DIRS] [FILES])";
private static $ZIP_CMD = "$(cd [ROOTDIR] && zip [TARGET] [FILES])";
private $h5ai, $dirs, $files, $sc401;
public function __construct($h5ai) { public function __construct($h5ai) {
@ -11,66 +15,116 @@ class Archive {
} }
public function create($format, $hrefs) { public function create($execution, $format, $hrefs) {
$this->dirs = array();
$this->files = array();
$this->sc401 = false;
$this->addHrefs($hrefs);
if ($this->sc401) {
return 401;
} else if (count($this->dirs) === 0 && count($this->files) === 0) {
return 404;
}
$target = H5ai::normalize_path(sys_get_temp_dir(), true) . "h5ai-selection-" . microtime(true) . rand() . "." . $format;
try {
if ($execution === "shell") {
if ($format === "tar") {
$cmd = Archive::$TAR_CMD;
} else if ($format === "zip") {
$cmd = Archive::$ZIP_CMD;
} else {
return null;
}
$cmd = str_replace("[ROOTDIR]", "\"" . $this->h5ai->getRootAbsPath() . "\"", $cmd);
$cmd = str_replace("[TARGET]", "\"" . $target . "\"", $cmd);
$cmd = str_replace("[DIRS]", count($this->dirs) ? "\"" . implode("\" \"", array_values($this->dirs)) . "\"" : "", $cmd);
$cmd = str_replace("[FILES]", count($this->files) ? "\"" . implode("\" \"", array_values($this->files)) . "\"" : "", $cmd);
`$cmd`;
} else if ($execution === "php") {
$target = H5ai::normalize_path(sys_get_temp_dir(), true) . "h5ai-selection-" . microtime(true) . "-" . rand() . "." . $format;
$archive = new PharData($target); $archive = new PharData($target);
foreach ($this->dirs as $archivedDir) {
$archive->addEmptyDir($archivedDir);
}
foreach ($this->files as $realFile => $archivedFile) {
$archive->addFile($realFile, $archivedFile); // very, very slow :/
}
}
} catch (Exeption $err) {
return 500;
}
return @filesize($target) ? $target : null;
}
private function addHrefs($hrefs) {
foreach ($hrefs as $href) { foreach ($hrefs as $href) {
$d = H5ai::normalize_path(dirname($href), true); $d = H5ai::normalize_path(dirname($href), true);
$n = basename($href); $n = basename($href);
$code = $this->h5ai->getHttpCode($d); $code = $this->h5ai->getHttpCode($d);
if ($code == 401) { if ($code == 401) {
return $code; $this->sc401 = true;
} }
if ($code == "h5ai" && !$this->h5ai->ignoreThisFile($n)) { if ($code == "h5ai" && !$this->h5ai->ignoreThisFile($n)) {
$realFile = $this->h5ai->getAbsPath($href); $realFile = $this->h5ai->getAbsPath($href);
$archivedFile = preg_replace("!^" . $this->h5ai->getRootAbsPath() . "!", "", $realFile); $archivedFile = preg_replace("!^" . H5ai::normalize_path($this->h5ai->getRootAbsPath(), true) . "!", "", $realFile);
if (is_dir($realFile)) { if (is_dir($realFile)) {
$rcode = $this->addDir($archive, $realFile, $archivedFile); $this->addDir($realFile, $archivedFile);
if ($rcode == 401) {
return $rcode;
}
} else { } else {
$this->addFile($archive, $realFile, $archivedFile); $this->addFile($realFile, $archivedFile);
}
} }
} }
} }
return filesize($target) ? $target : null;
}
private function addFile($realFile, $archivedFile) {
private function addFile($archive, $realFile, $archivedFile) {
if (is_readable($realFile)) { if (is_readable($realFile)) {
$archive->addFile($realFile, $archivedFile); $this->files[$realFile] = $archivedFile;
} }
} }
private function addDir($archive, $realDir, $archivedDir) { private function addDir($realDir, $archivedDir) {
$code = $this->h5ai->getHttpCode($this->h5ai->getAbsHref($realDir)); $code = $this->h5ai->getHttpCode($this->h5ai->getAbsHref($realDir));
if ($code == 401) {
$this->sc401 = true;
}
if ($code == "h5ai") { if ($code == "h5ai") {
$archive->addEmptyDir($archivedDir); $this->dirs[] = $archivedDir;
$files = $this->h5ai->readDir($realDir); $files = $this->h5ai->readDir($realDir);
foreach ($files as $file) { foreach ($files as $file) {
$realFile = $realDir . "/" . $file; $realFile = $realDir . "/" . $file;
$archivedFile = $archivedDir . "/" . $file; $archivedFile = $archivedDir . "/" . $file;
if (is_dir($realFile)) { if (is_dir($realFile)) {
$rcode = $this->addDir($archive, $realFile, $archivedFile); $this->addDir($realFile, $archivedFile);
if ($rcode == 401) {
return $rcode;
}
} else { } else {
$this->addFile($archive, $realFile, $archivedFile); $this->addFile($realFile, $archivedFile);
} }
} }
} }
return $code;
} }
} }

View file

@ -56,9 +56,11 @@ class H5ai {
private $h5aiAbsPath, private $h5aiAbsPath,
$rootAbsPath, $ignore, $ignoreRE, $rootAbsPath, $ignore, $ignoreRE,
$config, $options, $config, $options,
$cache,
$rootAbsHref, $h5aiAbsHref, $rootAbsHref, $h5aiAbsHref,
$absHref, $absPath; $absHref, $absPath,
$cache;
public $checks;
public function __construct() { public function __construct() {
@ -86,7 +88,9 @@ class H5ai {
"archive" => class_exists("PharData"), "archive" => class_exists("PharData"),
"gd" => GD_VERSION != "GD_VERSION", "gd" => GD_VERSION != "GD_VERSION",
"cache" => is_writable($this->h5aiAbsPath . "/cache"), "cache" => is_writable($this->h5aiAbsPath . "/cache"),
"temp" => is_writable(sys_get_temp_dir()) "temp" => is_writable(sys_get_temp_dir()),
"tar" => preg_match("/tar$/", `which tar`) > 0,
"zip" => preg_match("/zip$/", `which zip`) > 0
); );
} }