added HTTP cache and package size

This commit is contained in:
2017-09-30 00:49:35 +02:00
parent ce06721e79
commit 38d3022be0
3 changed files with 159 additions and 56 deletions

View File

@@ -11,11 +11,16 @@ var pkgMap : JsonNode
const serverURL {.strdefine.}: string = "http://oggio88.soon.it/jpacrepo/" const serverURL {.strdefine.}: string = "http://oggio88.soon.it/jpacrepo/"
proc last[T](s : seq[T]) : T = s[s.len - 1]
proc formatByteSize(size : BiggestInt) : string = size.float64.formatEng(precision=1, siPrefix=true, unit = "B")
type DownloadPanel = ref object type DownloadPanel = ref object
badge : Node badge : Node
listgroup : Node listgroup : Node
pkgs : HashSet[string] pkgs : HashSet[string]
footer : Node footer : Node
sizeLabel : Node
size : BiggestInt
proc newDownloadPanel(parent : Node) : DownloadPanel = proc newDownloadPanel(parent : Node) : DownloadPanel =
let dp = DownloadPanel() let dp = DownloadPanel()
@@ -45,6 +50,13 @@ proc newDownloadPanel(parent : Node) : DownloadPanel =
"text-align" : "right", "text-align" : "right",
"display" :"none" "display" :"none"
} }
"span":
style {
"font-weight" : "bold",
"padding-right" : "10px"
}
cb:
dp.sizeLabel = node
"button": "button":
class ["btn", "btn-primary"] class ["btn", "btn-primary"]
attrs {"type" : "button"} attrs {"type" : "button"}
@@ -78,28 +90,48 @@ proc updateBadge(dp : DownloadPanel) =
dp.badge.style.display = st dp.badge.style.display = st
dp.badge.textContent = $dp.pkgs.len() dp.badge.textContent = $dp.pkgs.len()
proc updateSize(dp : DownloadPanel) =
dp.sizeLabel.textContent = "Total: " & dp.size.formatByteSize
proc addPkg(dp : DownloadPanel, pkgfile : string) = proc addPkg(dp : DownloadPanel, pkgfile : string) =
if not dp.pkgs.contains(pkgfile): if not dp.pkgs.contains(pkgfile):
dp.pkgs.incl(pkgfile) dp.pkgs.incl(pkgfile)
htmlDocument(document, dp.listgroup): let req = newXMLHTTPRequest()
"li": let load_cb = proc(e : Event) =
var listElement : Node let sz = parseInt(req.responseText)
class ["list-group-item"] dp.size += sz
text pkgfile dp.updateSize
"span": htmlDocument(document, dp.listgroup):
class ["glyphicon", "glyphicon-remove", "pull-right"] "li":
var listElement : Node
class ["list-group-item"]
text pkgfile
"span":
class ["glyphicon", "glyphicon-remove", "pull-right"]
cb:
let fn = proc(e : Event) =
dp.pkgs.excl(pkgfile)
listElement.remove()
dp.updateBadge
dp.size -= sz
dp.updateSize
node.addEventListener("click", fn)
cb: cb:
let fn = proc(e : Event) = listElement = node
dp.pkgs.excl(pkgfile) req.addEventListener("load", load_cb)
listElement.remove() req.open("get", serverURL & "rest/pkg/filesize/" & pkgfile)
dp.updateBadge req.setRequestHeader("Accept", "application/json")
node.addEventListener("click", fn) req.send()
cb:
listElement = node
dp.updateBadge dp.updateBadge
proc readTableRow(row : Node) : JsonNode =
let pkgname = $row.querySelector("td:nth-child(2)").textContent
let version = $row.querySelector("td:nth-child(3) button").textContent
let arch = $row.querySelector("td:nth-child(4) button").textContent
pkgMap[pkgname][version][arch]
proc createDropdown(parent : Node, data :seq[string], onchange : proc(value : string)) = proc createDropdown(parent : Node, data :seq[string], onchange : proc(value : string)) =
htmlDocument(document, parent): htmlDocument(document, parent):
"div": "div":
var button : Node var button : Node
@@ -108,9 +140,7 @@ proc createDropdown(parent : Node, data :seq[string], onchange : proc(value : st
class ["btn", "btn-default", "dropdown-toggle"] class ["btn", "btn-default", "dropdown-toggle"]
attrs {"data-toggle": "dropdown", "type": "button"} attrs {"data-toggle": "dropdown", "type": "button"}
cb: cb:
for line in data: node.textContent = data.last
node.textContent = line
break
button = node button = node
"ul": "ul":
class ["dropdown-menu"] class ["dropdown-menu"]
@@ -162,6 +192,8 @@ proc newPkgTable(parent: Node, searchString : string) : PkgTable =
text "Version" text "Version"
"th": "th":
text "Arch" text "Arch"
"th":
text "Installed size"
"tbody": "tbody":
cb: cb:
var i = 0 var i = 0
@@ -169,7 +201,12 @@ proc newPkgTable(parent: Node, searchString : string) : PkgTable =
closureScope: closureScope:
htmlDocument(document, node): htmlDocument(document, node):
"tr": "tr":
var row : Node
var archCell : Node var archCell : Node
var sizeCell : Node
let size_change_callback = proc(newValue : string) =
sizeCell.textContent = readTableRow(row)["size"].getNum.formatByteSize
"td": "td":
"div": "div":
class ["checkbox"] class ["checkbox"]
@@ -189,7 +226,8 @@ proc newPkgTable(parent: Node, searchString : string) : PkgTable =
var newdata = newSeq[string]() var newdata = newSeq[string]()
for arch, pkgname in vs[newValue]: for arch, pkgname in vs[newValue]:
newdata.add(arch) newdata.add(arch)
createDropdown(archCell, newdata, proc(s : string) = discard) createDropdown(archCell, newdata, size_change_callback)
size_change_callback(newValue)
createDropdown(node, data, change_callback) createDropdown(node, data, change_callback)
"td": "td":
cb: cb:
@@ -200,7 +238,17 @@ proc newPkgTable(parent: Node, searchString : string) : PkgTable =
arches = a arches = a
for arch, pkgname in arches: for arch, pkgname in arches:
data.add(arch) data.add(arch)
createDropdown(node, data, proc(s : string) = discard) createDropdown(node, data, size_change_callback)
"td":
cb:
sizeCell = node
for v, arches in versions:
for key, value in arches:
node.textContent = value["size"].getNum.formatByteSize
return
cb:
row = node
pkgtable pkgtable
var dp : DownloadPanel var dp : DownloadPanel
@@ -248,10 +296,7 @@ htmlDocument document, document.body:
for row in rows: for row in rows:
let cbox = row.querySelector("td:first-child input") let cbox = row.querySelector("td:first-child input")
if cbox.checked: if cbox.checked:
let pkgname = $row.querySelector("td:nth-child(2)").textContent dp.addPkg(readTableRow(row)["filename"].getStr)
let version = $row.querySelector("td:nth-child(3) button").textContent
let arch = $row.querySelector("td:nth-child(4) button").textContent
dp.addPkg(pkgMap[pkgname][version][arch].getStr)
proc oninput(e : Event) = proc oninput(e : Event) =
if pkgMap.len == 0 or node.value.len < 2: return if pkgMap.len == 0 or node.value.len < 2: return
let pkgtable = newPkgTable(table, $node.value) let pkgtable = newPkgTable(table, $node.value)

View File

@@ -8,12 +8,13 @@ import com.oggio88.jpacrepo.model.PkgList;
import com.oggio88.jpacrepo.model.PkgName; import com.oggio88.jpacrepo.model.PkgName;
import com.oggio88.jpacrepo.model.StringList; import com.oggio88.jpacrepo.model.StringList;
import com.oggio88.jpacrepo.pacbase.Parser; import com.oggio88.jpacrepo.pacbase.Parser;
import com.oggio88.jpacrepo.persistence.QueryEngine;
import org.apache.commons.compress.archivers.tar.TarArchiveEntry; import org.apache.commons.compress.archivers.tar.TarArchiveEntry;
import org.apache.commons.compress.archivers.tar.TarArchiveOutputStream; import org.apache.commons.compress.archivers.tar.TarArchiveOutputStream;
import javax.annotation.Resource; import javax.annotation.Resource;
import javax.ejb.*; import javax.ejb.Singleton;
import javax.ejb.TransactionManagement;
import javax.ejb.TransactionManagementType;
import javax.inject.Inject; import javax.inject.Inject;
import javax.persistence.*; import javax.persistence.*;
import javax.persistence.criteria.CriteriaBuilder; import javax.persistence.criteria.CriteriaBuilder;
@@ -24,6 +25,7 @@ import javax.transaction.Status;
import javax.transaction.UserTransaction; import javax.transaction.UserTransaction;
import javax.ws.rs.*; import javax.ws.rs.*;
import javax.ws.rs.core.*; import javax.ws.rs.core.*;
import javax.xml.bind.annotation.XmlRootElement;
import java.io.*; import java.io.*;
import java.net.URI; import java.net.URI;
import java.nio.file.Files; import java.nio.file.Files;
@@ -33,6 +35,14 @@ import java.util.TreeMap;
import java.util.logging.Level; import java.util.logging.Level;
import java.util.logging.Logger; import java.util.logging.Logger;
@XmlRootElement
class PkgTuple
{
String md5sum;
public String filename;
public long size;
}
@CORSManaged @CORSManaged
@Singleton @Singleton
@Path("/pkg") @Path("/pkg")
@@ -41,7 +51,7 @@ import java.util.logging.Logger;
@TransactionManagement(TransactionManagementType.BEAN) @TransactionManagement(TransactionManagementType.BEAN)
public class PacmanWebService public class PacmanWebService
{ {
private SortedMap<String, SortedMap<String, SortedMap<String, String>>> cachedMap = null; private SortedMap<String, SortedMap<String, SortedMap<String, PkgTuple>>> cachedMap = null;
@PersistenceContext(unitName = "jpacrepo_pu") @PersistenceContext(unitName = "jpacrepo_pu")
private EntityManager em; private EntityManager em;
@@ -63,6 +73,42 @@ public class PacmanWebService
@DefaultConfiguration @DefaultConfiguration
private ApplicationContext ctx; private ApplicationContext ctx;
private SortedMap<String, SortedMap<String, SortedMap<String, PkgTuple>>> getCachedMap()
{
SortedMap<String, SortedMap<String, SortedMap<String, PkgTuple>>> result;
if (ctx.invalidateCache)
{
result = new TreeMap<>();
TypedQuery<Object[]> query = em.createQuery("SELECT pkg.name.id, pkg.version, pkg.arch, pkg.fileName, pkg.size, pkg.md5sum FROM PkgData pkg", Object[].class);
for (Object[] pkg : query.getResultList())
{
String name = (String) pkg[0];
String version = (String) pkg[1];
String arch = (String) pkg[2];
String filename = (String) pkg[3];
long size = (long) pkg[4];
String md5sum = (String) pkg[5];
result.putIfAbsent(name, new TreeMap<>());
SortedMap<String, SortedMap<String, PkgTuple>> map = result.get(name);
map.putIfAbsent(version, new TreeMap<>());
SortedMap<String, PkgTuple> map2 = map.get(version);
PkgTuple tuple = new PkgTuple();
tuple.filename = filename;
tuple.size = size;
tuple.md5sum = md5sum;
map2.putIfAbsent(arch, tuple);
}
cachedMap = result;
ctx.invalidateCache = false;
}
else
{
result = cachedMap;
}
return result;
}
@GET @GET
@Path("searchByName/{name}") @Path("searchByName/{name}")
public Response searchByName(@PathParam("name") String name) public Response searchByName(@PathParam("name") String name)
@@ -116,33 +162,22 @@ public class PacmanWebService
@GET @GET
@Produces(MediaType.APPLICATION_JSON) @Produces(MediaType.APPLICATION_JSON)
@Path("map") @Path("map")
public Response getPackageMap() public Response getPackageMap(@Context Request request)
{ {
SortedMap<String, SortedMap<String, SortedMap<String, String>>> result; CacheControl cc = new CacheControl();
if (ctx.invalidateCache) cc.setMaxAge(86400);
cc.setMustRevalidate(true);
cc.setNoCache(true);
SortedMap<String, SortedMap<String, SortedMap<String, PkgTuple>>> result = getCachedMap();
EntityTag etag = new EntityTag(Integer.toString(result.hashCode()));
Response.ResponseBuilder builder = request.evaluatePreconditions(etag);
if (builder == null)
{ {
result = new TreeMap<>(); builder = Response.ok(result);
TypedQuery<Object[]> query = em.createQuery("SELECT pkg.name.id, pkg.version, pkg.arch, pkg.fileName FROM PkgData pkg", Object[].class); builder.tag(etag);
for (Object[] pkg : query.getResultList())
{
String name = (String) pkg[0];
String version = (String) pkg[1];
String arch = (String) pkg[2];
String filename = (String) pkg[3];
result.putIfAbsent(name, new TreeMap<>());
SortedMap<String, SortedMap<String, String>> map = result.get(name);
map.putIfAbsent(version, new TreeMap<>());
SortedMap<String, String> map2 = map.get(version);
map2.putIfAbsent(arch, filename);
}
cachedMap = result;
ctx.invalidateCache = false;
} }
else builder.cacheControl(cc);
{ return builder.build();
result = cachedMap;
}
return Response.ok(result).build();
} }
@GET @GET
@@ -179,6 +214,27 @@ public class PacmanWebService
return manageQueryResult(fnquery.getResultList(), true); return manageQueryResult(fnquery.getResultList(), true);
} }
@GET
@Path("filesize/{filename}")
public Response getFileSize(@PathParam("filename") String fileName, @Context Request request)
{
CacheControl cc = new CacheControl();
cc.setMaxAge(86400);
cc.setMustRevalidate(true);
cc.setNoCache(true);
EntityTag etag = new EntityTag(Integer.toString(getCachedMap().hashCode()));
Response.ResponseBuilder builder = request.evaluatePreconditions(etag);
if (builder == null)
{
File res = ctx.getFile(fileName);
if (!res.exists()) throw new NotFoundException(String.format("File '%s' was not found", fileName));
builder = Response.ok(res.length());
builder.tag(etag);
}
builder.cacheControl(cc);
return builder.build();
}
@GET @GET
@Path("download/{filename}") @Path("download/{filename}")
@Produces(MediaType.APPLICATION_OCTET_STREAM) @Produces(MediaType.APPLICATION_OCTET_STREAM)
@@ -356,7 +412,8 @@ public class PacmanWebService
@OPTIONS @OPTIONS
@Path("/downloadTar") @Path("/downloadTar")
@Produces("text/plain; charset=UTF-8") @Produces("text/plain; charset=UTF-8")
public Response options() { public Response options()
{
return Response.ok("POST, OPTIONS").build(); return Response.ok("POST, OPTIONS").build();
} }
@@ -367,9 +424,10 @@ public class PacmanWebService
public Response downloadTar(@FormParam("pkgs") String formData) public Response downloadTar(@FormParam("pkgs") String formData)
{ {
String[] files = formData.split(" "); String[] files = formData.split(" ");
for(String fname : files) for (String fname : files)
{ {
if(!ctx.getFile(fname).exists()) throw new NotFoundException(String.format("Package file '%s' does not exist", fname)); if (!ctx.getFile(fname).exists())
throw new NotFoundException(String.format("Package file '%s' does not exist", fname));
} }
StreamingOutput stream = new StreamingOutput() StreamingOutput stream = new StreamingOutput()
{ {