Browse Source

Separate DRes and ORes rendering; rearrange metadata.

scossu 2 weeks ago
parent
commit
814c55f82d

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

@@ -5,43 +5,62 @@ return {
     abstract = "true",
     abstract = "true",
 
 
     properties = {
     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"] = {
         ["pas:id"] = {
             label = "Primary ID",
             label = "Primary ID",
             type = "string",
             type = "string",
             min_cardinality = 1,
             min_cardinality = 1,
             max_cardinality = 1,
             max_cardinality = 1,
         },
         },
-        ["pas:refId"] = {
+        ["dc:identifier"] = {
             label = "External system ID",
             label = "External system ID",
             type = "string",
             type = "string",
         },
         },
-        ["pas:prefLabel"] = {
-            label = "Preferred Label",
+        ["dc:title"] = {
+            label = "Title",
             type = "string",
             type = "string",
             max_cardinality = 1,
             max_cardinality = 1,
         },
         },
-        ["pas:altLabel"] = {
+        ["dc:alternative"] = {
             label = "Alternative Label",
             label = "Alternative Label",
             type = "string",
             type = "string",
         },
         },
-        ["pas:created"] = {
+        ["dc:description"] = {
+            label = "Description",
+            type = "string",
+        },
+        ["dc:created"] = {
             label = "Created On",
             label = "Created On",
             type = "timestamp",
             type = "timestamp",
             min_cardinality = 1,
             min_cardinality = 1,
             max_cardinality = 1,
             max_cardinality = 1,
         },
         },
-        ["pas:updated"] = {
+        ["dc:modified"] = {
             label = "Last Updated On",
             label = "Last Updated On",
             type = "timestamp",
             type = "timestamp",
             min_cardinality = 1,
             min_cardinality = 1,
             max_cardinality = 1,
             max_cardinality = 1,
         },
         },
-        ["pas:createdBy"] = {
+        ["dc:creator"] = {
             label = "Created By",
             label = "Created By",
             type = "rel",
             type = "rel",
             range = {["pas:Agent"] = true},
             range = {["pas:Agent"] = true},
         },
         },
-        ["pas:updatedBy"] = {
+        ["dc:contributor"] = {
             label = "Last Updated By",
             label = "Last Updated By",
             type = "rel",
             type = "rel",
             range = {["pas:Agent"] = true},
             range = {["pas:Agent"] = true},

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

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

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

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

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

@@ -1,5 +1,15 @@
 return {
 return {
-    id = "dctype:StillImage",
+    id = "pas:StillImageFile",
     label = "Still Image File",
     label = "Still Image File",
     broader = "pas: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",
    "csv",
    "datafile",
    "datafile",
    "etlua",
    "etlua",
-   "lyaml",
+   "lua-vips",
    "penlight",
    "penlight",
+   "sllog",
 }
 }
 build = {
 build = {
     type = "builtin",
     type = "builtin",
@@ -33,6 +34,7 @@ build = {
         ["pocket_archive.model"] = "src/model.lua",
         ["pocket_archive.model"] = "src/model.lua",
         ["pocket_archive.submission"] = "src/submission.lua",
         ["pocket_archive.submission"] = "src/submission.lua",
         ["pocket_archive.html_generator"] = "src/html_generator.lua",
         ["pocket_archive.html_generator"] = "src/html_generator.lua",
+        ["pocket_archive.transformers"] = "src/transformers.lua",
         ["pocket_archive.monocypher"] = {
         ["pocket_archive.monocypher"] = {
             "ext/monocypher/monocypher.c",
             "ext/monocypher/monocypher.c",
             "ext/monocypher/lua_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 root_path = path.dirname(path.dirname(fpath))
 local config_path = os.getenv("PA_CONFIG_DIR") or (root_path .. "/config")
 local config_path = os.getenv("PA_CONFIG_DIR") or (root_path .. "/config")
 
 
+
 local M = {
 local M = {
     root = root_path,
     root = root_path,
     config = dofile(config_path .. "/app.lua"),
     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)
 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))
 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.
 -- Initialize random ID generator.
 math.randomseed(M.config.id.seed[1], M.config.id.seed[2])
 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 datafile = require "datafile"
 local dir = require "pl.dir"
 local dir = require "pl.dir"
 local etlua = require "etlua"
 local etlua = require "etlua"
+local plpath = require "pl.path"
 local pp = require "pl.pretty"
 local pp = require "pl.pretty"
 
 
+local nsm = require "lsup.namespace"
 local term = require "lsup.term"
 local term = require "lsup.term"
 local triple = require "lsup.triple"
 local triple = require "lsup.triple"
 local graph = require "lsup.graph"
 local graph = require "lsup.graph"
 
 
 local pkar = require "pocket_archive"
 local pkar = require "pocket_archive"
+local logger = pkar.logger
 local model = require "pocket_archive.model"
 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.
 -- Compile all templates once.
 -- TODO Add override for user-maintained templates.
 -- 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")
 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: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()
 fh:close()
 
 
 
 
+-- HTML generator module.
 local M = {
 local M = {
     res_dir = pkar.config.htmlgen.out_dir .. "/res",
     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 dmd = {}
     local rel = {}
     local rel = {}
     local children = {}
     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
     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.
             -- 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.
             -- Sibling.
             for o in pairs(ots) do ls_next = o.data break end
             for o in pairs(ots) do ls_next = o.data break end
         else
         else
             -- Descriptive metadata.
             -- Descriptive metadata.
-            dmd[p.data] = {}
+            dmd[fname] = {label = p_label, uri = fname}
             -- TODO differentiate term types
             -- 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
         end
+        ::skip::
     end
     end
     table.sort(dmd)
     table.sort(dmd)
     table.sort(rel)
     table.sort(rel)
     table.sort(children)
     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,
         dmd = dmd,
         rel = rel,
         rel = rel,
         children = children,
         children = children,
         ls_next = ls_next,
         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(
     local ofh = assert(io.open(string.format(
             "%s/%s.html", M.res_dir, res_id), "w"))
             "%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
 end
 
 
 
 
 M.generate_resources = function()
 M.generate_resources = function()
-    -- TODO parallelize
+    dir.rmtree(M.res_dir)
     dir.makepath(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
 end
 
 
 
 
 M.generate_idx = function()
 M.generate_idx = function()
     local obj_idx = {}
     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
     end
 
 
-    pp.dump(obj_idx)
+    logger:debug(pp.write(obj_idx))
     out_html = idx_tpl({
     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,
         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
 end
 
 
 
 

+ 6 - 7
src/model.lua

@@ -36,13 +36,12 @@ M.parse_model = function(mod_id)
     end
     end
     traverse(mod_id)
     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
     end
-    --]]
 
 
     local function merge(src, dest)
     local function merge(src, dest)
         for k, v in pairs(src) do
         for k, v in pairs(src) do
@@ -57,7 +56,7 @@ M.parse_model = function(mod_id)
             ::continue::
             ::continue::
         end
         end
     end
     end
-    local config = {lineage = lineage}
+    local config = {lineage = lineage, types = types}
 
 
     for _, src_config in ipairs(hierarchy) do
     for _, src_config in ipairs(hierarchy) do
         merge(src_config, config)
         merge(src_config, config)

+ 62 - 37
src/submission.lua

@@ -2,6 +2,7 @@ local io = io
 
 
 local csv = require "csv"
 local csv = require "csv"
 local dir = require "pl.dir"
 local dir = require "pl.dir"
+local libmagic = require "libmagic"
 local plpath = require "pl.path"
 local plpath = require "pl.path"
 local pp = require "pl.pretty"
 local pp = require "pl.pretty"
 
 
@@ -14,6 +15,11 @@ local model = require "pocket_archive.model"
 local pkar = require "pocket_archive"
 local pkar = require "pocket_archive"
 
 
 
 
+local logger = pkar.logger
+
+-- "nil" table - for missing key fallback in chaining.
+local NT = {}
+
 local M = {}  -- Submission module
 local M = {}  -- Submission module
 
 
 -- Adapted from lua-núcleo
 -- Adapted from lua-núcleo
@@ -37,6 +43,10 @@ local function escape_pattern(s)
     return (s:gsub(".", matches))
     return (s:gsub(".", matches))
 end
 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.
 -- For idgen(). Makes a 60-character pool with ~5.9 bits of entropy per char.
 local chpool = {}
 local chpool = {}
@@ -141,16 +151,16 @@ M.generate_sip_v1 = function(path)
     -- Infer structure from paths and row ordering.
     -- Infer structure from paths and row ordering.
     for i, v in ipairs(mdlist) do
     for i, v in ipairs(mdlist) do
         for j = i + 1, #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
             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.")
                 --print("next match.")
-                v["next"] = mdlist[j].path
+                v["next"] = mdlist[j]["pas:sourcePath"]
             end
             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.")
                 --print("First child match.")
-                v.firstChild = mdlist[j].path
+                v.first = mdlist[j]["pas:sourcePath"]
             end
             end
         end
         end
         v._sort = nil
         v._sort = nil
@@ -170,9 +180,9 @@ M.generate_sip_v2 = function(path)
     local i = 1
     local i = 1
     for row in sub_data:lines() do
     for row in sub_data:lines() do
         print("Processing row: " .. i)
         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.
             -- New row.
             sip[i] = {id = M.idgen()}
             sip[i] = {id = M.idgen()}
             for k, v in pairs(row) do
             for k, v in pairs(row) do
@@ -189,7 +199,7 @@ M.generate_sip_v2 = function(path)
             elseif not prev_path then
             elseif not prev_path then
                 error(("No path information at row %d"):format(i), 2)
                 error(("No path information at row %d"):format(i), 2)
             else
             else
-                row.path = prev_path
+                row["pas:sourcePath"] = prev_path
                 for k, v in pairs(row) do
                 for k, v in pairs(row) do
                     if v == "" then goto cont2 end  -- skip empty strings.
                     if v == "" then goto cont2 end  -- skip empty strings.
                     if pkar.config.md.single_values[k] then
                     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.
     -- Infer structure from paths and row ordering.
     for i, v in ipairs(sip) do
     for i, v in ipairs(sip) do
         for j = i + 1, #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
             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.")
                 --print("next match.")
-                v["pas:next"] = sip[j].path
+                v["pas:next"] = "pas:" .. sip[j].id
             end
             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.")
                 --print("First child match.")
-                v["pas:firstChild"] = sip[j].path
+                v["pas:first"] = "pas:" .. sip[j].id
             end
             end
         end
         end
         v._sort = nil
         v._sort = nil
@@ -240,32 +251,42 @@ M.update_rsrc_md = function(rsrc)
     rmod = model.parse_model(rsrc["pas:contentType"])
     rmod = model.parse_model(rsrc["pas:contentType"])
     triples = {}
     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.
     rsrc.id = nil  -- Exclude from metadata scan.
     for k, v in pairs(rsrc) do
     for k, v in pairs(rsrc) do
         print("Adding attribute:", k, v)
         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
         if type(v) == "table" then
             for vv, _ in pairs(v) do
             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
             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
     end
     for i, m in ipairs(rmod.lineage) do
     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
     end
 
 
     -- This is a full replacement.
     -- 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
     -- TODO implement lsup_lua fn to add a single triple and add triples in
     -- the previous loop.
     -- the previous loop.
     gr:add(triples)
     gr:add(triples)
+    logger:debug("Graph: ", tostring(gr))
+    for trp in gr:lookup() do logger:debug(tostring(trp)) end
 end
 end
 
 
 
 
@@ -274,21 +295,25 @@ M.deposit = function(sip)
         -- TODO Wrap this chunk into a txn. Each row is atomic.
         -- TODO Wrap this chunk into a txn. Each row is atomic.
         print(("Processing resource #%d of %d: %s"):format(i, #sip, rsrc.id))
         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 it's a directory, skip file processing.
         if not plpath.isfile(in_path) then goto continue end
         if not plpath.isfile(in_path) then goto continue end
 
 
         do
         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)
             dir.makepath(tmp_dir)
 
 
             local ifh = assert(io.open(in_path, "r"))
             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 hash_it = mc.new_blake2b()
             local fsize = 0
             local fsize = 0
             print(("Hashing %s"):format(in_path))
             print(("Hashing %s"):format(in_path))
+            local ofh = assert(io.open(tmp_path, "w"))
             while true do
             while true do
                 chunk = ifh:read(pkar.config.fs.stream_chunk_size)
                 chunk = ifh:read(pkar.config.fs.stream_chunk_size)
                 if not chunk then break end
                 if not chunk then break end
@@ -303,15 +328,15 @@ M.deposit = function(sip)
             ofh:close()
             ofh:close()
             ifh:close()
             ifh:close()
 
 
-            out_dir = ("%s%s/%s/"):format(
+            local out_dir = ("%s%s/%s/"):format(
                     pkar.config.fs.ores_path,
                     pkar.config.fs.ores_path,
                     checksum:sub(1, 4),
                     checksum:sub(1, 4),
                     checksum:sub(5, 9))
                     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)
             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
         end
 
 
         ::continue::
         ::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>
                 <ul>
                 <% for uri, data in pairs(obj_idx) do %>
                 <% for uri, data in pairs(obj_idx) do %>
                     <li class="obj_link">
                     <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 %>
                             <%= data.title.data %>
                             <%if data.title.lang then %>
                             <%if data.title.lang then %>
                                 <span class="langtag"><%= data.title.lang %></span>
                                 <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."
 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."
 ,,,,"And this is another alternative label","Second description."
 ,,,,"Yet another alt label.",
 ,,,,"Yet another alt label.",