Added label search and pages
This commit is contained in:
parent
78ed396c36
commit
e6b69eb7b4
|
@ -1,6 +1,8 @@
|
||||||
# RAR-Index
|
# RAR-Index
|
||||||
It's some project for my school which is supposed to index _rar_-archives and make them searchable over a very ugly web frontend.
|
It's some project for my school which is supposed to index _rar_-archives and make them searchable over a very ugly web frontend.
|
||||||
|
|
||||||
|
**!!Works only on python 3.10 and newer!!**
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
- Add and delete archives
|
- Add and delete archives
|
||||||
- edit their labels (as their owner)
|
- edit their labels (as their owner)
|
||||||
|
|
25
flask/app.py
25
flask/app.py
|
@ -36,7 +36,7 @@ def convsize(s:int):
|
||||||
def homepage():
|
def homepage():
|
||||||
# try to get userdata, else logout state
|
# try to get userdata, else logout state
|
||||||
logged_in,userdata=get_login_info(request.cookies.get('session-id'))
|
logged_in,userdata=get_login_info(request.cookies.get('session-id'))
|
||||||
archives=db.get_n_archives()
|
archives,count=db.get_n_archives()
|
||||||
return render_template("home.html", title="Homepage",userdata=userdata,login=logged_in,archives=archives)
|
return render_template("home.html", title="Homepage",userdata=userdata,login=logged_in,archives=archives)
|
||||||
|
|
||||||
@app.route('/user')
|
@app.route('/user')
|
||||||
|
@ -195,7 +195,8 @@ def labeleditpage(archid:int):
|
||||||
return render_template("labels.html", title="Edit Labels",userdata=userdata,login=logged_in,archive=archive,res_labels=label_dict,labels_names=labels_name_list)
|
return render_template("labels.html", title="Edit Labels",userdata=userdata,login=logged_in,archive=archive,res_labels=label_dict,labels_names=labels_name_list)
|
||||||
|
|
||||||
@app.route('/search')
|
@app.route('/search')
|
||||||
def searchpage():
|
@app.route('/search/<int:page>')
|
||||||
|
def searchpage(page:int=1):
|
||||||
# try to get userdata, else logout state
|
# try to get userdata, else logout state
|
||||||
logged_in,userdata=get_login_info(request.cookies.get('session-id'))
|
logged_in,userdata=get_login_info(request.cookies.get('session-id'))
|
||||||
|
|
||||||
|
@ -205,8 +206,12 @@ def searchpage():
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
sorttype="time"
|
sorttype="time"
|
||||||
try:
|
try:
|
||||||
category=request.args['category']
|
category=int(request.args['category'])
|
||||||
label_dict=db.get_label_labeltypes(int(category))
|
catparentid=db.get_category_info(category)[2]
|
||||||
|
if catparentid:
|
||||||
|
label_dict=db.get_label_labeltypes(catparentid)
|
||||||
|
else:
|
||||||
|
label_dict=db.get_label_labeltypes(category)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
category=0
|
category=0
|
||||||
label_dict={}
|
label_dict={}
|
||||||
|
@ -215,7 +220,7 @@ def searchpage():
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
keywords=[]
|
keywords=[]
|
||||||
try:
|
try:
|
||||||
count=request.args['count']
|
count=int(request.args['count'])
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
count=20
|
count=20
|
||||||
labels=[]
|
labels=[]
|
||||||
|
@ -228,11 +233,16 @@ def searchpage():
|
||||||
continue
|
continue
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
labels=[]
|
labels=[]
|
||||||
archives=db.get_n_archives(sorttype,category,keywords,count,labels)
|
archives,total_count=db.get_n_archives(sorttype,category,keywords,count,labels,page)
|
||||||
|
# if the requested page is larger than the maximum, return page 1
|
||||||
|
if page*count > total_count and (page-1)*count >= total_count:
|
||||||
|
page=1
|
||||||
|
archives,total_count=db.get_n_archives(sorttype,category,keywords,count,labels,page)
|
||||||
|
|
||||||
|
|
||||||
htmlcatlist=get_category_selection()
|
htmlcatlist=get_category_selection()
|
||||||
|
|
||||||
return render_template("search.html", title="Advanced Search",categories=htmlcatlist,userdata=userdata,login=logged_in,archives=archives,res_labels=label_dict,labels=labels)
|
return render_template("search.html", title="Advanced Search",page=page,count=count,total_count=total_count,categories=htmlcatlist,userdata=userdata,login=logged_in,archives=archives,res_labels=label_dict,labels=labels)
|
||||||
|
|
||||||
########## FUNCTIONS
|
########## FUNCTIONS
|
||||||
|
|
||||||
|
@ -254,6 +264,7 @@ def get_login_info(sesskey:str):
|
||||||
userdata=()
|
userdata=()
|
||||||
return logged_in,userdata
|
return logged_in,userdata
|
||||||
|
|
||||||
|
## returns a cookie and redirects to the homepage
|
||||||
def setcookie(name:str,value:str,lifetime:int=10000):
|
def setcookie(name:str,value:str,lifetime:int=10000):
|
||||||
resp = make_response(redirect('/'))
|
resp = make_response(redirect('/'))
|
||||||
resp.set_cookie(name, value, max_age=lifetime)
|
resp.set_cookie(name, value, max_age=lifetime)
|
||||||
|
|
|
@ -183,6 +183,12 @@ class db:
|
||||||
def get_categories(self):
|
def get_categories(self):
|
||||||
self.cur.execute("SELECT * FROM Cats;")
|
self.cur.execute("SELECT * FROM Cats;")
|
||||||
return self.cur.fetchall()
|
return self.cur.fetchall()
|
||||||
|
|
||||||
|
## Gets and returns all info about one (1) category
|
||||||
|
## OUTPUT: tup=(ID:int,CATEGORY:str,PARENT:int,DESCRIPTION:str)
|
||||||
|
def get_category_info(self, catid:int):
|
||||||
|
self.cur.execute(f"SELECT * FROM Cats WHERE ID={catid}")
|
||||||
|
return self.cur.fetchone()
|
||||||
|
|
||||||
## get all labeltypes and their respective labels based on a category parent
|
## get all labeltypes and their respective labels based on a category parent
|
||||||
## OUTPUT: res_dict:dict={…,LabType.NAME:[…,(ID:int,NAME:str),…],…}
|
## OUTPUT: res_dict:dict={…,LabType.NAME:[…,(ID:int,NAME:str),…],…}
|
||||||
|
@ -245,24 +251,24 @@ class db:
|
||||||
return True, ""
|
return True, ""
|
||||||
|
|
||||||
## Returns n archives, sorted by a column
|
## Returns n archives, sorted by a column
|
||||||
## OUTPUT: archives:array=[…,(ID:int,NAME:str,SIZE:str,IMPORTED[UNIX]:int),…]
|
## OUTPUT: archives:array=[…,(ID:int,NAME:str,SIZE:str,IMPORTED[UNIX]:int),…], total_count:int
|
||||||
def get_n_archives(self, sorttype:str="time",category:int=0, keywords:list=[], count:int=20,labels:list=[]): # TODO: CLEANN!!!!!
|
def get_n_archives(self, sorttype:str="time",category:int=0, keywords:list=[], count:int=20, labels:list=None, page:int=1): # TODO: CLEANN!!!!!
|
||||||
match sorttype:
|
match sorttype:
|
||||||
case "size":
|
case "size":
|
||||||
sorttype="SIZE DESC"
|
sorttype="Archs.SIZE DESC"
|
||||||
case "time":
|
case "time":
|
||||||
sorttype="IMPORTED DESC"
|
sorttype="Archs.IMPORTED DESC"
|
||||||
case "za":
|
case "za":
|
||||||
sorttype="NAME DESC"
|
sorttype="Archs.NAME DESC"
|
||||||
case _:
|
case _:
|
||||||
sorttype="NAME ASC"
|
sorttype="Archs.NAME ASC"
|
||||||
|
|
||||||
# create SQL query for keywords
|
# create SQL query for keywords
|
||||||
keyword_string=""
|
keyword_string=""
|
||||||
for w in keywords:
|
for w in keywords:
|
||||||
keyword_string+=f"AND NAME LIKE '%{w}%' "
|
keyword_string+=f"AND Archs.NAME LIKE '%{w}%' "
|
||||||
if len(keywords) == 1:
|
if len(keywords) == 1:
|
||||||
keyword_string+=f"OR HASH = '{keywords[0]}' "
|
keyword_string+=f"OR Archs.HASH = '{keywords[0]}' "
|
||||||
|
|
||||||
# get all children of category (if exist) and put into query string
|
# get all children of category (if exist) and put into query string
|
||||||
categories=self.get_categories()
|
categories=self.get_categories()
|
||||||
|
@ -272,13 +278,20 @@ class db:
|
||||||
catlist.append(str(i[0]))
|
catlist.append(str(i[0]))
|
||||||
categories="(" + ",".join(catlist) + ")"
|
categories="(" + ",".join(catlist) + ")"
|
||||||
|
|
||||||
self.cur.execute(f"""SELECT Archs.ID,Archs.NAME,Archs.SIZE,Archs.IMPORTED FROM Archs
|
# select all archives by keywords, categories and labels
|
||||||
{"WHERE 1=1" if category==0 else "WHERE CATEGORY IN " + categories}
|
self.cur.execute(f"""SELECT DISTINCT Archs.ID,Archs.NAME,Archs.SIZE,Archs.IMPORTED FROM Archs
|
||||||
|
JOIN ArchLab ON Archs.ID=ArchLab.ArchID
|
||||||
|
{"WHERE 1=1" if category==0 else "WHERE Archs.CATEGORY IN " + categories}
|
||||||
|
{f"AND ArchLab.LabID IN ({','.join(labels)})" if labels else ""}
|
||||||
{keyword_string}
|
{keyword_string}
|
||||||
ORDER BY {sorttype} LIMIT {count if count else 20}""")
|
GROUP BY Archs.ID
|
||||||
|
{f"HAVING COUNT(Archs.ID)={len(labels)}" if labels else ""}
|
||||||
|
ORDER BY {sorttype}""")
|
||||||
archives=self.cur.fetchall()
|
archives=self.cur.fetchall()
|
||||||
|
res_archives=archives[page*count-count:page*count] # get archives for the selected page
|
||||||
|
total_count=len(archives)
|
||||||
|
|
||||||
return archives
|
return res_archives, total_count
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
#startup()
|
#startup()
|
||||||
|
|
|
@ -31,4 +31,44 @@ a.grid-item {
|
||||||
|
|
||||||
div.clickable:hover {
|
div.clickable:hover {
|
||||||
background: lightgrey;
|
background: lightgrey;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.pageselect {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr max-content 1fr;
|
||||||
|
margin: 1em auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.align-right {
|
||||||
|
display: inline-flex;
|
||||||
|
justify-content: end;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.align-left {
|
||||||
|
display: inline-flex;
|
||||||
|
justify-content: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.pageitem {
|
||||||
|
display: inline-block;
|
||||||
|
font-weight: 700;
|
||||||
|
text-align: center;
|
||||||
|
height: 2em;
|
||||||
|
width: 1.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.pagebutton {
|
||||||
|
background: lightgrey;
|
||||||
|
font-weight: 600;
|
||||||
|
justify-self: auto;
|
||||||
|
border: none;
|
||||||
|
margin: 0 0.2em;
|
||||||
|
height: 2em;
|
||||||
|
width: 1.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
input.pagebutton:hover {
|
||||||
|
background: grey;
|
||||||
|
border: none;
|
||||||
}
|
}
|
|
@ -6,6 +6,7 @@
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
{% if archives|length > 0 %}
|
{% if archives|length > 0 %}
|
||||||
|
<h2>Newest archives</h2>
|
||||||
<div class="grid-container">
|
<div class="grid-container">
|
||||||
<div class="grid-header"><b>ARCHIVE</b></div>
|
<div class="grid-header"><b>ARCHIVE</b></div>
|
||||||
<div class="grid-header"><b>SIZE</b></div>
|
<div class="grid-header"><b>SIZE</b></div>
|
||||||
|
@ -18,6 +19,6 @@
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
{% else %}
|
{% else %}
|
||||||
<p>No matching archives</p>
|
<p>Add some archives first!</p>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endblock %}
|
{% endblock %}
|
|
@ -18,7 +18,7 @@
|
||||||
<option value="size" {% if "size" == request.args.get('sort') %}selected="selected"{% endif %}>Archive Size</option>
|
<option value="size" {% if "size" == request.args.get('sort') %}selected="selected"{% endif %}>Archive Size</option>
|
||||||
</select>
|
</select>
|
||||||
<b>Category:</b>
|
<b>Category:</b>
|
||||||
<select name="category">
|
<select onchange="this.form.submit()" name="category">
|
||||||
<option value="0">None</option>
|
<option value="0">None</option>
|
||||||
{% for i in categories %}
|
{% for i in categories %}
|
||||||
<option value="{{i[0]}}" {% if i[0] == request.args.get('category')|int %}selected="selected"{% endif %}>{{i[1]}}</option>
|
<option value="{{i[0]}}" {% if i[0] == request.args.get('category')|int %}selected="selected"{% endif %}>{{i[1]}}</option>
|
||||||
|
@ -26,7 +26,6 @@
|
||||||
</select>
|
</select>
|
||||||
<b>Count: </b><input type="number" name="count" step="1" placeholder="Number of items" {% if request.args.get('count') %}value="{{request.args.get('count')}}"{% else %} value="20"{% endif %}>
|
<b>Count: </b><input type="number" name="count" step="1" placeholder="Number of items" {% if request.args.get('count') %}value="{{request.args.get('count')}}"{% else %} value="20"{% endif %}>
|
||||||
</div>
|
</div>
|
||||||
{# <!-- Can't get that to work
|
|
||||||
<div class="labels-container">
|
<div class="labels-container">
|
||||||
{% for ltype in res_labels %}
|
{% for ltype in res_labels %}
|
||||||
<div class="flex-item"><b>{{ltype}}</b>
|
<div class="flex-item"><b>{{ltype}}</b>
|
||||||
|
@ -35,8 +34,7 @@
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>-->
|
</div>
|
||||||
#}
|
|
||||||
</div>
|
</div>
|
||||||
{% if request.args.get('q') %}
|
{% if request.args.get('q') %}
|
||||||
<input type="text" name="q" value="{{request.args.get('q')}}" placeholder="Keywords">
|
<input type="text" name="q" value="{{request.args.get('q')}}" placeholder="Keywords">
|
||||||
|
@ -44,20 +42,46 @@
|
||||||
<input type="text" name="q" placeholder="Keywords">
|
<input type="text" name="q" placeholder="Keywords">
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<input type="submit" value="Search!">
|
<input type="submit" value="Search!">
|
||||||
</form>
|
|
||||||
{% if archives|length > 0 %}
|
|
||||||
<div class="grid-container">
|
|
||||||
<div class="grid-header"><b>ARCHIVE</b></div>
|
|
||||||
<div class="grid-header"><b>SIZE</b></div>
|
|
||||||
<div class="grid-header"><b>IMPORTED</b></div>
|
|
||||||
{% for arch in archives %}
|
|
||||||
<a class="grid-item" href="/view/{{arch[0]}}"><div class="grid-item clickable"><b>{{arch[1]}}</b></div></a>
|
|
||||||
<div class="grid-item"><p> {{arch[2]|spacer}} </p></div>
|
|
||||||
<div class="grid-item"><p> {{arch[3]|ctime}} </p></div>
|
|
||||||
|
|
||||||
{% endfor %}
|
{% if archives|length > 0 %}
|
||||||
|
<div class="grid-container">
|
||||||
|
<div class="grid-header"><b>ARCHIVE</b></div>
|
||||||
|
<div class="grid-header"><b>SIZE</b></div>
|
||||||
|
<div class="grid-header"><b>IMPORTED</b></div>
|
||||||
|
{% for arch in archives %}
|
||||||
|
<a class="grid-item" href="/view/{{arch[0]}}"><div class="grid-item clickable"><b>{{arch[1]}}</b></div></a>
|
||||||
|
<div class="grid-item"><p> {{arch[2]|spacer}} </p></div>
|
||||||
|
<div class="grid-item"><p> {{arch[3]|ctime}} </p></div>
|
||||||
|
|
||||||
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
{% else %}
|
{% else %}
|
||||||
<p>No matching archives</p>
|
<p>No matching archives</p>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
{% if total_count > page*count %}
|
||||||
|
<div class="pageselect">
|
||||||
|
<div class="align-right">
|
||||||
|
{% if page-3 > 1 %}
|
||||||
|
<div class="pageitem">…</div>
|
||||||
|
{% endif %}
|
||||||
|
{% for pagenum in range(page-3,page) %}
|
||||||
|
{% if pagenum > 0 %}
|
||||||
|
<input class="pagebutton" type=submit formaction="/search/{{pagenum}}" value="{{pagenum}}">
|
||||||
|
{% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
<div class="pageitem" id="currentpage">{{page}}</div>
|
||||||
|
<div class="align-left">
|
||||||
|
{% for pagenum in range(page+1,page+4) %}
|
||||||
|
{% if (pagenum-1)*count < total_count %}
|
||||||
|
<input class="pagebutton" type=submit formaction="/search/{{pagenum}}" value="{{pagenum}}">
|
||||||
|
{% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
{% if (page+4)*count < total_count %}
|
||||||
|
<div class="pageitem">…</div>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
</form>
|
||||||
{% endblock %}
|
{% endblock %}
|
|
@ -20,6 +20,6 @@
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<img id="cover" src="/static/covers/{{archive[0]}}.webp" onerror="this.onerror=null; this.src='/static/covers/default.webp'" alt="Cover image">
|
<img id="cover" src="/static/covers/custom/{{archive[0]}}.webp" onerror="this.onerror=null; this.src='/static/covers/default.webp'" alt="Cover image">
|
||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
Loading…
Reference in a new issue