Jelajahi Sumber

Separate DRes and ORes rendering; rearrange metadata.

scossu 2 minggu lalu
induk
melakukan
814c55f82d

+ 27 - 8
config/model/typedef/anything.lua

@@ -5,43 +5,62 @@ return {
     abstract = "true",
 
     properties = {
+        ["pas:sourcePath"] = {
+            label = "Source path",
+            description = [[
+                  File or folder path relative to the SIP root as it was
+                  originally submitted.]],
+            type = "string",
+            min_cardinality = 1,
+            max_cardinality = 1,
+        },
+        ["pas:contentType"] = {
+            label = "Content type",
+            type = "string",
+            min_cardinality = 1,
+            max_cardinality = 1,
+        },
         ["pas:id"] = {
             label = "Primary ID",
             type = "string",
             min_cardinality = 1,
             max_cardinality = 1,
         },
-        ["pas:refId"] = {
+        ["dc:identifier"] = {
             label = "External system ID",
             type = "string",
         },
-        ["pas:prefLabel"] = {
-            label = "Preferred Label",
+        ["dc:title"] = {
+            label = "Title",
             type = "string",
             max_cardinality = 1,
         },
-        ["pas:altLabel"] = {
+        ["dc:alternative"] = {
             label = "Alternative Label",
             type = "string",
         },
-        ["pas:created"] = {
+        ["dc:description"] = {
+            label = "Description",
+            type = "string",
+        },
+        ["dc:created"] = {
             label = "Created On",
             type = "timestamp",
             min_cardinality = 1,
             max_cardinality = 1,
         },
-        ["pas:updated"] = {
+        ["dc:modified"] = {
             label = "Last Updated On",
             type = "timestamp",
             min_cardinality = 1,
             max_cardinality = 1,
         },
-        ["pas:createdBy"] = {
+        ["dc:creator"] = {
             label = "Created By",
             type = "rel",
             range = {["pas:Agent"] = true},
         },
-        ["pas:updatedBy"] = {
+        ["dc:contributor"] = {
             label = "Last Updated By",
             type = "rel",
             range = {["pas:Agent"] = true},

+ 5 - 0
config/model/typedef/artifact.lua

@@ -5,6 +5,11 @@ return {
     broader = "pas:Anything",
 
     properties = {
+        ["pas:first"] = {
+            label = "First child",
+            type = "resource",
+            range = {["pas:Part"] = true},
+        },
         ["pas:hasFile"] = {
             label = "Has file",
             type = "rel",

+ 1 - 0
config/model/typedef/file.lua

@@ -1,4 +1,5 @@
 return {
+    id = "pas:File",
     label = "File",
     broader = "pas:Anything",
 

+ 11 - 1
config/model/typedef/still_image_file.lua

@@ -1,5 +1,15 @@
 return {
-    id = "dctype:StillImage",
+    id = "pas:StillImageFile",
     label = "Still Image File",
     broader = "pas:File",
+
+    presentation_type = "image",
+
+    -- Transformers for site generation.
+    transformers = {
+        -- Main deliverable asset.
+        deliverable = {fn = "img_resize", ext = ".jpg", 1024},
+        -- Thumbnail derivative.
+        thumbnail = {fn = "img_resize", ext = ".jpg", 256},
+    },
 }

+ 3 - 1
pocket_archive-scm-1.rockspec

@@ -23,8 +23,9 @@ dependencies = {
    "csv",
    "datafile",
    "etlua",
-   "lyaml",
+   "lua-vips",
    "penlight",
+   "sllog",
 }
 build = {
     type = "builtin",
@@ -33,6 +34,7 @@ build = {
         ["pocket_archive.model"] = "src/model.lua",
         ["pocket_archive.submission"] = "src/submission.lua",
         ["pocket_archive.html_generator"] = "src/html_generator.lua",
+        ["pocket_archive.transformers"] = "src/transformers.lua",
         ["pocket_archive.monocypher"] = {
             "ext/monocypher/monocypher.c",
             "ext/monocypher/lua_monocypher.c",

+ 19 - 2
src/core.lua

@@ -9,10 +9,24 @@ local fpath = debug.getinfo(1, "S").source:sub(2)
 local root_path = path.dirname(path.dirname(fpath))
 local config_path = os.getenv("PA_CONFIG_DIR") or (root_path .. "/config")
 
+
 local M = {
     root = root_path,
     config = dofile(config_path .. "/app.lua"),
-    nsm = namespace.new(),
+
+    default_title = "Pocket Archive",
+
+    --Logger config.
+    logger = require "sllog":init{
+      {"err",   "%T PKAR %-5L ",          "%n", io.stderr},
+      {"warn",  "%T PKAR %-5L ",          "%n", io.stderr},
+      {"info",  "%T PKAR %-5L ",          "%n", io.stderr},
+      {"debug", "%T PKAR %-5L%f (%S) ",   "%n", io.stderr},
+      timefn=(socket or {}).gettime,  -- use socket.gettime if available
+      report="debug",   -- to which level should internal log events be passed?
+      hookrequire=true, -- also report calls to require()
+      level="debug",    -- output levels up to and including "dbg"
+    },
 }
 
 M.store_id = "file://" .. (os.getenv("PA_BASE") or M.config.fs.dres_path)
@@ -20,7 +34,10 @@ M.store = store.new(store.MDB, M.store_id)
 
 print(("Default NSM: %s"):format(M.nsm))
 
-for pfx, ns in pairs(M.config.namespace) do M.nsm:add(pfx, ns) end
+for pfx, ns in pairs(M.config.namespace) do namespace.add(pfx, ns) end
+
+-- Some constant terms.
+M.RDF_TYPE = term.new_iriref_ns("rdf:type", M.nsm)
 
 -- Initialize random ID generator.
 math.randomseed(M.config.id.seed[1], M.config.id.seed[2])

+ 204 - 62
src/html_generator.lua

@@ -1,130 +1,272 @@
 local datafile = require "datafile"
 local dir = require "pl.dir"
 local etlua = require "etlua"
+local plpath = require "pl.path"
 local pp = require "pl.pretty"
 
+local nsm = require "lsup.namespace"
 local term = require "lsup.term"
 local triple = require "lsup.triple"
 local graph = require "lsup.graph"
 
 local pkar = require "pocket_archive"
+local logger = pkar.logger
 local model = require "pocket_archive.model"
+local transformers = require "pocket_archive.transformers"
 
+-- "nil" table - for missing key fallback in chaining.
+local NT = {}
 
 -- Compile all templates once.
 -- TODO Add override for user-maintained templates.
-local fh, idx_tpl, res_tpl, err
+local fh, idx_tpl, dres_tpl, ores_tpl
 fh = datafile.open("templates/index.html")
-idx_tpl, err = etlua.compile(fh:read("a"))
-if not idx_tpl then error(err) end
+idx_tpl = assert(etlua.compile(fh:read("a")))
 fh:close()
 
-fh = datafile.open("templates/res.html")
-res_tpl, err = etlua.compile(fh:read("a"))
-if not res_tpl then error(err) end
+fh = datafile.open("templates/dres.html")
+dres_tpl = assert(etlua.compile(fh:read("a")))
+fh:close()
+
+fh = datafile.open("templates/ores.html")
+ores_tpl = assert(etlua.compile(fh:read("a")))
 fh:close()
 
 
+-- HTML generator module.
 local M = {
     res_dir = pkar.config.htmlgen.out_dir .. "/res",
+    asset_dir = pkar.config.htmlgen.out_dir .. "/assets",
+    media_dir = pkar.config.htmlgen.out_dir .. "/media",
 }
 
-M.generate_resource = function(gr_uri)
-    local gr = graph.get(gr_uri, pkar.store)
+
+local function get_breadcrumbs(mconf)
+    -- Breadcrumbs, from top class to current class.
+    -- Also verify if it's a File subclass.
+    local breadcrumbs = {}
+    for i = 1, #mconf.lineage do
+        breadcrumbs[i] = {
+            mconf.lineage[i],
+            model.models[mconf.lineage[i]].label
+        }
+    end
+
+    return breadcrumbs
+end
+
+
+local function generate_dres(s, mconf)
+    local gr = graph.new(pkar.store, term.DEFAULT_CTX)
+
     local dmd = {}
     local rel = {}
     local children = {}
-
-    local res_type
-    local type_attr = gr:attr(
-            gr_uri, term.new_iriref("pas:contentType", pkar.nsm))
-    res_type = type_attr[1].data
-    local mconf = model.models[res_type]
-
-    local attrs = gr:connections(gr_uri, term.LINK_OUTBOUND)
+    -- Metadata
+    local attrs = gr:connections(s, term.LINK_OUTBOUND)
+    --require "debugger"()
     for p, ots in pairs(attrs) do
-        if ((mconf.attributes or {})[p.data] or {}).type == "rel" then
+        local fname = nsm.denormalize_uri(p.data)
+        p_label = ((mconf.properties or NT)[fname] or NT).label
+        -- RDF types are shown in in breadcrumbs.
+        if fname == "rdf:type" then goto skip
+        elseif ((mconf.properties or NT)[fname] or NT).type == "rel" then
             -- Relationship.
-            rel[p.data] = {}
-            for o in pairs(ots) do table.insert(dmd[p.data], o.data) end
-        elseif p.data == "pas:firstChild" then
-            -- Children.
-            for o in pairs(ots) do table.insert(children, o.data) end
-        elseif p.data == "pas:next" then
+            rel[fname] = {label = p_label, uri = fname}
+            for o in pairs(ots) do table.insert(dmd[fname], o.data) end
+        elseif fname == "pas:first" then
+            -- Build a linked list for every first found.
+            local p = term.new_iriref_ns("pas:next")
+            local dc_title = term.new_iriref_ns("dc:title")
+            local tn_p = term.new_iriref_ns("pas:thumbnail")
+            for o in pairs(ots) do
+                -- Loop through all first children.
+                local node_uri = o
+                logger:debug("local node_uri", node_uri.data)
+                local ll = {}
+                while node_uri do
+                    -- Loop trough all next nodes for each first child.
+                    local el_gr = graph.get(node_uri, pkar.store)
+                    table.insert(ll, {
+                        uri = node_uri,
+                        label = gr:attr(node_uri, dc_title)[1],
+                        tn = gr:attr(node_uri, tn_p)[1],
+                    })
+                    local next_attr = gr:attr(node_uri, p)
+                    node_uri = next(next_attr)  -- There can only be one "next"
+                end
+                table.insert(children, ll)
+            end
+        elseif fname == "pas:next" then
             -- Sibling.
             for o in pairs(ots) do ls_next = o.data break end
         else
             -- Descriptive metadata.
-            dmd[p.data] = {}
+            dmd[fname] = {label = p_label, uri = fname}
             -- TODO differentiate term types
-            for o in pairs(ots) do table.insert(dmd[p.data], o.data) end
+            for o in pairs(ots) do table.insert(dmd[fname], o.data) end
+            table.sort(dmd[fname])
         end
+        ::skip::
     end
     table.sort(dmd)
     table.sort(rel)
     table.sort(children)
-    print("DMD:")
-    pp.dump(dmd)
-    print("REL:")
-    pp.dump(rel)
-
-    out_html = res_tpl({
-        title = pkar.config.site.title or "Pocket Archive",
-        uri = gr_uri,
-        pkar = pkar,
+    logger:debug("Lineage:", pp.write(mconf.lineage))
+    logger:debug("DMD:", pp.write(dmd))
+    logger:debug("REL:", pp.write(rel))
+    logger:debug("Children:", pp.write(children))
+    logger:debug("Breadcrumbs:", pp.write(get_breadcrumbs(mconf)))
+
+    out_html = dres_tpl({
+        site_title = pkar.config.site.title or pkar.default_title,
+        title = ((dmd["dc:title"] or NT)[1] or NT) or s.data,
+        mconf = mconf,
+        uri = s,
         dmd = dmd,
         rel = rel,
         children = children,
         ls_next = ls_next,
+        breadcrumbs = get_breadcrumbs(mconf),
+        deliverable = deliverable,
     })
 
-    local res_id = gr_uri.data:gsub(pkar.nsm:get_ns("par"), "")
+    local res_id = s.data:gsub(nsm.get_ns("par"), "")
     local ofh = assert(io.open(string.format(
             "%s/%s.html", M.res_dir, res_id), "w"))
-    return ofh:write(out_html)
+    ofh:write(out_html)
+    ofh:close()
+end
+
+
+local function generate_ores(s, mconf)
+    local gr = graph.new(pkar.store, term.DEFAULT_CTX)
+
+    local techmd = {}
+    local rel = {}
+    -- Metadata
+    local attrs = gr:connections(s, term.LINK_OUTBOUND)
+    for p, ots in pairs(attrs) do
+        local fname = nsm.denormalize_uri(p.data)
+        p_label = ((mconf.properties or NT)[fname] or NT).label
+        -- RDF types are shown in in breadcrumbs.
+        if fname == "rdf:type" then goto skip
+        elseif ((mconf.properties or NT)[fname] or NT).type == "rel" then
+            -- Relationship.
+            rel[fname] = {label = p_label, uri = fname}
+            for o in pairs(ots) do table.insert(techmd[fname], o.data) end
+        elseif fname == "pas:next" then
+            -- Sibling.
+            for o in pairs(ots) do ls_next = o.data break end
+        else
+            -- Descriptive metadata.
+            techmd[fname] = {label = p_label, uri = fname}
+            -- TODO differentiate term types
+            for o in pairs(ots) do table.insert(techmd[fname], o.data) end
+            table.sort(techmd[fname])
+        end
+        ::skip::
+    end
+    table.sort(techmd)
+    table.sort(rel)
+    logger:debug("Lineage:", pp.write(mconf.lineage))
+    logger:debug("Breadcrumbs:", pp.write(get_breadcrumbs(mconf)))
+    logger:debug("techmd:", pp.write(techmd))
+    logger:debug("REL:", pp.write(rel))
+
+    --require "debugger"()
+    -- Transform and move media assets.
+    logger:info("Transforming resource file.")
+    local res_path = techmd["pas:path"]
+    if not res_path then error("No file path for File resource!") end
+    local txconf = (mconf.transformers or NT).deliverable or {fn = "copy"}
+    local dest_fname = plpath.basename(res_path[1])
+    if txconf.ext then
+        dest_fname = plpath.splitext(dest_fname) .. txconf.ext
+    end
+    dest = M.asset_dir .. "/" .. dest_fname
+    assert(transformers[txconf.fn](
+            res_path[1], dest, table.unpack(txconf or NT)))
+    local deliverable = dest:gsub(pkar.config.htmlgen.out_dir, "..")
+    logger:info("Deliverable: ", dest)
+
+    out_html = ores_tpl({
+        site_title = pkar.config.site.title or pkar.default_title,
+        fname = plpath.basename(techmd["pas:sourcePath"][1]),
+        mconf = mconf,
+        uri = s,
+        techmd = techmd,
+        rel = rel,
+        ls_next = ls_next,
+        breadcrumbs = get_breadcrumbs(mconf),
+        deliverable = deliverable,
+    })
+
+    local res_id = s.data:gsub(nsm.get_ns("par"), "")
+    local ofh = assert(io.open(string.format(
+            "%s/%s.html", M.res_dir, res_id), "w"))
+    ofh:write(out_html)
+    ofh:close()
+end
+
+
+M.generate_resource = function(s)
+    local gr = graph.new(pkar.store, term.DEFAULT_CTX)
+
+    local res_type
+    local type_attr = gr:attr(
+        s, term.new_iriref_ns("pas:contentType"))
+    res_type = next(type_attr).data
+    local mconf = model.models[res_type]
+
+    if mconf.types["pas:File"] then return generate_ores(s, mconf)
+    else return generate_dres(s, mconf) end
 end
 
 
 M.generate_resources = function()
-    -- TODO parallelize
+    dir.rmtree(M.res_dir)
     dir.makepath(M.res_dir)
-    local index_ts = graph.list(pkar.store)
-    for gr_uri in pairs(index_ts) do M.generate_resource(gr_uri) end
+    dir.rmtree(M.asset_dir)
+    dir.makepath(M.asset_dir)
+    dir.rmtree(M.media_dir)
+    dir.makepath(M.media_dir)
+    local gr = graph.new(pkar.store, term.DEFAULT_CTX)
+    local subjects = gr:unique_terms(triple.POS_S)
+
+    -- TODO parallelize
+    for s in pairs(subjects) do M.generate_resource(s) end
 end
 
 
 M.generate_idx = function()
     local obj_idx = {}
-    local index_ts = graph.list(pkar.store)
-    for gr_uri in pairs(index_ts) do
-        local obj = {}
-        local cur
-        gr = graph.get(gr_uri, pkar.store)
-        -- Get all subject of type: Artifact.
-        s_ts = gr:term_set(
-            term.new_iriref("rdf:type", pkar.nsm), triple.POS_P,
-            term.new_iriref("pas:Artifact", pkar.nsm), triple.POS_O
-        )
-        for s in pairs(s_ts) do
-            for trp in gr:lookup(s, term.new_iriref("dc:title", pkar.nsm)) do
-                obj.title = trp.o
-                break  -- Only one value to use
-            end
-        end
-        if obj.title then obj_idx[gr_uri.data] = obj end
+    local gr = graph.new(pkar.store, term.DEFAULT_CTX)
+    -- Get all subject of type: Artifact.
+    s_ts = gr:term_set(
+        pkar.RDF_TYPE, triple.POS_P,
+        term.new_iriref_ns("pas:Artifact"), triple.POS_O
+    )
+    for s in pairs(s_ts) do
+        local s_label = nsm.denormalize_uri(s.data)
+        local titles = gr:attr(s, term.new_iriref_ns("dc:title"))
+        local obj = {title = next(titles)}
+        if obj.title then obj_idx[s_label] = obj end
     end
 
-    pp.dump(obj_idx)
+    logger:debug(pp.write(obj_idx))
     out_html = idx_tpl({
-        title = pkar.config.site.title or "Pocket Archive",
-        pkar = pkar,
+        title = pkar.config.site.title or pkar.default_title,
+        nsm = nsm,
         obj_idx = obj_idx,
     })
 
-    local ofh = assert(io.open(
-            pkar.config.htmlgen.out_dir .. "/index.html", "w"))
+    local idx_path = pkar.config.htmlgen.out_dir .. "/index.html"
+    local ofh = assert(io.open(idx_path, "w"))
 
-    return ofh:write(out_html)
+    logger:debug("Writing info at ", idx_path)
+    ofh:write(out_html)
+    ofh:close()
 end
 
 

+ 6 - 7
src/model.lua

@@ -36,13 +36,12 @@ M.parse_model = function(mod_id)
     end
     traverse(mod_id)
 
-    local lineage = {}  -- Config ID lineage, from ancestor to leaf.
-    for _, mod in ipairs(hierarchy) do table.insert(lineage, mod.id) end
-    --[[
-    for k, v in ipairs(hierarchy) do 
-        for kk, vv in pairs(v) do print(k, kk, vv) end
+    local lineage = {}  -- Ordered lineage of types, from ancestor to leaf.
+    local types = {}  -- Set of all types.
+    for _, mod in ipairs(hierarchy) do
+        table.insert(lineage, mod.id)
+        types[mod.id] = true
     end
-    --]]
 
     local function merge(src, dest)
         for k, v in pairs(src) do
@@ -57,7 +56,7 @@ M.parse_model = function(mod_id)
             ::continue::
         end
     end
-    local config = {lineage = lineage}
+    local config = {lineage = lineage, types = types}
 
     for _, src_config in ipairs(hierarchy) do
         merge(src_config, config)

+ 62 - 37
src/submission.lua

@@ -2,6 +2,7 @@ local io = io
 
 local csv = require "csv"
 local dir = require "pl.dir"
+local libmagic = require "libmagic"
 local plpath = require "pl.path"
 local pp = require "pl.pretty"
 
@@ -14,6 +15,11 @@ local model = require "pocket_archive.model"
 local pkar = require "pocket_archive"
 
 
+local logger = pkar.logger
+
+-- "nil" table - for missing key fallback in chaining.
+local NT = {}
+
 local M = {}  -- Submission module
 
 -- Adapted from lua-núcleo
@@ -37,6 +43,10 @@ local function escape_pattern(s)
     return (s:gsub(".", matches))
 end
 
+-- Initialize libmagic database.
+local magic = libmagic.open( libmagic.MIME_TYPE, libmagic.NO_CHECK_COMPRESS )
+assert(magic:load())
+
 
 -- For idgen(). Makes a 60-character pool with ~5.9 bits of entropy per char.
 local chpool = {}
@@ -141,16 +151,16 @@ M.generate_sip_v1 = function(path)
     -- Infer structure from paths and row ordering.
     for i, v in ipairs(mdlist) do
         for j = i + 1, #mdlist do
-            --print(string.format("comparing %s : %s", v.path, mdlist[j].path))
+            --print(string.format("comparing %s : %s", v["pas:sourcePath"], mdlist[j]["pas:sourcePath"]))
             if not v["next"] and
-                    mdlist[j].path:match("(.*/)") == v.path:match("(.*/)") then
+                    mdlist[j]["pas:sourcePath"]:match("(.*/)") == v["pas:sourcePath"]:match("(.*/)") then
                 --print("next match.")
-                v["next"] = mdlist[j].path
+                v["next"] = mdlist[j]["pas:sourcePath"]
             end
-            if not v.firstChild and
-                    mdlist[j].path:match("^" .. escape_pattern(v.path)) then
+            if not v.first and
+                    mdlist[j]["pas:sourcePath"]:match("^" .. escape_pattern(v["pas:sourcePath"])) then
                 --print("First child match.")
-                v.firstChild = mdlist[j].path
+                v.first = mdlist[j]["pas:sourcePath"]
             end
         end
         v._sort = nil
@@ -170,9 +180,9 @@ M.generate_sip_v2 = function(path)
     local i = 1
     for row in sub_data:lines() do
         print("Processing row: " .. i)
-        print("Row path: " .. row["path"])
-        if row["path"] ~= "" then
-            prev_path = row["path"]
+        print("Row path: " .. row["pas:sourcePath"])
+        if row["pas:sourcePath"] ~= "" then
+            prev_path = row["pas:sourcePath"]
             -- New row.
             sip[i] = {id = M.idgen()}
             for k, v in pairs(row) do
@@ -189,7 +199,7 @@ M.generate_sip_v2 = function(path)
             elseif not prev_path then
                 error(("No path information at row %d"):format(i), 2)
             else
-                row.path = prev_path
+                row["pas:sourcePath"] = prev_path
                 for k, v in pairs(row) do
                     if v == "" then goto cont2 end  -- skip empty strings.
                     if pkar.config.md.single_values[k] then
@@ -208,16 +218,17 @@ M.generate_sip_v2 = function(path)
     -- Infer structure from paths and row ordering.
     for i, v in ipairs(sip) do
         for j = i + 1, #sip do
-            --print(string.format("comparing %s : %s", v.path, sip[j].path))
+            --print(string.format("comparing %s : %s", v["pas:sourcePath"], sip[j]["pas:sourcePath"]))
             if not v["pas:next"] and
-                    sip[j].path:match("(.*/)") == v.path:match("(.*/)") then
+                    sip[j]["pas:sourcePath"]:match("(.*/)") ==
+                            v["pas:sourcePath"]:match("(.*/)") then
                 --print("next match.")
-                v["pas:next"] = sip[j].path
+                v["pas:next"] = "pas:" .. sip[j].id
             end
-            if not v["pas:firstChild"] and
-                    sip[j].path:match("^" .. escape_pattern(v.path)) then
+            if not v["pas:first"] and
+                    sip[j]["pas:sourcePath"]:match("^" .. escape_pattern(v["pas:sourcePath"])) then
                 --print("First child match.")
-                v["pas:firstChild"] = sip[j].path
+                v["pas:first"] = "pas:" .. sip[j].id
             end
         end
         v._sort = nil
@@ -240,32 +251,42 @@ M.update_rsrc_md = function(rsrc)
     rmod = model.parse_model(rsrc["pas:contentType"])
     triples = {}
 
-    local s = term.new_iriref("par:" .. rsrc.id, pkar.nsm)
-    gr = graph.new(pkar.store, s.data, pkar.nsm)
+    gr = graph.new(pkar.store, term.DEFAULT_CTX)
+    local s = term.new_iriref_ns("par:" .. rsrc.id)
     rsrc.id = nil  -- Exclude from metadata scan.
     for k, v in pairs(rsrc) do
         print("Adding attribute:", k, v)
-        local p = term.new_iriref(k, pkar.nsm)
+        local p = term.new_iriref_ns(k)
+        local o
         if type(v) == "table" then
             for vv, _ in pairs(v) do
-                table.insert(triples, triple.new(s, p, term.new_lit(vv)))
+                if ((rmod.properties or NT)[k] or NT).type == "rel" then
+                    o = term.new_iriref_ns(vv)
+                else o = term.new_lit(vv) end
+                table.insert(triples, triple.new(s, p, o))
             end
-        else table.insert(triples, triple.new(s, p, term.new_lit(v))) end
+        else
+            if ((rmod.properties or NT)[k] or NT).type == "rel" then
+                o = term.new_iriref_ns(v)
+            else o = term.new_lit(v) end
+            table.insert(triples, triple.new(s, p, o))
+        end
     end
     for i, m in ipairs(rmod.lineage) do
-        table.insert(triples, triple.new(
-            s, term.new_iriref("rdf:type", pkar.nsm),
-            term.new_iriref(m)
-        ))
+        table.insert(
+            triples, triple.new(s, pkar.RDF_TYPE, term.new_iriref_ns(m)))
     end
 
     -- This is a full replacement.
-    print("Removing triples.")
-    gr:remove();
-    print("Adding triples.")
+    --require "debugger"()
+    logger:info("Removing triples.")
+    gr:remove(s)
+    logger:info("Adding triples.")
     -- TODO implement lsup_lua fn to add a single triple and add triples in
     -- the previous loop.
     gr:add(triples)
+    logger:debug("Graph: ", tostring(gr))
+    for trp in gr:lookup() do logger:debug(tostring(trp)) end
 end
 
 
@@ -274,21 +295,25 @@ M.deposit = function(sip)
         -- TODO Wrap this chunk into a txn. Each row is atomic.
         print(("Processing resource #%d of %d: %s"):format(i, #sip, rsrc.id))
 
-        in_path = sip.root_path .. rsrc.path
+        local in_path = sip.root_path .. rsrc["pas:sourcePath"]
+        local fext = plpath.extension(in_path)
         -- If it's a directory, skip file processing.
         if not plpath.isfile(in_path) then goto continue end
 
         do
-            tmp_dir = pkar.config.fs.ores_path .. "tmp/"
-            local tmp_path = tmp_dir .. rsrc.id
+            local tmp_dir = pkar.config.fs.ores_path .. "tmp/"
+            local file_ext
+            _, file_ext = plpath.splitext(in_path)
+            local tmp_path = tmp_dir .. rsrc.id .. file_ext
             dir.makepath(tmp_dir)
 
             local ifh = assert(io.open(in_path, "r"))
-            local ofh = assert(io.open(tmp_path, "w"))
 
+            rsrc["dc:format"] = {[magic:filehandle(ifh)] = true}
             local hash_it = mc.new_blake2b()
             local fsize = 0
             print(("Hashing %s"):format(in_path))
+            local ofh = assert(io.open(tmp_path, "w"))
             while true do
                 chunk = ifh:read(pkar.config.fs.stream_chunk_size)
                 if not chunk then break end
@@ -303,15 +328,15 @@ M.deposit = function(sip)
             ofh:close()
             ifh:close()
 
-            out_dir = ("%s%s/%s/"):format(
+            local out_dir = ("%s%s/%s/"):format(
                     pkar.config.fs.ores_path,
                     checksum:sub(1, 4),
                     checksum:sub(5, 9))
-            out_path = out_dir .. checksum:sub(1,32)
-            rsrc.path = out_path
+            local out_path = out_dir .. checksum:sub(1,32) .. fext
             dir.makepath(out_dir)
-            print(("Moving file %s to %s"):format(tmp_path, rsrc.path))
-            dir.movefile(tmp_path, rsrc.path)
+            print(("Moving file %s to %s"):format(tmp_path, out_path))
+            dir.movefile(tmp_path, out_path)
+            rsrc["pas:path"] = out_path
         end
 
         ::continue::

+ 35 - 0
src/transformers.lua

@@ -0,0 +1,35 @@
+local vips = require "vips"
+
+
+M = {}
+
+
+-- Resize an image to a maximum size on either dimension.
+M.img_resize = function(src, dest, size)
+    print(("Resizing image %s with size %d to %s"):format(src, size, dest))
+    local img = vips.Image.thumbnail(src, size)
+
+    img:write_to_file(dest)
+
+    return true
+end
+
+
+-- Straight copy with no transformation.
+M.copy = function(src, dest)
+    local ifh = assert(io.open(src, "r"))
+    local ofh = assert(io.open(dest, "w"))
+
+    while true do
+        chunk = ifh:read(pkar.config.fs.stream_chunk_size)
+        if not chunk then break end
+        ofh:write(chunk)
+    end
+    ifh:close()
+    ofh:close()
+
+    return true
+end
+
+
+return M

+ 84 - 0
templates/dres.html

@@ -0,0 +1,84 @@
+<!DOCTYPE html>
+<html>
+    <head>
+        <title>
+            <%= title %>&emsp;&#x2741;&emsp;<%= site_title %>
+        </title>
+    </head>
+    <body>
+        <header>
+            <h1><%= breadcrumbs[#breadcrumbs][2] %>&ensp;:&ensp;<%= title %></h1>
+        </header>
+        <main>
+            <section id="res_lineage">
+                <h2>Classification</h2>
+                <p>
+                <% for i, bc in ipairs(breadcrumbs) do %>
+                    <% cls, label = table.unpack(bc) %>
+                    <% if i ~= 1 then %>
+                    <span class="breadcrump_sep">&ensp;&#x27B3;&ensp;</span>
+                    <% end %>
+                    <a
+                        class="breadcrumb<%if i == #breadcrumbs then %> current<% end %>"
+                        href="../class/<%= cls -%>">
+                        <%= label -%>
+                    </a>
+                <% end %>
+                </p>
+            </section>
+            <section id="res_dmd">
+                <h2>Metadata</h2>
+                <dl class="res_md">
+                <% for p, ol in pairs(dmd) do %>
+                    <dt>
+                        <% if ol.label then %><%= ol.label %>
+                        <% else %><code><%= ol.uri %></code><% end %>
+                    </dt>
+                    <% for _, o in ipairs(ol) do %>
+                      <dd><%= o %></dd>
+                    <% end %>
+                <% end %>
+                </dl>
+            </section>
+            <% if #rel > 0 then %>
+            <section id="res_rel">
+                <h2>Relationships</h2>
+                <dl class="res_md">
+                <% for p, ol in pairs(rel) do %>
+                    <dt>
+                        <% if ol.label then %><%= ol.label %>
+                        <% else %><code><%= ol.uri %></code><% end %>
+                    </dt>
+                    <% for _, o in ipairs(ol) do %>
+                        <dd><a href="res/<%= o.data %>.html"><%= o.data %></a></dd>
+                    <% end %>
+                <% end %>
+                </dl>
+            </section>
+            <% end %>
+            <% if #children > 0 then %>
+            <section id="res_children">
+                <h2>Contains</h2>
+                <ul>
+                <% for _, ll in ipairs(children) do %>
+                    <li><ol>
+                    <% for i, el in ipairs(ll) do %>
+                        <li><a href="<%= el.uri %>"><%= el.label %></a></li>
+                    <%end %>
+                    </ol></li>
+                <% end %>
+                </ul>
+            </section>
+            <% end %>
+            <% if deliverable then %>
+            <section id="res_pres">
+            <% if mconf.presentation_type == "image" then %>
+                <img src="<%= deliverable -%>" />
+            <% end %>
+            </section>
+            <% end %>
+        </main>
+        <footer></footer>
+    </body>
+</html>
+

+ 1 - 1
templates/index.html

@@ -13,7 +13,7 @@
                 <ul>
                 <% for uri, data in pairs(obj_idx) do %>
                     <li class="obj_link">
-                        <a href="<%= uri:gsub(pkar.nsm:get_ns('par'), 'res/') %>.html">
+                        <a href="<%= uri:gsub('par:', 'res/') %>.html">
                             <%= data.title.data %>
                             <%if data.title.lang then %>
                                 <span class="langtag"><%= data.title.lang %></span>

+ 68 - 0
templates/ores.html

@@ -0,0 +1,68 @@
+<!DOCTYPE html>
+<html>
+    <head>
+        <title><%= title %>&emsp;&#x2741;&emsp;<%= fname %></title>
+    </head>
+    <body>
+        <header>
+            <h1><%= breadcrumbs[#breadcrumbs][2] -%>:&nbsp;<%= fname %></h1>
+        </header>
+        <main>
+            <section id="res_lineage">
+                <h2>Classification</h2>
+                <p>
+                <% for i, bc in ipairs(breadcrumbs) do %>
+                    <% cls, label = table.unpack(bc) %>
+                    <% if i ~= 1 then %>
+                    <span class="breadcrump_sep">&ensp;&#x27B3;&ensp;</span>
+                    <% end %>
+                    <a
+                        class="breadcrumb<%if i == #breadcrumbs then %> current<% end %>"
+                        href="../class/<%= cls -%>">
+                        <%= label -%>
+                    </a>
+                <% end %>
+                </p>
+            </section>
+            <section class="res_md" id="res_techmd">
+                <h2>Metadata</h2>
+                <dl>
+                <% for p, ol in pairs(techmd) do %>
+                    <dt>
+                        <% if ol.label then %><%= ol.label %>
+                        <% else %><code><%= ol.uri %></code><% end %>
+                    </dt>
+                    <% for _, o in ipairs(ol) do %>
+                      <dd><%= o %></dd>
+                    <% end %>
+                <% end %>
+                </dl>
+            </section>
+            <% if #rel > 0 then %>
+            <section class="res_md" id="res_rel">
+                <h2>Relationships</h2>
+                <dl>
+                <% for p, ol in pairs(rel) do %>
+                    <dt>
+                        <% if ol.label then %><%= ol.label %>
+                        <% else %><code><%= ol.uri %></code><% end %>
+                    </dt>
+                    <% for _, o in ipairs(ol) do %>
+                        <dd><a href="res/<%= o.data %>.html"><%= o.data %></a></dd>
+                    <% end %>
+                <% end %>
+                </dl>
+            </section>
+            <% end %>
+            <% if deliverable then %>
+            <section id="res_pres">
+            <% if mconf.presentation_type == "image" then %>
+                <img src="<%= deliverable -%>" />
+            <% end %>
+            </section>
+            <% end %>
+        </main>
+        <footer></footer>
+    </body>
+</html>
+

+ 0 - 53
templates/res.html

@@ -1,53 +0,0 @@
-<!DOCTYPE html>
-<html>
-    <head>
-        <title>
-            <%= title %>&emsp;❁&emsp;<%= ((dmd["dc:title"] or {})[1] or {}).data or uri.data %>
-        </title>
-    </head>
-    <body>
-        <header>
-            <h1>
-                <%= ((dmd["dc:title"] or {})[1] or {}).data or uri.data %>
-            </h1>
-        </header>
-        <main>
-            <section id="res_dmd">
-                <h2>Metadata</h2>
-                <dl class="res_md">
-                <% for p, ol in pairs(dmd) do %>
-                    <dt><%= p %></dt>
-                    <% for _, o in ipairs(ol) do %>
-                      <dd><%= o %></dd>
-                    <% end %>
-                <% end %>
-                </dl>
-            </section>
-            <% if #rel then %>
-            <section id="res_rel">
-                <h2>Relationships</h2>
-                <dl class="res_md">
-                <% for p, ol in pairs(rel) do %>
-                    <dt><%= p.data %></dt>
-                    <% for _, o in ipairs(ol) do %>
-                        <dd><a href="res/<%= o.data %>.html"><%= o.data %></a></dd>
-                    <% end %>
-                <% end %>
-                </dl>
-            </section>
-            <% end %>
-            <% if #children then %>
-            <section id="res_children">
-                <h2>Children</h2>
-                <ul>
-                <% for _, child in ipairs(children) do %>
-                    <li><a href = "<%= child %>"><%= child %></li>
-                <% end %>
-                </ul>
-            </section>
-            <% end %>
-        </main>
-        <footer></footer>
-    </body>
-</html>
-

+ 1 - 1
test/sample_submission/postcard-bag/data/submission-v2.csv

@@ -1,4 +1,4 @@
-"path","dc:identifier","pas:contentType","dc:title","dc:alternative","dc:description"
+"pas:sourcePath","dc:identifier","pas:contentType","dc:title","dc:alternative","dc:description"
 0001,0001,"Postcard","Example Postcard","This is an alternative label","Note that recto and verso representations have been named front and back, to emphasize that the ordering is not alphabetical."
 ,,,,"And this is another alternative label","Second description."
 ,,,,"Yet another alt label.",