mirror of
https://github.com/simon987/Simple-Incremental-Search-Tool.git
synced 2025-04-19 10:16:41 +00:00
Code cleanup
This commit is contained in:
parent
4eb9cf6b63
commit
87f35571fa
@ -8,9 +8,10 @@ default_options = {
|
|||||||
"EbookContentLength": "2000",
|
"EbookContentLength": "2000",
|
||||||
"MimeGuesser": "extension", # extension, content
|
"MimeGuesser": "extension", # extension, content
|
||||||
"CheckSumCalculators": "", # md5, sha1, sha256
|
"CheckSumCalculators": "", # md5, sha1, sha256
|
||||||
"FileParsers": "media, text, picture, font" # media, text, picture
|
"FileParsers": "media, text, picture, font, pdf, docx, spreadsheet, ebook"
|
||||||
}
|
}
|
||||||
|
|
||||||
index_every = 10000
|
index_every = 10000
|
||||||
nGramMin = 3
|
nGramMin = 3
|
||||||
nGramMax = 3
|
nGramMax = 3
|
||||||
|
bcrypt_rounds = 14
|
||||||
|
@ -12,6 +12,7 @@ from thumbnail import ThumbnailGenerator
|
|||||||
from storage import Directory
|
from storage import Directory
|
||||||
import shutil
|
import shutil
|
||||||
import config
|
import config
|
||||||
|
from ctypes import c_char_p
|
||||||
|
|
||||||
|
|
||||||
class RunningTask:
|
class RunningTask:
|
||||||
@ -166,15 +167,13 @@ class TaskManager:
|
|||||||
done.value = 1
|
done.value = 1
|
||||||
|
|
||||||
def cancel_task(self):
|
def cancel_task(self):
|
||||||
self.current_task = None
|
self.current_task.done.value = 1
|
||||||
self.current_process.terminate()
|
|
||||||
|
|
||||||
def check_new_task(self):
|
def check_new_task(self):
|
||||||
|
|
||||||
if self.current_task is None:
|
if self.current_task is None:
|
||||||
for i in sorted(self.storage.tasks(), reverse=True):
|
for i in sorted(self.storage.tasks(), reverse=True):
|
||||||
if not self.storage.tasks()[i].completed:
|
self.start_task(self.storage.tasks()[i])
|
||||||
self.start_task(self.storage.tasks()[i])
|
|
||||||
else:
|
else:
|
||||||
if self.current_task.done.value == 1:
|
if self.current_task.done.value == 1:
|
||||||
|
|
||||||
@ -183,3 +182,4 @@ class TaskManager:
|
|||||||
self.current_task = None
|
self.current_task = None
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
10
indexer.py
10
indexer.py
@ -78,8 +78,14 @@ class Indexer:
|
|||||||
self.es.indices.put_mapping(body={"properties": {
|
self.es.indices.put_mapping(body={"properties": {
|
||||||
"path": {"type": "text", "analyzer": "path_analyser", "copy_to": "suggest-path"},
|
"path": {"type": "text", "analyzer": "path_analyser", "copy_to": "suggest-path"},
|
||||||
"suggest-path": {"type": "completion", "analyzer": "keyword"},
|
"suggest-path": {"type": "completion", "analyzer": "keyword"},
|
||||||
"mime": {"type": "text", "analyzer": "path_analyser", "copy_to": "mime_kw"},
|
"mime": {"type": "keyword"},
|
||||||
"mime_kw": {"type": "keyword"},
|
"encoding": {"type": "keyword"},
|
||||||
|
"format_name": {"type": "keyword"},
|
||||||
|
"format_long_name": {"type": "keyword"},
|
||||||
|
"duration": {"type": "float"},
|
||||||
|
"width": {"type": "integer"},
|
||||||
|
"height": {"type": "integer"},
|
||||||
|
"mtime": {"type": "integer"},
|
||||||
"directory": {"type": "short"},
|
"directory": {"type": "short"},
|
||||||
"name": {"analyzer": "my_nGram", "type": "text"},
|
"name": {"analyzer": "my_nGram", "type": "text"},
|
||||||
"album": {"analyzer": "my_nGram", "type": "text"},
|
"album": {"analyzer": "my_nGram", "type": "text"},
|
||||||
|
13
parsing.py
13
parsing.py
@ -181,18 +181,9 @@ class MediaFileParser(GenericFileParser):
|
|||||||
|
|
||||||
if "format" in metadata:
|
if "format" in metadata:
|
||||||
|
|
||||||
if "bit_rate" in metadata["format"]:
|
|
||||||
info["bit_rate"] = int(metadata["format"]["bit_rate"])
|
|
||||||
|
|
||||||
if "nb_streams" in metadata["format"]:
|
|
||||||
info["nb_streams"] = int(metadata["format"]["nb_streams"])
|
|
||||||
|
|
||||||
if "duration" in metadata["format"]:
|
if "duration" in metadata["format"]:
|
||||||
info["duration"] = float(metadata["format"]["duration"])
|
info["duration"] = float(metadata["format"]["duration"])
|
||||||
|
|
||||||
if "format_name" in metadata["format"]:
|
|
||||||
info["format_name"] = metadata["format"]["format_name"]
|
|
||||||
|
|
||||||
if "format_long_name" in metadata["format"]:
|
if "format_long_name" in metadata["format"]:
|
||||||
info["format_long_name"] = metadata["format"]["format_long_name"]
|
info["format_long_name"] = metadata["format"]["format_long_name"]
|
||||||
|
|
||||||
@ -243,10 +234,10 @@ class PictureFileParser(GenericFileParser):
|
|||||||
with open(full_path, "rb") as image_file:
|
with open(full_path, "rb") as image_file:
|
||||||
with Image.open(image_file) as image:
|
with Image.open(image_file) as image:
|
||||||
info["mode"] = image.mode
|
info["mode"] = image.mode
|
||||||
info["format"] = image.format
|
info["format_name"] = image.format
|
||||||
info["width"] = image.width
|
info["width"] = image.width
|
||||||
info["height"] = image.height
|
info["height"] = image.height
|
||||||
except (OSError, ValueError) as e:
|
except (OSError, ValueError):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
return info
|
return info
|
||||||
|
10
run.py
10
run.py
@ -263,13 +263,6 @@ def get_current_task():
|
|||||||
return ""
|
return ""
|
||||||
|
|
||||||
|
|
||||||
@app.route("/task/current/cancel")
|
|
||||||
def cancel_current_task():
|
|
||||||
|
|
||||||
tm.cancel_task()
|
|
||||||
return redirect("/task")
|
|
||||||
|
|
||||||
|
|
||||||
@app.route("/task/add")
|
@app.route("/task/add")
|
||||||
def task_add():
|
def task_add():
|
||||||
type = request.args.get("type")
|
type = request.args.get("type")
|
||||||
@ -284,6 +277,9 @@ def task_add():
|
|||||||
def task_del(task_id):
|
def task_del(task_id):
|
||||||
storage.del_task(task_id)
|
storage.del_task(task_id)
|
||||||
|
|
||||||
|
if tm.current_task is not None and task_id == tm.current_task.task.id:
|
||||||
|
tm.cancel_task()
|
||||||
|
|
||||||
return redirect("/task")
|
return redirect("/task")
|
||||||
|
|
||||||
|
|
||||||
|
@ -57,7 +57,7 @@ class Search:
|
|||||||
"aggs": {
|
"aggs": {
|
||||||
"mimeTypes": {
|
"mimeTypes": {
|
||||||
"terms": {
|
"terms": {
|
||||||
"field": "mime_kw",
|
"field": "mime",
|
||||||
"size": 10000
|
"size": 10000
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -10,7 +10,6 @@ class MediaFileParserTest(TestCase):
|
|||||||
|
|
||||||
info = parser.parse("test_files/cat1.wav")
|
info = parser.parse("test_files/cat1.wav")
|
||||||
|
|
||||||
self.assertEqual(info["bit_rate"], 256044)
|
|
||||||
self.assertEqual(info["format_name"], "wav")
|
self.assertEqual(info["format_name"], "wav")
|
||||||
self.assertEqual(info["format_long_name"], "WAV / WAVE (Waveform Audio)")
|
self.assertEqual(info["format_long_name"], "WAV / WAVE (Waveform Audio)")
|
||||||
self.assertEqual(info["duration"], 20.173875)
|
self.assertEqual(info["duration"], 20.173875)
|
||||||
@ -20,7 +19,6 @@ class MediaFileParserTest(TestCase):
|
|||||||
|
|
||||||
info = parser.parse("test_files/vid1.mp4")
|
info = parser.parse("test_files/vid1.mp4")
|
||||||
|
|
||||||
self.assertEqual(info["bit_rate"], 513012)
|
|
||||||
self.assertEqual(info["format_name"], "mov,mp4,m4a,3gp,3g2,mj2")
|
self.assertEqual(info["format_name"], "mov,mp4,m4a,3gp,3g2,mj2")
|
||||||
self.assertEqual(info["format_long_name"], "QuickTime / MOV")
|
self.assertEqual(info["format_long_name"], "QuickTime / MOV")
|
||||||
self.assertEqual(info["duration"], 5.334)
|
self.assertEqual(info["duration"], 5.334)
|
||||||
@ -30,7 +28,6 @@ class MediaFileParserTest(TestCase):
|
|||||||
|
|
||||||
info = parser.parse("test_files/vid2.webm")
|
info = parser.parse("test_files/vid2.webm")
|
||||||
|
|
||||||
self.assertEqual(info["bit_rate"], 343153)
|
|
||||||
self.assertEqual(info["format_name"], "matroska,webm")
|
self.assertEqual(info["format_name"], "matroska,webm")
|
||||||
self.assertEqual(info["format_long_name"], "Matroska / WebM")
|
self.assertEqual(info["format_long_name"], "Matroska / WebM")
|
||||||
self.assertEqual(info["duration"], 10.619)
|
self.assertEqual(info["duration"], 10.619)
|
||||||
@ -40,7 +37,6 @@ class MediaFileParserTest(TestCase):
|
|||||||
|
|
||||||
info = parser.parse("test_files/vid3.ogv")
|
info = parser.parse("test_files/vid3.ogv")
|
||||||
|
|
||||||
self.assertEqual(info["bit_rate"], 590261)
|
|
||||||
self.assertEqual(info["format_name"], "ogg")
|
self.assertEqual(info["format_name"], "ogg")
|
||||||
self.assertEqual(info["format_long_name"], "Ogg")
|
self.assertEqual(info["format_long_name"], "Ogg")
|
||||||
self.assertEqual(info["duration"], 10.618867)
|
self.assertEqual(info["duration"], 10.618867)
|
||||||
|
@ -13,7 +13,7 @@ class PictureFileParserTest(TestCase):
|
|||||||
self.assertEqual(info["mode"], "RGB")
|
self.assertEqual(info["mode"], "RGB")
|
||||||
self.assertEqual(info["width"], 420)
|
self.assertEqual(info["width"], 420)
|
||||||
self.assertEqual(info["height"], 315)
|
self.assertEqual(info["height"], 315)
|
||||||
self.assertEqual(info["format"], "JPEG")
|
self.assertEqual(info["format_name"], "JPEG")
|
||||||
|
|
||||||
def test_parse_png(self):
|
def test_parse_png(self):
|
||||||
|
|
||||||
@ -24,7 +24,7 @@ class PictureFileParserTest(TestCase):
|
|||||||
self.assertEqual(info["mode"], "RGBA")
|
self.assertEqual(info["mode"], "RGBA")
|
||||||
self.assertEqual(info["width"], 288)
|
self.assertEqual(info["width"], 288)
|
||||||
self.assertEqual(info["height"], 64)
|
self.assertEqual(info["height"], 64)
|
||||||
self.assertEqual(info["format"], "PNG")
|
self.assertEqual(info["format_name"], "PNG")
|
||||||
|
|
||||||
def test_parse_gif(self):
|
def test_parse_gif(self):
|
||||||
|
|
||||||
@ -35,7 +35,7 @@ class PictureFileParserTest(TestCase):
|
|||||||
self.assertEqual(info["mode"], "P")
|
self.assertEqual(info["mode"], "P")
|
||||||
self.assertEqual(info["width"], 420)
|
self.assertEqual(info["width"], 420)
|
||||||
self.assertEqual(info["height"], 315)
|
self.assertEqual(info["height"], 315)
|
||||||
self.assertEqual(info["format"], "GIF")
|
self.assertEqual(info["format_name"], "GIF")
|
||||||
|
|
||||||
def test_parse_bmp(self):
|
def test_parse_bmp(self):
|
||||||
|
|
||||||
@ -46,4 +46,4 @@ class PictureFileParserTest(TestCase):
|
|||||||
self.assertEqual(info["mode"], "RGB")
|
self.assertEqual(info["mode"], "RGB")
|
||||||
self.assertEqual(info["width"], 150)
|
self.assertEqual(info["width"], 150)
|
||||||
self.assertEqual(info["height"], 200)
|
self.assertEqual(info["height"], 200)
|
||||||
self.assertEqual(info["format"], "BMP")
|
self.assertEqual(info["format_name"], "BMP")
|
||||||
|
5
static/css/fa-brands.min.css
vendored
Normal file
5
static/css/fa-brands.min.css
vendored
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
/*!
|
||||||
|
* Font Awesome Free 5.0.8 by @fontawesome - https://fontawesome.com
|
||||||
|
* License - https://fontawesome.com/license (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License)
|
||||||
|
*/
|
||||||
|
@font-face{font-family:Font Awesome\ 5 Brands;font-style:normal;font-weight:400;src:url(../webfonts/fa-brands-400.eot);src:url(../webfonts/fa-brands-400.eot?#iefix) format("embedded-opentype"),url(../webfonts/fa-brands-400.woff2) format("woff2"),url(../webfonts/fa-brands-400.woff) format("woff"),url(../webfonts/fa-brands-400.ttf) format("truetype"),url(../webfonts/fa-brands-400.svg#fontawesome) format("svg")}.fab{font-family:Font Awesome\ 5 Brands}
|
5
static/css/fa-regular.min.css
vendored
Normal file
5
static/css/fa-regular.min.css
vendored
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
/*!
|
||||||
|
* Font Awesome Free 5.0.8 by @fontawesome - https://fontawesome.com
|
||||||
|
* License - https://fontawesome.com/license (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License)
|
||||||
|
*/
|
||||||
|
@font-face{font-family:Font Awesome\ 5 Free;font-style:normal;font-weight:400;src:url(../webfonts/fa-regular-400.eot);src:url(../webfonts/fa-regular-400.eot?#iefix) format("embedded-opentype"),url(../webfonts/fa-regular-400.woff2) format("woff2"),url(../webfonts/fa-regular-400.woff) format("woff"),url(../webfonts/fa-regular-400.ttf) format("truetype"),url(../webfonts/fa-regular-400.svg#fontawesome) format("svg")}.far{font-family:Font Awesome\ 5 Free;font-weight:400}
|
5
static/css/fa-solid.min.css
vendored
Normal file
5
static/css/fa-solid.min.css
vendored
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
/*!
|
||||||
|
* Font Awesome Free 5.0.8 by @fontawesome - https://fontawesome.com
|
||||||
|
* License - https://fontawesome.com/license (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License)
|
||||||
|
*/
|
||||||
|
@font-face{font-family:Font Awesome\ 5 Free;font-style:normal;font-weight:900;src:url(../webfonts/fa-solid-900.eot);src:url(../webfonts/fa-solid-900.eot?#iefix) format("embedded-opentype"),url(../webfonts/fa-solid-900.woff2) format("woff2"),url(../webfonts/fa-solid-900.woff) format("woff"),url(../webfonts/fa-solid-900.ttf) format("truetype"),url(../webfonts/fa-solid-900.svg#fontawesome) format("svg")}.fa,.fas{font-family:Font Awesome\ 5 Free;font-weight:900}
|
5
static/css/fontawesome.min.css
vendored
Normal file
5
static/css/fontawesome.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
147
static/css/search.css
Normal file
147
static/css/search.css
Normal file
@ -0,0 +1,147 @@
|
|||||||
|
body {overflow-y:scroll;}
|
||||||
|
.document {
|
||||||
|
padding: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.document p {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.document:hover p {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
.badge-video {
|
||||||
|
color: #FFFFFF;
|
||||||
|
background-color: #F27761;
|
||||||
|
}
|
||||||
|
|
||||||
|
.badge-image {
|
||||||
|
color: #FFFFFF;
|
||||||
|
background-color: #AA99C9;
|
||||||
|
}
|
||||||
|
|
||||||
|
.badge-audio {
|
||||||
|
color: #FFFFFF;
|
||||||
|
background-color: #00ADEF;
|
||||||
|
}
|
||||||
|
|
||||||
|
.badge-resolution {
|
||||||
|
color: #212529;
|
||||||
|
background-color: #FFC107;
|
||||||
|
}
|
||||||
|
|
||||||
|
.badge-text {
|
||||||
|
color: #FFFFFF;
|
||||||
|
background-color: #FAAB3C;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-img-top {
|
||||||
|
display: block;
|
||||||
|
min-width: 64px;
|
||||||
|
max-width: 100%;
|
||||||
|
max-height: 256px;
|
||||||
|
width: unset;
|
||||||
|
margin: 0 auto 0;
|
||||||
|
padding: 3px 3px 0 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-img-overlay {
|
||||||
|
pointer-events: none;
|
||||||
|
padding: 0.75rem;
|
||||||
|
|
||||||
|
bottom: 0;
|
||||||
|
top: unset;
|
||||||
|
left: unset;
|
||||||
|
right: unset;
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-title {
|
||||||
|
font-size: 10pt;
|
||||||
|
white-space: nowrap;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.badge {
|
||||||
|
margin-right: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fit {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
padding: 3px;
|
||||||
|
min-width: 64px;
|
||||||
|
max-width: 100%;
|
||||||
|
max-height: 256px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.audio-fit {
|
||||||
|
height: 39px;
|
||||||
|
vertical-align: bottom;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 1200px) {
|
||||||
|
.card-columns {
|
||||||
|
column-count: 4;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 1500px) {
|
||||||
|
.container {
|
||||||
|
max-width: 1440px;
|
||||||
|
}
|
||||||
|
.card-columns {
|
||||||
|
column-count: 5;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.hl {
|
||||||
|
background: #fff217;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content-div {
|
||||||
|
font-family: SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;
|
||||||
|
font-size: 13px;
|
||||||
|
padding: 1em;
|
||||||
|
background-color: #f5f5f5;
|
||||||
|
border: 1px solid #ccc;
|
||||||
|
border-radius: 4px;
|
||||||
|
margin: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.irs-single, .irs-from, .irs-to {
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.irs-slider {
|
||||||
|
cursor: col-resize;
|
||||||
|
}
|
||||||
|
|
||||||
|
.custom-select {
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.irs {
|
||||||
|
margin-top: 1em;
|
||||||
|
margin-bottom: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.inspire-tree .selected > .wholerow, .inspire-tree .selected > .title-wrap:hover + .wholerow
|
||||||
|
{
|
||||||
|
background: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.inspire-tree {
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 14px;
|
||||||
|
font-family: Helvetica, Nueue, Verdana, sans-serif;
|
||||||
|
max-height: 450px;
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-xs {
|
||||||
|
padding: .1rem .3rem;
|
||||||
|
font-size: .875rem;
|
||||||
|
border-radius: .2rem;
|
||||||
|
}
|
604
static/js/search.js
Normal file
604
static/js/search.js
Normal file
@ -0,0 +1,604 @@
|
|||||||
|
let searchBar = document.getElementById("searchBar");
|
||||||
|
let pathBar = document.getElementById("pathBar");
|
||||||
|
let must_match = true;
|
||||||
|
let scroll_id = null;
|
||||||
|
let docCount = 0;
|
||||||
|
let searchQueued = false;
|
||||||
|
let coolingDown = false;
|
||||||
|
let selectedDirs = [];
|
||||||
|
|
||||||
|
function toggleSearchBar() {
|
||||||
|
must_match = !must_match;
|
||||||
|
searchQueued = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
let tree = new InspireTree({
|
||||||
|
selection: {
|
||||||
|
mode: 'checkbox'
|
||||||
|
},
|
||||||
|
data: mimeMap
|
||||||
|
});
|
||||||
|
new InspireTreeDOM(tree, {
|
||||||
|
target: '.tree'
|
||||||
|
});
|
||||||
|
|
||||||
|
//Select all
|
||||||
|
tree.select();
|
||||||
|
tree.node("any").deselect();
|
||||||
|
|
||||||
|
tree.on("node.click", function(event, node, handler) {
|
||||||
|
event.preventTreeDefault();
|
||||||
|
|
||||||
|
if (node.id === "any") {
|
||||||
|
|
||||||
|
if (!node.itree.state.checked) {
|
||||||
|
tree.deselect();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
tree.node("any").deselect();
|
||||||
|
}
|
||||||
|
|
||||||
|
handler();
|
||||||
|
searchQueued = true;
|
||||||
|
});
|
||||||
|
|
||||||
|
new autoComplete({
|
||||||
|
selector: '#pathBar',
|
||||||
|
minChars: 1,
|
||||||
|
delay: 75,
|
||||||
|
renderItem: function (item){
|
||||||
|
return '<div class="autocomplete-suggestion" data-val="' + item + '">' + item + '</div>';
|
||||||
|
},
|
||||||
|
source: async function(term, suggest) {
|
||||||
|
term = term.toLowerCase();
|
||||||
|
|
||||||
|
const choices = await getPathChoices();
|
||||||
|
|
||||||
|
let matches = [];
|
||||||
|
for (let i=0; i<choices.length; i++) {
|
||||||
|
if (~choices[i].toLowerCase().indexOf(term)) {
|
||||||
|
matches.push(choices[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
suggest(matches);
|
||||||
|
},
|
||||||
|
onSelect: function() {
|
||||||
|
searchQueued = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
function makeStatsCard(searchResult) {
|
||||||
|
|
||||||
|
let statsCard = document.createElement("div");
|
||||||
|
statsCard.setAttribute("class", "card");
|
||||||
|
let statsCardBody = document.createElement("div");
|
||||||
|
statsCardBody.setAttribute("class", "card-body");
|
||||||
|
|
||||||
|
let stat = document.createElement("p");
|
||||||
|
stat.appendChild(document.createTextNode(searchResult["hits"]["total"] + " results in " + searchResult["took"] + "ms"));
|
||||||
|
|
||||||
|
let sizeStat = document.createElement("span");
|
||||||
|
sizeStat.appendChild(document.createTextNode(humanFileSize(searchResult["aggregations"]["total_size"]["value"])));
|
||||||
|
|
||||||
|
statsCardBody.appendChild(stat);
|
||||||
|
statsCardBody.appendChild(sizeStat);
|
||||||
|
statsCard.appendChild(statsCardBody);
|
||||||
|
|
||||||
|
return statsCard;
|
||||||
|
}
|
||||||
|
|
||||||
|
function makeResultContainer() {
|
||||||
|
let resultContainer = document.createElement("div");
|
||||||
|
resultContainer.setAttribute("class", "card-columns");
|
||||||
|
|
||||||
|
return resultContainer;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* https://stackoverflow.com/questions/10420352
|
||||||
|
*/
|
||||||
|
function humanFileSize(bytes) {
|
||||||
|
|
||||||
|
if(bytes === 0) {
|
||||||
|
return "? B"
|
||||||
|
}
|
||||||
|
|
||||||
|
let thresh = 1000;
|
||||||
|
if(Math.abs(bytes) < thresh) {
|
||||||
|
return bytes + ' B';
|
||||||
|
}
|
||||||
|
let units = ['kB','MB','GB','TB','PB','EB','ZB','YB'];
|
||||||
|
let u = -1;
|
||||||
|
do {
|
||||||
|
bytes /= thresh;
|
||||||
|
++u;
|
||||||
|
} while(Math.abs(bytes) >= thresh && u < units.length - 1);
|
||||||
|
|
||||||
|
return bytes.toFixed(1) + ' ' + units[u];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function initPopover() {
|
||||||
|
$('[data-toggle="popover"]').popover({
|
||||||
|
trigger: "focus",
|
||||||
|
delay: { "show": 0, "hide": 100 },
|
||||||
|
placement: "bottom",
|
||||||
|
html: true
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enable gif loading on hover
|
||||||
|
* @param thumbnail
|
||||||
|
* @param documentId
|
||||||
|
*/
|
||||||
|
function gifOver(thumbnail, documentId) {
|
||||||
|
let callee = arguments.callee;
|
||||||
|
|
||||||
|
thumbnail.addEventListener("mouseover", function () {
|
||||||
|
|
||||||
|
thumbnail.mouseStayedOver = true;
|
||||||
|
|
||||||
|
window.setTimeout(function() {
|
||||||
|
if (thumbnail.mouseStayedOver) {
|
||||||
|
thumbnail.removeEventListener('mouseover', callee, false);
|
||||||
|
|
||||||
|
//Load gif
|
||||||
|
thumbnail.setAttribute("src", "/file/" + documentId);
|
||||||
|
}
|
||||||
|
}, 750); //todo grab hover time from config
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
thumbnail.addEventListener("mouseout", function() {
|
||||||
|
//Reset timer
|
||||||
|
thumbnail.mouseStayedOver = false;
|
||||||
|
thumbnail.setAttribute("src", "/thumb/" + documentId);
|
||||||
|
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function videoOver(thumbnail, imgWrapper, thumbnailOverlay, documentId, docCard) {
|
||||||
|
|
||||||
|
docCard.addEventListener("focus", function () {
|
||||||
|
let callee = arguments.callee;
|
||||||
|
docCard.mouseStayedOver = true;
|
||||||
|
|
||||||
|
window.setTimeout(function() {
|
||||||
|
|
||||||
|
if(docCard.mouseStayedOver) {
|
||||||
|
docCard.removeEventListener('focus', callee, false);
|
||||||
|
|
||||||
|
imgWrapper.removeChild(thumbnail);
|
||||||
|
imgWrapper.removeChild(thumbnailOverlay);
|
||||||
|
|
||||||
|
let video = document.createElement("video");
|
||||||
|
let vidSource = document.createElement("source");
|
||||||
|
vidSource.setAttribute("src", "/file/" + documentId);
|
||||||
|
vidSource.setAttribute("type", "video/webm");
|
||||||
|
video.appendChild(vidSource);
|
||||||
|
video.setAttribute("class", "fit");
|
||||||
|
video.setAttribute("loop", "");
|
||||||
|
video.setAttribute("controls", "");
|
||||||
|
video.setAttribute("preload", "");
|
||||||
|
video.setAttribute("poster", "/thumb/" + documentId);
|
||||||
|
imgWrapper.appendChild(video);
|
||||||
|
|
||||||
|
video.addEventListener("dblclick", function() {
|
||||||
|
video.webkitRequestFullScreen();
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}, 750);
|
||||||
|
});
|
||||||
|
docCard.addEventListener("blur", function() {
|
||||||
|
docCard.mouseStayedOver = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function downloadPopover(element, documentId) {
|
||||||
|
element.setAttribute("data-content",
|
||||||
|
'<a class="btn btn-sm btn-primary" href="/dl/'+ documentId +'"><i class="fas fa-download"></i> Download</a>' +
|
||||||
|
'<a class="btn btn-sm btn-success" style="margin-left:3px;" href="/file/'+ documentId + '" target="_blank"><i class="fas fa-eye"></i> View</a>');
|
||||||
|
element.setAttribute("data-toggle", "popover");
|
||||||
|
element.addEventListener("mouseover", function() {
|
||||||
|
element.focus();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param hit
|
||||||
|
* @returns {Element}
|
||||||
|
*/
|
||||||
|
function createDocCard(hit) {
|
||||||
|
|
||||||
|
let docCard = document.createElement("div");
|
||||||
|
docCard.setAttribute("class", "card");
|
||||||
|
docCard.setAttribute("tabindex", "-1");
|
||||||
|
|
||||||
|
let docCardBody = document.createElement("div");
|
||||||
|
docCardBody.setAttribute("class", "card-body document");
|
||||||
|
|
||||||
|
let link = document.createElement("a");
|
||||||
|
link.setAttribute("href", "/document/" + hit["_id"]);
|
||||||
|
link.setAttribute("target", "_blank");
|
||||||
|
|
||||||
|
//Title
|
||||||
|
let title = document.createElement("p");
|
||||||
|
title.setAttribute("class", "file-title");
|
||||||
|
let extension = hit["_source"].hasOwnProperty("extension") && hit["_source"]["extension"] !== "" ? "." + hit["_source"]["extension"] : "";
|
||||||
|
|
||||||
|
if (hit.hasOwnProperty("highlight") && hit["highlight"].hasOwnProperty("name")) {
|
||||||
|
title.insertAdjacentHTML('afterbegin', hit["highlight"]["name"] + extension);
|
||||||
|
} else {
|
||||||
|
title.appendChild(document.createTextNode(hit["_source"]["name"] + extension));
|
||||||
|
}
|
||||||
|
|
||||||
|
title.setAttribute("title", hit["_source"]["path"] + hit["_source"]["name"] + extension);
|
||||||
|
docCard.appendChild(title);
|
||||||
|
|
||||||
|
let tagContainer = document.createElement("div");
|
||||||
|
tagContainer.setAttribute("class", "card-text");
|
||||||
|
|
||||||
|
if (hit["_source"].hasOwnProperty("mime") && hit["_source"]["mime"] !== null) {
|
||||||
|
|
||||||
|
let tags = [];
|
||||||
|
let thumbnail = null;
|
||||||
|
let thumbnailOverlay = null;
|
||||||
|
let imgWrapper = document.createElement("div");
|
||||||
|
imgWrapper.setAttribute("style", "position: relative");
|
||||||
|
|
||||||
|
let mimeCategory = hit["_source"]["mime"].split("/")[0];
|
||||||
|
|
||||||
|
//Thumbnail
|
||||||
|
switch (mimeCategory) {
|
||||||
|
|
||||||
|
case "video":
|
||||||
|
case "image":
|
||||||
|
thumbnail = document.createElement("img");
|
||||||
|
thumbnail.setAttribute("class", "card-img-top");
|
||||||
|
thumbnail.setAttribute("src", "/thumb/" + hit["_id"]);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
//Thumbnail overlay
|
||||||
|
switch (mimeCategory) {
|
||||||
|
|
||||||
|
case "image":
|
||||||
|
thumbnailOverlay = document.createElement("div");
|
||||||
|
thumbnailOverlay.setAttribute("class", "card-img-overlay");
|
||||||
|
|
||||||
|
//Resolution
|
||||||
|
let resolutionBadge = document.createElement("span");
|
||||||
|
resolutionBadge.setAttribute("class", "badge badge-resolution");
|
||||||
|
if (hit["_source"].hasOwnProperty("width")) {
|
||||||
|
resolutionBadge.appendChild(document.createTextNode(hit["_source"]["width"] + "x" + hit["_source"]["height"]));
|
||||||
|
}
|
||||||
|
thumbnailOverlay.appendChild(resolutionBadge);
|
||||||
|
|
||||||
|
var format = hit["_source"]["format"];
|
||||||
|
|
||||||
|
//Hover
|
||||||
|
if(format === "GIF") {
|
||||||
|
gifOver(thumbnail, hit["_id"]);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "video":
|
||||||
|
thumbnailOverlay = document.createElement("div");
|
||||||
|
thumbnailOverlay.setAttribute("class", "card-img-overlay");
|
||||||
|
|
||||||
|
//Duration
|
||||||
|
let durationBadge = document.createElement("span");
|
||||||
|
durationBadge.setAttribute("class", "badge badge-resolution");
|
||||||
|
durationBadge.appendChild(document.createTextNode(parseFloat(hit["_source"]["duration"]).toFixed(2) + "s"));
|
||||||
|
thumbnailOverlay.appendChild(durationBadge);
|
||||||
|
|
||||||
|
//Hover
|
||||||
|
videoOver(thumbnail, imgWrapper, thumbnailOverlay, hit["_id"], docCard)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
//Tags
|
||||||
|
switch (mimeCategory) {
|
||||||
|
|
||||||
|
case "video":
|
||||||
|
if (hit["_source"].hasOwnProperty("format_long_name")) {
|
||||||
|
let formatTag = document.createElement("span");
|
||||||
|
formatTag.setAttribute("class", "badge badge-pill badge-video");
|
||||||
|
formatTag.appendChild(document.createTextNode(hit["_source"]["format_long_name"].replace(" ", "")));
|
||||||
|
tags.push(formatTag);
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
case "image": {
|
||||||
|
let formatTag = document.createElement("span");
|
||||||
|
formatTag.setAttribute("class", "badge badge-pill badge-image");
|
||||||
|
formatTag.appendChild(document.createTextNode(format));
|
||||||
|
tags.push(formatTag);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case "audio": {
|
||||||
|
if (hit["_source"].hasOwnProperty("format_long_name")) {
|
||||||
|
let formatTag = document.createElement("span");
|
||||||
|
formatTag.setAttribute("class", "badge badge-pill badge-audio");
|
||||||
|
formatTag.appendChild(document.createTextNode(hit["_source"]["format_long_name"]));
|
||||||
|
tags.push(formatTag);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
case "text": {
|
||||||
|
let formatTag = document.createElement("span");
|
||||||
|
formatTag.setAttribute("class", "badge badge-pill badge-text");
|
||||||
|
formatTag.appendChild(document.createTextNode(hit["_source"]["encoding"]));
|
||||||
|
tags.push(formatTag);
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
//Content
|
||||||
|
if (hit.hasOwnProperty("highlight") && hit["highlight"].hasOwnProperty("content")) {
|
||||||
|
|
||||||
|
let contentDiv = document.createElement("div");
|
||||||
|
contentDiv.setAttribute("class", "content-div bg-light");
|
||||||
|
contentDiv.insertAdjacentHTML('afterbegin', hit["highlight"]["content"][0]);
|
||||||
|
docCard.appendChild(contentDiv);
|
||||||
|
}
|
||||||
|
|
||||||
|
//Audio
|
||||||
|
if (mimeCategory === "audio" && hit["_source"].hasOwnProperty("format_long_name")) {
|
||||||
|
|
||||||
|
let audio = document.createElement("audio");
|
||||||
|
audio.setAttribute("preload", "none");
|
||||||
|
audio.setAttribute("class", "audio-fit fit");
|
||||||
|
audio.setAttribute("controls", "");
|
||||||
|
audio.setAttribute("type", hit["_source"]["mime"]);
|
||||||
|
audio.setAttribute("src", "file/" + hit["_id"]);
|
||||||
|
|
||||||
|
docCard.appendChild(audio)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (thumbnail !== null) {
|
||||||
|
imgWrapper.appendChild(thumbnail);
|
||||||
|
docCard.appendChild(imgWrapper);
|
||||||
|
}
|
||||||
|
if (thumbnailOverlay !== null) {
|
||||||
|
imgWrapper.appendChild(thumbnailOverlay);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let i = 0; i < tags.length; i++) {
|
||||||
|
tagContainer.appendChild(tags[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
//Size tag
|
||||||
|
let sizeTag = document.createElement("small");
|
||||||
|
sizeTag.appendChild(document.createTextNode(humanFileSize(hit["_source"]["size"])));
|
||||||
|
sizeTag.setAttribute("class", "text-muted");
|
||||||
|
tagContainer.appendChild(sizeTag);
|
||||||
|
|
||||||
|
|
||||||
|
//Download button
|
||||||
|
downloadPopover(docCard, hit["_id"]);
|
||||||
|
|
||||||
|
docCardBody.appendChild(link);
|
||||||
|
docCard.appendChild(docCardBody);
|
||||||
|
|
||||||
|
link.appendChild(title);
|
||||||
|
docCardBody.appendChild(tagContainer);
|
||||||
|
|
||||||
|
return docCard;
|
||||||
|
}
|
||||||
|
|
||||||
|
function makePageIndicator(searchResult) {
|
||||||
|
let pageIndicator = document.createElement("div");
|
||||||
|
pageIndicator.appendChild(document.createTextNode(docCount + " / " +searchResult["hits"]["total"]));
|
||||||
|
return pageIndicator;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function insertHits(resultContainer, hits) {
|
||||||
|
for (let i = 0 ; i < hits.length; i++) {
|
||||||
|
resultContainer.appendChild(createDocCard(hits[i]));
|
||||||
|
docCount++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
window.addEventListener("scroll", function () {
|
||||||
|
|
||||||
|
if (!coolingDown) {
|
||||||
|
|
||||||
|
let threshold = 350;
|
||||||
|
|
||||||
|
if ((window.innerHeight + window.scrollY) >= document.body.offsetHeight - threshold) {
|
||||||
|
//load next page
|
||||||
|
|
||||||
|
let xhttp = new XMLHttpRequest();
|
||||||
|
xhttp.onreadystatechange = function() {
|
||||||
|
if (this.readyState === 4 && this.status === 200) {
|
||||||
|
|
||||||
|
let searchResult = JSON.parse(this.responseText);
|
||||||
|
let searchResults = document.getElementById("searchResults");
|
||||||
|
let hits = searchResult["hits"]["hits"];
|
||||||
|
|
||||||
|
//Page indicator
|
||||||
|
let pageIndicator = makePageIndicator(searchResult);
|
||||||
|
searchResults.appendChild(pageIndicator);
|
||||||
|
|
||||||
|
//Result container
|
||||||
|
let resultContainer = makeResultContainer();
|
||||||
|
searchResults.appendChild(resultContainer);
|
||||||
|
|
||||||
|
insertHits(resultContainer, hits);
|
||||||
|
|
||||||
|
initPopover();
|
||||||
|
|
||||||
|
if (hits.length !== 0) {
|
||||||
|
coolingDown = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
xhttp.open("GET", "/scroll?scroll_id=" + scroll_id, true);
|
||||||
|
xhttp.send();
|
||||||
|
coolingDown = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
function getSelectedMimeTypes() {
|
||||||
|
let mimeTypes = [];
|
||||||
|
|
||||||
|
let selected = tree.selected();
|
||||||
|
|
||||||
|
for (let i = 0; i < selected.length; i++) {
|
||||||
|
|
||||||
|
if(selected[i].id === "any") {
|
||||||
|
return "any"
|
||||||
|
}
|
||||||
|
|
||||||
|
//Only get children
|
||||||
|
if (selected[i].text.indexOf("(") !== -1) {
|
||||||
|
mimeTypes.push(selected[i].id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return mimeTypes
|
||||||
|
}
|
||||||
|
|
||||||
|
function search() {
|
||||||
|
|
||||||
|
if (searchQueued === true) {
|
||||||
|
searchQueued = false;
|
||||||
|
|
||||||
|
//Clear old search results
|
||||||
|
let searchResults = document.getElementById("searchResults");
|
||||||
|
while (searchResults.firstChild) {
|
||||||
|
searchResults.removeChild(searchResults.firstChild);
|
||||||
|
}
|
||||||
|
|
||||||
|
let query = searchBar.value;
|
||||||
|
|
||||||
|
let xhttp = new XMLHttpRequest();
|
||||||
|
xhttp.onreadystatechange = function() {
|
||||||
|
if (this.readyState === 4 && this.status === 200) {
|
||||||
|
|
||||||
|
let searchResult = JSON.parse(this.responseText);
|
||||||
|
scroll_id = searchResult["_scroll_id"];
|
||||||
|
|
||||||
|
//Search stats
|
||||||
|
searchResults.appendChild(makeStatsCard(searchResult));
|
||||||
|
|
||||||
|
//Autocomplete
|
||||||
|
if (searchResult.hasOwnProperty("suggest") && searchResult["suggest"].hasOwnProperty("path")) {
|
||||||
|
pathAutoComplete = [];
|
||||||
|
for (let i = 0; i < searchResult["suggest"]["path"][0]["options"].length; i++) {
|
||||||
|
pathAutoComplete.push(searchResult["suggest"]["path"][0]["options"][i].text)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//Setup page
|
||||||
|
let resultContainer = makeResultContainer();
|
||||||
|
searchResults.appendChild(resultContainer);
|
||||||
|
|
||||||
|
//Insert search results (hits)
|
||||||
|
docCount = 0;
|
||||||
|
insertHits(resultContainer, searchResult["hits"]["hits"]);
|
||||||
|
|
||||||
|
//Initialise download/view button popover
|
||||||
|
initPopover();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
xhttp.open("POST", "/search", true);
|
||||||
|
|
||||||
|
let postBody = {};
|
||||||
|
postBody.q = query;
|
||||||
|
postBody.size_min = size_min;
|
||||||
|
postBody.size_max = size_max;
|
||||||
|
postBody.mime_types = getSelectedMimeTypes();
|
||||||
|
postBody.must_match = must_match;
|
||||||
|
postBody.directories = selectedDirs;
|
||||||
|
postBody.path = pathBar.value.replace(/\/$/, "").toLowerCase(); //remove trailing slashes
|
||||||
|
xhttp.setRequestHeader('content-type', 'application/json');
|
||||||
|
xhttp.send(JSON.stringify(postBody));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let pathAutoComplete = [];
|
||||||
|
let size_min = 0;
|
||||||
|
let size_max = 10000000000000;
|
||||||
|
|
||||||
|
searchBar.addEventListener("keyup", function () {
|
||||||
|
searchQueued = true;
|
||||||
|
});
|
||||||
|
|
||||||
|
//Size slider
|
||||||
|
let sizeSlider = $("#sizeSlider").ionRangeSlider({
|
||||||
|
type: "double",
|
||||||
|
grid: false,
|
||||||
|
force_edges: true,
|
||||||
|
min: 0,
|
||||||
|
max: 3684.03149864,
|
||||||
|
from: 0,
|
||||||
|
to: 3684.03149864,
|
||||||
|
min_interval: 5,
|
||||||
|
drag_interval: true,
|
||||||
|
prettify: function (num) {
|
||||||
|
|
||||||
|
if(num === 0) {
|
||||||
|
return "0 B"
|
||||||
|
} else if (num >= 3684) {
|
||||||
|
return humanFileSize(num * num * num) + "+";
|
||||||
|
}
|
||||||
|
|
||||||
|
return humanFileSize(num * num * num)
|
||||||
|
},
|
||||||
|
onChange: function(e) {
|
||||||
|
size_min = (e.from * e.from * e.from);
|
||||||
|
size_max = (e.to * e.to * e.to);
|
||||||
|
|
||||||
|
if (e.to >= 3684) {
|
||||||
|
size_max = 10000000000000;
|
||||||
|
}
|
||||||
|
|
||||||
|
searchQueued = true;
|
||||||
|
}
|
||||||
|
})[0];
|
||||||
|
|
||||||
|
//Directories select
|
||||||
|
function updateDirectories() {
|
||||||
|
let selected = $('#directories').find('option:selected');
|
||||||
|
selectedDirs = [];
|
||||||
|
$(selected).each(function(){
|
||||||
|
selectedDirs.push(parseInt($(this).val()));
|
||||||
|
});
|
||||||
|
|
||||||
|
searchQueued = true;
|
||||||
|
}
|
||||||
|
document.getElementById("directories").addEventListener("change", updateDirectories);
|
||||||
|
updateDirectories();
|
||||||
|
searchQueued = false;
|
||||||
|
|
||||||
|
//Suggest
|
||||||
|
function getPathChoices() {
|
||||||
|
return new Promise(getPaths => {
|
||||||
|
|
||||||
|
let xhttp = new XMLHttpRequest();
|
||||||
|
xhttp.onreadystatechange = function() {
|
||||||
|
if (this.readyState === 4 && this.status === 200) {
|
||||||
|
getPaths(JSON.parse(xhttp.responseText))
|
||||||
|
}
|
||||||
|
};
|
||||||
|
xhttp.open("GET", "/suggest?prefix=" + pathBar.value, true);
|
||||||
|
xhttp.send();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
window.setInterval(search, 75);
|
@ -8,7 +8,7 @@ import config
|
|||||||
class CheckSumCalculator:
|
class CheckSumCalculator:
|
||||||
|
|
||||||
def checksum(self, string: str):
|
def checksum(self, string: str):
|
||||||
return flask_bcrypt.generate_password_hash(string, 14) # todo load from config
|
return flask_bcrypt.generate_password_hash(string, config.bcrypt_rounds)
|
||||||
|
|
||||||
|
|
||||||
class DuplicateDirectoryException(Exception):
|
class DuplicateDirectoryException(Exception):
|
||||||
|
@ -11,20 +11,18 @@
|
|||||||
<script src="/static/js/Chart.min.js" type="text/javascript"></script>
|
<script src="/static/js/Chart.min.js" type="text/javascript"></script>
|
||||||
<script src="/static/js/auto-complete.min.js" type="text/javascript"></script>
|
<script src="/static/js/auto-complete.min.js" type="text/javascript"></script>
|
||||||
<script src="/static/js/ion.rangeSlider.min.js" type="text/javascript"></script>
|
<script src="/static/js/ion.rangeSlider.min.js" type="text/javascript"></script>
|
||||||
|
|
||||||
<script src="/static/js/lodash.min.js" type="text/javascript"></script>
|
<script src="/static/js/lodash.min.js" type="text/javascript"></script>
|
||||||
<script src="/static/js/inspire-tree.min.js" type="text/javascript"></script>
|
<script src="/static/js/inspire-tree.min.js" type="text/javascript"></script>
|
||||||
<script src="/static/js/inspire-tree-dom.min.js" type="text/javascript"></script>
|
<script src="/static/js/inspire-tree-dom.min.js" type="text/javascript"></script>
|
||||||
|
|
||||||
{# <link href="/static/css/normalize.css" rel="stylesheet" type="text/css">#}
|
|
||||||
<link href="/static/css/fontawesome-all.min.css" rel="stylesheet" type="text/css">
|
<link href="/static/css/fontawesome-all.min.css" rel="stylesheet" type="text/css">
|
||||||
<link href="/static/css/bootstrap.min.css" rel="stylesheet" type="text/css">
|
<link href="/static/css/bootstrap.min.css" rel="stylesheet" type="text/css">
|
||||||
<link href="/static/css/auto-complete.css" rel="stylesheet" type="text/css">
|
<link href="/static/css/auto-complete.css" rel="stylesheet" type="text/css">
|
||||||
<link href="/static/css/ion.rangeSlider.css" rel="stylesheet" type="text/css">
|
<link href="/static/css/ion.rangeSlider.css" rel="stylesheet" type="text/css">
|
||||||
<link href="/static/css/ion.rangeSlider.skinFlat.css" rel="stylesheet" type="text/css">
|
<link href="/static/css/ion.rangeSlider.skinFlat.css" rel="stylesheet" type="text/css">
|
||||||
|
|
||||||
<link href="/static/css/inspire-tree-light.css" rel="stylesheet" type="text/css">
|
<link href="/static/css/inspire-tree-light.css" rel="stylesheet" type="text/css">
|
||||||
|
|
||||||
|
{% block imports %}{% endblock %}
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.info-table {
|
.info-table {
|
||||||
|
@ -4,161 +4,13 @@
|
|||||||
|
|
||||||
{% block title %}Search{% endblock title %}
|
{% block title %}Search{% endblock title %}
|
||||||
|
|
||||||
|
{% block imports %}
|
||||||
|
<link href="/static/css/search.css" rel="stylesheet" type="text/css">
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
{% block body %}
|
{% block body %}
|
||||||
|
|
||||||
<style>
|
|
||||||
body {overflow-y:scroll;}
|
|
||||||
.document {
|
|
||||||
padding: 0.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.document p {
|
|
||||||
margin-bottom: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.document:hover p {
|
|
||||||
text-decoration: underline;
|
|
||||||
}
|
|
||||||
|
|
||||||
.badge-video {
|
|
||||||
color: #FFFFFF;
|
|
||||||
background-color: #F27761;
|
|
||||||
}
|
|
||||||
|
|
||||||
.badge-image {
|
|
||||||
color: #FFFFFF;
|
|
||||||
background-color: #AA99C9;
|
|
||||||
}
|
|
||||||
|
|
||||||
.badge-audio {
|
|
||||||
color: #FFFFFF;
|
|
||||||
background-color: #00ADEF;
|
|
||||||
}
|
|
||||||
|
|
||||||
.badge-resolution {
|
|
||||||
color: #212529;
|
|
||||||
background-color: #FFC107;
|
|
||||||
}
|
|
||||||
|
|
||||||
.badge-text {
|
|
||||||
color: #FFFFFF;
|
|
||||||
background-color: #FAAB3C;
|
|
||||||
}
|
|
||||||
|
|
||||||
.card-img-top {
|
|
||||||
display: block;
|
|
||||||
min-width: 64px;
|
|
||||||
max-width: 100%;
|
|
||||||
max-height: 256px;
|
|
||||||
width: unset;
|
|
||||||
margin: 0 auto 0;
|
|
||||||
padding: 3px 3px 0 3px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.card-img-overlay {
|
|
||||||
pointer-events: none;
|
|
||||||
padding: 0.75rem;
|
|
||||||
|
|
||||||
bottom: 0;
|
|
||||||
top: unset;
|
|
||||||
left: unset;
|
|
||||||
right: unset;
|
|
||||||
}
|
|
||||||
|
|
||||||
.file-title {
|
|
||||||
font-size: 10pt;
|
|
||||||
white-space: nowrap;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
.badge {
|
|
||||||
margin-right: 3px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.fit {
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
padding: 3px;
|
|
||||||
min-width: 64px;
|
|
||||||
max-width: 100%;
|
|
||||||
max-height: 256px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.audio-fit {
|
|
||||||
height: 39px;
|
|
||||||
vertical-align: bottom;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (min-width: 1200px) {
|
|
||||||
.card-columns {
|
|
||||||
column-count: 4;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (min-width: 1500px) {
|
|
||||||
.container {
|
|
||||||
max-width: 1440px;
|
|
||||||
}
|
|
||||||
.card-columns {
|
|
||||||
column-count: 5;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.hl {
|
|
||||||
color: red;
|
|
||||||
}
|
|
||||||
|
|
||||||
.content-div {
|
|
||||||
font-family: SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;
|
|
||||||
font-size: 13px;
|
|
||||||
padding: 1em;
|
|
||||||
background-color: #f5f5f5;
|
|
||||||
border: 1px solid #ccc;
|
|
||||||
border-radius: 4px;
|
|
||||||
margin: 3px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.irs-single, .irs-from, .irs-to {
|
|
||||||
font-size: 13px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.irs-slider {
|
|
||||||
cursor: col-resize;
|
|
||||||
}
|
|
||||||
|
|
||||||
.custom-select {
|
|
||||||
overflow: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.irs {
|
|
||||||
margin-top: 1em;
|
|
||||||
margin-bottom: 1em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.inspire-tree .selected > .wholerow, .inspire-tree .selected > .title-wrap:hover + .wholerow
|
|
||||||
{
|
|
||||||
background: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.inspire-tree {
|
|
||||||
font-weight: 400;
|
|
||||||
font-size: 14px;
|
|
||||||
font-family: Helvetica, Nueue, Verdana, sans-serif;
|
|
||||||
max-height: 450px;
|
|
||||||
overflow: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-xs {
|
|
||||||
padding: .1rem .3rem;
|
|
||||||
font-size: .875rem;
|
|
||||||
border-radius: .2rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
</style>
|
|
||||||
|
|
||||||
<div class="container">
|
<div class="container">
|
||||||
|
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
@ -168,14 +20,14 @@
|
|||||||
<div class="input-group-prepend">
|
<div class="input-group-prepend">
|
||||||
<div class="input-group-text">
|
<div class="input-group-text">
|
||||||
<span onclick="document.getElementById('barToggle').click()">Must match </span>
|
<span onclick="document.getElementById('barToggle').click()">Must match </span>
|
||||||
<input type="checkbox" id="barToggle" onclick="toggleSearchBar()" checked>
|
<input title="Toggle between 'Should' and 'Must' match mode" type="checkbox" id="barToggle" onclick="toggleSearchBar()" checked>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<input id="searchBar" type="search" class="form-control" placeholder="Search">
|
<input id="searchBar" type="search" class="form-control" placeholder="Search">
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<input id="sizeSlider" name="size">
|
<input title="File size" id="sizeSlider" name="size">
|
||||||
|
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col">
|
<div class="col">
|
||||||
@ -196,642 +48,15 @@
|
|||||||
<div class="tree"></div>
|
<div class="tree"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script type="text/javascript">
|
|
||||||
let tree = new InspireTree({
|
|
||||||
selection: {
|
|
||||||
mode: 'checkbox'
|
|
||||||
},
|
|
||||||
data: {{ mime_map | tojson }}
|
|
||||||
});
|
|
||||||
new InspireTreeDOM(tree, {
|
|
||||||
target: '.tree'
|
|
||||||
});
|
|
||||||
|
|
||||||
//Select all
|
|
||||||
tree.select();
|
|
||||||
tree.node("any").deselect();
|
|
||||||
|
|
||||||
tree.on("node.click", function(event, node, handler) {
|
|
||||||
event.preventTreeDefault();
|
|
||||||
|
|
||||||
if (node.id === "any") {
|
|
||||||
|
|
||||||
if (!node.itree.state.checked) {
|
|
||||||
tree.deselect();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
tree.node("any").deselect();
|
|
||||||
}
|
|
||||||
|
|
||||||
handler();
|
|
||||||
searchQueued = true;
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script>
|
<div id="searchResults"></div>
|
||||||
|
|
||||||
new autoComplete({
|
|
||||||
selector: '#pathBar',
|
|
||||||
minChars: 1,
|
|
||||||
delay: 75,
|
|
||||||
renderItem: function (item, search){
|
|
||||||
return '<div class="autocomplete-suggestion" data-val="' + item + '">' + item + '</div>';
|
|
||||||
},
|
|
||||||
source: async function(term, suggest) {
|
|
||||||
term = term.toLowerCase();
|
|
||||||
|
|
||||||
const choices = await getPathChoices();
|
|
||||||
|
|
||||||
let matches = [];
|
|
||||||
for (let i=0; i<choices.length; i++) {
|
|
||||||
if (~choices[i].toLowerCase().indexOf(term)) {
|
|
||||||
matches.push(choices[i]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
suggest(matches);
|
|
||||||
},
|
|
||||||
onSelect: function(event, term, item) {
|
|
||||||
searchQueued = true;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<div id="searchResults">
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
var mimeMap = {{ mime_map | tojson }};
|
||||||
let searchBar = document.getElementById("searchBar");
|
|
||||||
let pathBar = document.getElementById("pathBar");
|
|
||||||
let must_match = true;
|
|
||||||
let scroll_id = null;
|
|
||||||
let docCount = 0;
|
|
||||||
let treeSelected = false;
|
|
||||||
let searchQueued = false;
|
|
||||||
let coolingDown = false;
|
|
||||||
let selectedDirs = [];
|
|
||||||
|
|
||||||
function toggleSearchBar() {
|
|
||||||
must_match = !must_match;
|
|
||||||
searchQueued = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
function toggleTree() {
|
|
||||||
if (treeSelected) {
|
|
||||||
tree.select();
|
|
||||||
} else {
|
|
||||||
tree.deselect();
|
|
||||||
}
|
|
||||||
|
|
||||||
treeSelected = !treeSelected;
|
|
||||||
searchQueued = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
function makeStatsCard(searchResult) {
|
|
||||||
|
|
||||||
let statsCard = document.createElement("div");
|
|
||||||
statsCard.setAttribute("class", "card");
|
|
||||||
let statsCardBody = document.createElement("div");
|
|
||||||
statsCardBody.setAttribute("class", "card-body");
|
|
||||||
|
|
||||||
let stat = document.createElement("p");
|
|
||||||
stat.appendChild(document.createTextNode(searchResult["hits"]["total"] + " results in " + searchResult["took"] + "ms"));
|
|
||||||
|
|
||||||
let sizeStat = document.createElement("span");
|
|
||||||
sizeStat.appendChild(document.createTextNode(humanFileSize(searchResult["aggregations"]["total_size"]["value"])));
|
|
||||||
|
|
||||||
statsCardBody.appendChild(stat);
|
|
||||||
statsCardBody.appendChild(sizeStat);
|
|
||||||
statsCard.appendChild(statsCardBody);
|
|
||||||
|
|
||||||
return statsCard;
|
|
||||||
}
|
|
||||||
|
|
||||||
function makeResultContainer() {
|
|
||||||
let resultContainer = document.createElement("div");
|
|
||||||
resultContainer.setAttribute("class", "card-columns");
|
|
||||||
|
|
||||||
return resultContainer;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* https://stackoverflow.com/questions/10420352
|
|
||||||
*/
|
|
||||||
function humanFileSize(bytes) {
|
|
||||||
|
|
||||||
if(bytes === 0) {
|
|
||||||
return "? B"
|
|
||||||
}
|
|
||||||
|
|
||||||
let thresh = 1000;
|
|
||||||
if(Math.abs(bytes) < thresh) {
|
|
||||||
return bytes + ' B';
|
|
||||||
}
|
|
||||||
let units = ['kB','MB','GB','TB','PB','EB','ZB','YB'];
|
|
||||||
let u = -1;
|
|
||||||
do {
|
|
||||||
bytes /= thresh;
|
|
||||||
++u;
|
|
||||||
} while(Math.abs(bytes) >= thresh && u < units.length - 1);
|
|
||||||
|
|
||||||
return bytes.toFixed(1) + ' ' + units[u];
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
function initPopover() {
|
|
||||||
$('[data-toggle="popover"]').popover({
|
|
||||||
trigger: "focus",
|
|
||||||
delay: { "show": 0, "hide": 100 },
|
|
||||||
placement: "bottom",
|
|
||||||
html: true
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Enable gif loading on hover
|
|
||||||
* @param thumbnail
|
|
||||||
* @param documentId
|
|
||||||
*/
|
|
||||||
function gifOver(thumbnail, documentId) {
|
|
||||||
let callee = arguments.callee;
|
|
||||||
|
|
||||||
thumbnail.addEventListener("mouseover", function () {
|
|
||||||
|
|
||||||
thumbnail.mouseStayedOver = true;
|
|
||||||
|
|
||||||
window.setTimeout(function() {
|
|
||||||
if (thumbnail.mouseStayedOver) {
|
|
||||||
thumbnail.removeEventListener('mouseover', callee, false);
|
|
||||||
|
|
||||||
//Load gif
|
|
||||||
thumbnail.setAttribute("src", "/file/" + documentId);
|
|
||||||
}
|
|
||||||
}, 750); //todo grab hover time from config
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
thumbnail.addEventListener("mouseout", function() {
|
|
||||||
//Reset timer
|
|
||||||
thumbnail.mouseStayedOver = false;
|
|
||||||
thumbnail.setAttribute("src", "/thumb/" + documentId);
|
|
||||||
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
function videoOver(thumbnail, imgWrapper, thumbnailOverlay, documentId, docCard) {
|
|
||||||
|
|
||||||
|
|
||||||
docCard.addEventListener("focus", function () {
|
|
||||||
let callee = arguments.callee;
|
|
||||||
docCard.mouseStayedOver = true;
|
|
||||||
|
|
||||||
window.setTimeout(function() {
|
|
||||||
|
|
||||||
if(docCard.mouseStayedOver) {
|
|
||||||
docCard.removeEventListener('focus', callee, false);
|
|
||||||
|
|
||||||
imgWrapper.removeChild(thumbnail);
|
|
||||||
imgWrapper.removeChild(thumbnailOverlay);
|
|
||||||
|
|
||||||
let video = document.createElement("video");
|
|
||||||
let vidSource = document.createElement("source");
|
|
||||||
vidSource.setAttribute("src", "/file/" + documentId);
|
|
||||||
vidSource.setAttribute("type", "video/webm");
|
|
||||||
video.appendChild(vidSource);
|
|
||||||
video.setAttribute("class", "fit");
|
|
||||||
video.setAttribute("loop", "");
|
|
||||||
video.setAttribute("controls", "");
|
|
||||||
video.setAttribute("preload", "");
|
|
||||||
video.setAttribute("poster", "/thumb/" + documentId);
|
|
||||||
imgWrapper.appendChild(video);
|
|
||||||
|
|
||||||
docCard.addEventListener("blur", function() {
|
|
||||||
video.pause();
|
|
||||||
});
|
|
||||||
|
|
||||||
video.addEventListener("dblclick", function() {
|
|
||||||
video.webkitRequestFullScreen();
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}, 750);
|
|
||||||
});
|
|
||||||
docCard.addEventListener("blur", function() {
|
|
||||||
docCard.mouseStayedOver = false;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function downloadPopover(element, documentId) {
|
|
||||||
element.setAttribute("data-content",
|
|
||||||
'<a class="btn btn-sm btn-primary" href="/dl/'+ documentId +'"><i class="fas fa-download"></i> Download</a>' +
|
|
||||||
'<a class="btn btn-sm btn-success" style="margin-left:3px;" href="/file/'+ documentId + '" target="_blank"><i class="fas fa-eye"></i> View</a>');
|
|
||||||
element.setAttribute("data-toggle", "popover");
|
|
||||||
element.addEventListener("mouseover", function() {
|
|
||||||
element.focus();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
let counter = 0;
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @param hit
|
|
||||||
* @returns {Element}
|
|
||||||
*/
|
|
||||||
function createDocCard(hit) {
|
|
||||||
|
|
||||||
let docCard = document.createElement("div");
|
|
||||||
docCard.setAttribute("class", "card");
|
|
||||||
docCard.setAttribute("tabindex", "-1");
|
|
||||||
|
|
||||||
let docCardBody = document.createElement("div");
|
|
||||||
docCardBody.setAttribute("class", "card-body document");
|
|
||||||
|
|
||||||
let link = document.createElement("a");
|
|
||||||
link.setAttribute("href", "/document/" + hit["_id"]);
|
|
||||||
link.setAttribute("target", "_blank");
|
|
||||||
|
|
||||||
//Title
|
|
||||||
let title = document.createElement("p");
|
|
||||||
title.setAttribute("class", "file-title");
|
|
||||||
let extension = hit["_source"].hasOwnProperty("extension") && hit["_source"]["extension"] !== "" ? "." + hit["_source"]["extension"] : "";
|
|
||||||
|
|
||||||
if (hit.hasOwnProperty("highlight") && hit["highlight"].hasOwnProperty("name")) {
|
|
||||||
title.insertAdjacentHTML('afterbegin', hit["highlight"]["name"] + extension);
|
|
||||||
} else {
|
|
||||||
title.appendChild(document.createTextNode(hit["_source"]["name"] + extension));
|
|
||||||
}
|
|
||||||
|
|
||||||
title.setAttribute("title", hit["_source"]["path"] + hit["_source"]["name"] + extension);
|
|
||||||
docCard.appendChild(title);
|
|
||||||
|
|
||||||
let tagContainer = document.createElement("div");
|
|
||||||
tagContainer.setAttribute("class", "card-text");
|
|
||||||
|
|
||||||
if (hit["_source"].hasOwnProperty("mime") && hit["_source"]["mime"] !== null) {
|
|
||||||
|
|
||||||
let tags = [];
|
|
||||||
let thumbnail = null;
|
|
||||||
let thumbnailOverlay = null;
|
|
||||||
let imgWrapper = document.createElement("div");
|
|
||||||
imgWrapper.setAttribute("style", "position: relative");
|
|
||||||
|
|
||||||
let mimeCategory = hit["_source"]["mime"].split("/")[0];
|
|
||||||
|
|
||||||
//Thumbnail
|
|
||||||
switch (mimeCategory) {
|
|
||||||
|
|
||||||
case "video":
|
|
||||||
case "image":
|
|
||||||
thumbnail = document.createElement("img");
|
|
||||||
thumbnail.setAttribute("class", "card-img-top");
|
|
||||||
thumbnail.setAttribute("src", "/thumb/" + hit["_id"]);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
//Thumbnail overlay
|
|
||||||
switch (mimeCategory) {
|
|
||||||
|
|
||||||
case "image":
|
|
||||||
thumbnailOverlay = document.createElement("div");
|
|
||||||
thumbnailOverlay.setAttribute("class", "card-img-overlay");
|
|
||||||
|
|
||||||
//Resolution
|
|
||||||
let resolutionBadge = document.createElement("span");
|
|
||||||
resolutionBadge.setAttribute("class", "badge badge-resolution");
|
|
||||||
if (hit["_source"].hasOwnProperty("width")) {
|
|
||||||
resolutionBadge.appendChild(document.createTextNode(hit["_source"]["width"] + "x" + hit["_source"]["height"]));
|
|
||||||
}
|
|
||||||
thumbnailOverlay.appendChild(resolutionBadge);
|
|
||||||
|
|
||||||
var format = hit["_source"]["format"];
|
|
||||||
|
|
||||||
//Hover
|
|
||||||
if(format === "GIF") {
|
|
||||||
gifOver(thumbnail, hit["_id"]);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case "video":
|
|
||||||
thumbnailOverlay = document.createElement("div");
|
|
||||||
thumbnailOverlay.setAttribute("class", "card-img-overlay");
|
|
||||||
|
|
||||||
//Duration
|
|
||||||
let durationBadge = document.createElement("span");
|
|
||||||
durationBadge.setAttribute("class", "badge badge-resolution");
|
|
||||||
durationBadge.appendChild(document.createTextNode(parseFloat(hit["_source"]["duration"]).toFixed(2) + "s"));
|
|
||||||
thumbnailOverlay.appendChild(durationBadge);
|
|
||||||
|
|
||||||
//Hover
|
|
||||||
videoOver(thumbnail, imgWrapper, thumbnailOverlay, hit["_id"], docCard)
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
//Tags
|
|
||||||
switch (mimeCategory) {
|
|
||||||
|
|
||||||
case "video":
|
|
||||||
if (hit["_source"].hasOwnProperty("format_long_name")) {
|
|
||||||
let formatTag = document.createElement("span");
|
|
||||||
formatTag.setAttribute("class", "badge badge-pill badge-video");
|
|
||||||
formatTag.appendChild(document.createTextNode(hit["_source"]["format_long_name"].replace(" ", "")));
|
|
||||||
tags.push(formatTag);
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
|
||||||
case "image": {
|
|
||||||
let formatTag = document.createElement("span");
|
|
||||||
formatTag.setAttribute("class", "badge badge-pill badge-image");
|
|
||||||
formatTag.appendChild(document.createTextNode(format));
|
|
||||||
tags.push(formatTag);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case "audio": {
|
|
||||||
let formatTag = document.createElement("span");
|
|
||||||
formatTag.setAttribute("class", "badge badge-pill badge-audio");
|
|
||||||
formatTag.appendChild(document.createTextNode(hit["_source"]["format_name"]));
|
|
||||||
tags.push(formatTag);
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
|
||||||
case "text": {
|
|
||||||
let formatTag = document.createElement("span");
|
|
||||||
formatTag.setAttribute("class", "badge badge-pill badge-text");
|
|
||||||
formatTag.appendChild(document.createTextNode(hit["_source"]["encoding"]));
|
|
||||||
tags.push(formatTag);
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
//Content
|
|
||||||
if (hit.hasOwnProperty("highlight") && hit["highlight"].hasOwnProperty("content")) {
|
|
||||||
|
|
||||||
let contentDiv = document.createElement("div");
|
|
||||||
contentDiv.setAttribute("class", "content-div bg-light");
|
|
||||||
contentDiv.insertAdjacentHTML('afterbegin', hit["highlight"]["content"][0]);
|
|
||||||
docCard.appendChild(contentDiv);
|
|
||||||
}
|
|
||||||
|
|
||||||
//Audio
|
|
||||||
if (mimeCategory === "audio" && hit["_source"].hasOwnProperty("format_long_name")) {
|
|
||||||
|
|
||||||
let audio = document.createElement("audio");
|
|
||||||
audio.setAttribute("preload", "none");
|
|
||||||
audio.setAttribute("class", "audio-fit fit");
|
|
||||||
audio.setAttribute("controls", "");
|
|
||||||
audio.setAttribute("type", hit["_source"]["mime"]);
|
|
||||||
audio.setAttribute("src", "file/" + hit["_id"]);
|
|
||||||
|
|
||||||
docCard.appendChild(audio)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (thumbnail !== null) {
|
|
||||||
imgWrapper.appendChild(thumbnail);
|
|
||||||
docCard.appendChild(imgWrapper);
|
|
||||||
}
|
|
||||||
if (thumbnailOverlay !== null) {
|
|
||||||
imgWrapper.appendChild(thumbnailOverlay);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (let i = 0; i < tags.length; i++) {
|
|
||||||
tagContainer.appendChild(tags[i]);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
//Size tag
|
|
||||||
let sizeTag = document.createElement("small");
|
|
||||||
sizeTag.appendChild(document.createTextNode(humanFileSize(hit["_source"]["size"])));
|
|
||||||
sizeTag.setAttribute("class", "text-muted");
|
|
||||||
tagContainer.appendChild(sizeTag);
|
|
||||||
|
|
||||||
|
|
||||||
//Download button
|
|
||||||
downloadPopover(docCard, hit["_id"]);
|
|
||||||
|
|
||||||
docCardBody.appendChild(link);
|
|
||||||
docCard.appendChild(docCardBody);
|
|
||||||
|
|
||||||
link.appendChild(title);
|
|
||||||
docCardBody.appendChild(tagContainer);
|
|
||||||
|
|
||||||
return docCard;
|
|
||||||
}
|
|
||||||
|
|
||||||
function makePageIndicator(searchResult) {
|
|
||||||
let pageIndicator = document.createElement("div");
|
|
||||||
pageIndicator.appendChild(document.createTextNode(docCount + " / " +searchResult["hits"]["total"]));
|
|
||||||
return pageIndicator;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
function insertHits(resultContainer, hits) {
|
|
||||||
for (let i = 0 ; i < hits.length; i++) {
|
|
||||||
resultContainer.appendChild(createDocCard(hits[i]));
|
|
||||||
docCount++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
window.addEventListener("scroll", function () {
|
|
||||||
|
|
||||||
if (!coolingDown) {
|
|
||||||
|
|
||||||
let threshold = 350;
|
|
||||||
|
|
||||||
if ((window.innerHeight + window.scrollY) >= document.body.offsetHeight - threshold) {
|
|
||||||
//load next page
|
|
||||||
|
|
||||||
let xhttp = new XMLHttpRequest();
|
|
||||||
xhttp.onreadystatechange = function() {
|
|
||||||
if (this.readyState === 4 && this.status === 200) {
|
|
||||||
|
|
||||||
let searchResult = JSON.parse(this.responseText);
|
|
||||||
let searchResults = document.getElementById("searchResults");
|
|
||||||
let hits = searchResult["hits"]["hits"];
|
|
||||||
|
|
||||||
//Page indicator
|
|
||||||
let pageIndicator = makePageIndicator(searchResult);
|
|
||||||
searchResults.appendChild(pageIndicator);
|
|
||||||
|
|
||||||
//Result container
|
|
||||||
let resultContainer = makeResultContainer();
|
|
||||||
searchResults.appendChild(resultContainer);
|
|
||||||
|
|
||||||
insertHits(resultContainer, hits);
|
|
||||||
|
|
||||||
initPopover();
|
|
||||||
|
|
||||||
if (hits.length !== 0) {
|
|
||||||
coolingDown = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
xhttp.open("GET", "/scroll?scroll_id=" + scroll_id, true);
|
|
||||||
xhttp.send();
|
|
||||||
coolingDown = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
function getSelectedMimeTypes() {
|
|
||||||
let mimeTypes = [];
|
|
||||||
|
|
||||||
let selected = tree.selected();
|
|
||||||
|
|
||||||
for (let i = 0; i < selected.length; i++) {
|
|
||||||
|
|
||||||
if(selected[i].id === "any") {
|
|
||||||
return "any"
|
|
||||||
}
|
|
||||||
|
|
||||||
//Only get children
|
|
||||||
if (selected[i].text.indexOf("(") !== -1) {
|
|
||||||
mimeTypes.push(selected[i].id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return mimeTypes
|
|
||||||
}
|
|
||||||
|
|
||||||
function search() {
|
|
||||||
|
|
||||||
if (searchQueued === true) {
|
|
||||||
searchQueued = false;
|
|
||||||
|
|
||||||
//Clear old search results
|
|
||||||
let searchResults = document.getElementById("searchResults");
|
|
||||||
while (searchResults.firstChild) {
|
|
||||||
searchResults.removeChild(searchResults.firstChild);
|
|
||||||
}
|
|
||||||
|
|
||||||
let query = searchBar.value;
|
|
||||||
|
|
||||||
let xhttp = new XMLHttpRequest();
|
|
||||||
xhttp.onreadystatechange = function() {
|
|
||||||
if (this.readyState === 4 && this.status === 200) {
|
|
||||||
|
|
||||||
let searchResult = JSON.parse(this.responseText);
|
|
||||||
scroll_id = searchResult["_scroll_id"];
|
|
||||||
|
|
||||||
//Search stats
|
|
||||||
searchResults.appendChild(makeStatsCard(searchResult));
|
|
||||||
|
|
||||||
//Autocomplete
|
|
||||||
if (searchResult.hasOwnProperty("suggest") && searchResult["suggest"].hasOwnProperty("path")) {
|
|
||||||
pathAutoComplete = [];
|
|
||||||
for (let i = 0; i < searchResult["suggest"]["path"][0]["options"].length; i++) {
|
|
||||||
pathAutoComplete.push(searchResult["suggest"]["path"][0]["options"][i].text)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
//Setup page
|
|
||||||
let resultContainer = makeResultContainer();
|
|
||||||
searchResults.appendChild(resultContainer);
|
|
||||||
|
|
||||||
//Insert search results (hits)
|
|
||||||
docCount = 0;
|
|
||||||
insertHits(resultContainer, searchResult["hits"]["hits"]);
|
|
||||||
|
|
||||||
//Initialise download/view button popover
|
|
||||||
initPopover();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
xhttp.open("POST", "/search", true);
|
|
||||||
|
|
||||||
let postBody = {};
|
|
||||||
postBody.q = query;
|
|
||||||
postBody.size_min = size_min;
|
|
||||||
postBody.size_max = size_max;
|
|
||||||
postBody.mime_types = getSelectedMimeTypes();
|
|
||||||
postBody.must_match = must_match;
|
|
||||||
postBody.directories = selectedDirs;
|
|
||||||
postBody.path = pathBar.value.replace(/\/$/, "").toLowerCase(); //remove trailing slashes
|
|
||||||
xhttp.setRequestHeader('content-type', 'application/json');
|
|
||||||
xhttp.send(JSON.stringify(postBody));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let pathAutoComplete = [];
|
|
||||||
let size_min = 0;
|
|
||||||
let size_max = 10000000000000;
|
|
||||||
|
|
||||||
searchBar.addEventListener("keyup", function () {
|
|
||||||
searchQueued = true;
|
|
||||||
});
|
|
||||||
|
|
||||||
//Size slider
|
|
||||||
let sizeSlider = $("#sizeSlider").ionRangeSlider({
|
|
||||||
type: "double",
|
|
||||||
grid: false,
|
|
||||||
force_edges: true,
|
|
||||||
min: 0,
|
|
||||||
max: 3684.03149864,
|
|
||||||
from: 0,
|
|
||||||
to: 3684.03149864,
|
|
||||||
min_interval: 5,
|
|
||||||
drag_interval: true,
|
|
||||||
prettify: function (num) {
|
|
||||||
|
|
||||||
if(num === 0) {
|
|
||||||
return "0 B"
|
|
||||||
} else if (num >= 3684) {
|
|
||||||
return humanFileSize(num * num * num) + "+";
|
|
||||||
}
|
|
||||||
|
|
||||||
return humanFileSize(num * num * num)
|
|
||||||
},
|
|
||||||
onChange: function(e) {
|
|
||||||
size_min = (e.from * e.from * e.from);
|
|
||||||
size_max = (e.to * e.to * e.to);
|
|
||||||
|
|
||||||
if (e.to >= 3684) {
|
|
||||||
size_max = 10000000000000;
|
|
||||||
}
|
|
||||||
|
|
||||||
searchQueued = true;
|
|
||||||
}
|
|
||||||
})[0];
|
|
||||||
|
|
||||||
//Directories select
|
|
||||||
function updateDirectories() {
|
|
||||||
let selected = $('#directories').find('option:selected');
|
|
||||||
selectedDirs = [];
|
|
||||||
$(selected).each(function(){
|
|
||||||
selectedDirs.push(parseInt($(this).val()));
|
|
||||||
});
|
|
||||||
|
|
||||||
searchQueued = true;
|
|
||||||
}
|
|
||||||
document.getElementById("directories").addEventListener("change", updateDirectories);
|
|
||||||
updateDirectories();
|
|
||||||
searchQueued = false;
|
|
||||||
|
|
||||||
//Suggest
|
|
||||||
function getPathChoices() {
|
|
||||||
return new Promise(getPaths => {
|
|
||||||
|
|
||||||
let xhttp = new XMLHttpRequest();
|
|
||||||
xhttp.onreadystatechange = function() {
|
|
||||||
if (this.readyState === 4 && this.status === 200) {
|
|
||||||
getPaths(JSON.parse(xhttp.responseText))
|
|
||||||
}
|
|
||||||
};
|
|
||||||
xhttp.open("GET", "/suggest?prefix=" + pathBar.value, true);
|
|
||||||
xhttp.send();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
window.setInterval(search, 75)
|
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
<script src="/static/js/search.js"></script>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{% endblock body %}
|
{% endblock body %}
|
@ -74,8 +74,8 @@
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var currentTask = JSON.parse(this.responseText);
|
let currentTask = JSON.parse(this.responseText);
|
||||||
var percent = currentTask.parsed / currentTask.total * 100;
|
let percent = currentTask.parsed / currentTask.total * 100;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|
||||||
@ -84,7 +84,7 @@
|
|||||||
document.getElementById("task-label-" + currentTask.id).innerHTML = "Calculating file count...";
|
document.getElementById("task-label-" + currentTask.id).innerHTML = "Calculating file count...";
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
var bar = document.getElementById("task-bar-" + currentTask.id);
|
let bar = document.getElementById("task-bar-" + currentTask.id);
|
||||||
bar.setAttribute("style", "width: " + percent + "%;");
|
bar.setAttribute("style", "width: " + percent + "%;");
|
||||||
document.getElementById("task-label-" + currentTask.id).innerHTML = currentTask.parsed + " / " + currentTask.total + " (" + percent.toFixed(2) + "%)";
|
document.getElementById("task-label-" + currentTask.id).innerHTML = currentTask.parsed + " / " + currentTask.total + " (" + percent.toFixed(2) + "%)";
|
||||||
|
|
||||||
@ -93,8 +93,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
window.reload();
|
window.reload();
|
||||||
}
|
}
|
||||||
|
@ -37,12 +37,13 @@ class ThumbnailGenerator:
|
|||||||
try:
|
try:
|
||||||
(ffmpeg.
|
(ffmpeg.
|
||||||
input(path)
|
input(path)
|
||||||
.overwrite_output("tmp", vframes=1, f="image2", loglevel="error")
|
.output("tmp", vframes=1, f="image2", loglevel="error")
|
||||||
.run()
|
.run()
|
||||||
)
|
)
|
||||||
self.generate_image("tmp", dest_path)
|
self.generate_image("tmp", dest_path)
|
||||||
os.remove("tmp")
|
os.remove("tmp")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
print(e)
|
||||||
print("Couldn't make thumbnail for " + path)
|
print("Couldn't make thumbnail for " + path)
|
||||||
|
|
||||||
def generate_all(self, docs, dest_path, counter: Value=None):
|
def generate_all(self, docs, dest_path, counter: Value=None):
|
||||||
|
28
tmp_specs
28
tmp_specs
@ -1,28 +0,0 @@
|
|||||||
Ajouter un utilisateur
|
|
||||||
mettre admin
|
|
||||||
Enlever admin
|
|
||||||
ne marche pas si t'est le seul admin
|
|
||||||
|
|
||||||
y'existe conn.executescript
|
|
||||||
|
|
||||||
Utiliser des functions queries pour afficher genre le total size of query, etc
|
|
||||||
Utiliser opendirectories-bot pour afficher des info
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Plugins
|
|
||||||
MP3 tags
|
|
||||||
todo: other music
|
|
||||||
Font files
|
|
||||||
images
|
|
||||||
video tags
|
|
||||||
|
|
||||||
use es filter to filter out folders that the user has no permission to search
|
|
||||||
|
|
||||||
option to toggle auto complete
|
|
||||||
option to set password loop count
|
|
||||||
option to chose checksum thingy
|
|
||||||
option to chose mime guesser
|
|
||||||
option to toggle search history/stats
|
|
||||||
|
|
||||||
thumbnails are stored in a folder for each folder: easy to delete
|
|
Loading…
x
Reference in New Issue
Block a user