Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
48 commits
Select commit Hold shift + click to select a range
76e9d08
Some basic infrastructure
kjetilk Oct 30, 2018
a162ef4
Reading from file works
kjetilk Oct 31, 2018
c4e59cb
Test for non-existant file
kjetilk Oct 31, 2018
423297b
Set no quota if the predicate isn't there
kjetilk Oct 31, 2018
fb35d01
Introduce get-folder-size dependency
kjetilk Oct 31, 2018
b3298b5
Not-yet working code for over quota
kjetilk Oct 31, 2018
0f9c04d
turn the signature
kjetilk Oct 31, 2018
38c023e
Improve the callback async handling, thanks @megoth
kjetilk Oct 31, 2018
97d81eb
Make the quota functions async
kjetilk Nov 5, 2018
2b6a272
Still failing async tests
kjetilk Nov 5, 2018
0b02130
Fix the tests to async, thanks @megoth
kjetilk Nov 5, 2018
889ed4a
Comment on the implementation
kjetilk Nov 5, 2018
2ebd8ad
Add a negative test
kjetilk Nov 5, 2018
fe9581c
Make actualSize function part of the public API
kjetilk Nov 5, 2018
2cc260c
Improve documentation somewhat
kjetilk Nov 5, 2018
3d30cc4
add cache TODO
kjetilk Nov 5, 2018
22b21f7
Some basic infrastructure
kjetilk Oct 30, 2018
58a33e4
Reading from file works
kjetilk Oct 31, 2018
3383003
Test for non-existant file
kjetilk Oct 31, 2018
383b849
Set no quota if the predicate isn't there
kjetilk Oct 31, 2018
a5c968f
Introduce get-folder-size dependency
kjetilk Oct 31, 2018
2ec4905
Not-yet working code for over quota
kjetilk Oct 31, 2018
53fe1b1
turn the signature
kjetilk Oct 31, 2018
2c51be3
Improve the callback async handling, thanks @megoth
kjetilk Oct 31, 2018
7dbaa1e
Make the quota functions async
kjetilk Nov 5, 2018
4d09c2e
Still failing async tests
kjetilk Nov 5, 2018
790e8ea
Fix the tests to async, thanks @megoth
kjetilk Nov 5, 2018
1a54eb8
Comment on the implementation
kjetilk Nov 5, 2018
50cb63f
Add a negative test
kjetilk Nov 5, 2018
8b42908
Make actualSize function part of the public API
kjetilk Nov 5, 2018
43be6a1
Improve documentation somewhat
kjetilk Nov 5, 2018
34aa3bf
add cache TODO
kjetilk Nov 5, 2018
4925cef
Merge branch 'feature/check-quota' of github.com:solid/node-solid-ser…
kjetilk Nov 6, 2018
6378213
Failing LDP tests
kjetilk Nov 7, 2018
1b38a86
Must use callback
megoth Nov 7, 2018
8f082e9
Comment out my own tests
kjetilk Nov 7, 2018
0eec430
Check quota in PATCH too
kjetilk Nov 7, 2018
a5516b1
more docs
kjetilk Nov 7, 2018
c5082fa
improve wording
kjetilk Nov 7, 2018
a99a0ee
rename other to fs for debugging
kjetilk Nov 7, 2018
9f171d7
improve test description
kjetilk Nov 7, 2018
7423905
skip failing tests
kjetilk Nov 7, 2018
2327cd1
skip another test
kjetilk Nov 8, 2018
e981aa5
Merge branch 'master' into feature/check-quota
kjetilk Nov 8, 2018
7b2f773
Remove byte units and the logic to support it. Also use anyValue method
kjetilk Nov 15, 2018
3dcddce
Remove a console.log
kjetilk Nov 15, 2018
4deae1e
Merge branch 'master' into feature/check-quota
kjetilk Nov 20, 2018
a5a9617
Update package.json
kjetilk Nov 20, 2018
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions default-templates/new-account/settings/serverSide.ttl
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
@prefix dct: <http://purl.org/dc/terms/>.
@prefix pim: <http://www.w3.org/ns/pim/space#>.
@prefix solid: <http://www.w3.org/ns/solid/terms#>.
@prefix unit: <http://www.w3.invalid/ns#>.

<>
a pim:ConfigurationFile;

dct:description "Administrative settings for the POD that the user can only read." .

</>
solid:storageQuota "25000000" .

13 changes: 13 additions & 0 deletions default-templates/new-account/settings/serverSide.ttl.acl
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
@prefix acl: <http://www.w3.org/ns/auth/acl#>.
@prefix foaf: <http://xmlns.com/foaf/0.1/>.

<#owner>
a acl:Authorization;

acl:agent
<{{webId}}>;

acl:accessTo <./serverSide.ttl>;

acl:mode acl:Read .

1 change: 1 addition & 0 deletions lib/debug.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,4 @@ exports.container = debug('solid:container')
exports.accounts = debug('solid:accounts')
exports.email = debug('solid:email')
exports.ldp = debug('solid:ldp')
exports.fs = debug('solid:fs')
25 changes: 17 additions & 8 deletions lib/handlers/patch.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ const utils = require('../utils.js')
const error = require('../http-error')
const $rdf = require('rdflib')
const crypto = require('crypto')
const overQuota = require('../utils').overQuota

const DEFAULT_TARGET_TYPE = 'text/turtle'

Expand Down Expand Up @@ -53,7 +54,7 @@ function patchHandler (req, res, next) {
])
// Patch the graph and write it back to the file
.then(([graph, patchObject]) => applyPatch(patchObject, graph, target))
.then(graph => writeGraph(graph, target))
.then(graph => writeGraph(graph, target, root, ldp.serverUri))
// Send the result to the client
.then(result => { res.send(result) })
.then(next, next)
Expand Down Expand Up @@ -137,19 +138,27 @@ function applyPatch (patchObject, graph, target) {
}

// Writes the RDF graph to the given resource
function writeGraph (graph, resource) {
function writeGraph (graph, resource, root, serverUri) {
debug('PATCH -- Writing patched file')
return new Promise((resolve, reject) => {
const resourceSym = graph.sym(resource.uri)
const serialized = $rdf.serialize(resourceSym, graph, resource.uri, resource.contentType)

fs.writeFile(resource.file, serialized, {encoding: 'utf8'}, function (err) {
if (err) {
return reject(error(500, `Failed to write file after patch: ${err}`))
// First check if we are above quota
overQuota(root, serverUri).then((isOverQuota) => {
if (isOverQuota) {
return reject(error(413,
'User has exceeded their storage quota'))
}
debug('PATCH -- applied successfully')
resolve('Patch applied successfully.\n')
})

fs.writeFile(resource.file, serialized, {encoding: 'utf8'}, function (err) {
if (err) {
return reject(error(500, `Failed to write file after patch: ${err}`))
}
debug('PATCH -- applied successfully')
resolve('Patch applied successfully.\n')
})
}).catch(() => reject(error(500, 'Error finding user quota')))
})
}

Expand Down
42 changes: 26 additions & 16 deletions lib/ldp.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ const utils = require('./utils')
const error = require('./http-error')
const stringToStream = require('./utils').stringToStream
const serialize = require('./utils').serialize
const overQuota = require('./utils').overQuota

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note for vNext: we should probably be able to implement this with a decorator pattern only.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds good. For example with traits?

const extend = require('extend')
const rimraf = require('rimraf')
const ldpContainer = require('./ldp-container')
Expand Down Expand Up @@ -266,24 +267,33 @@ class LDP {
return callback(error(409,
'PUT not supported on containers, use POST instead'))
}
// First, create the enclosing directory, if necessary
const dirName = path.dirname(filePath)
mkdirp(dirName, (err) => {
if (err) {
debug.handlers('PUT -- Error creating directory: ' + err)
return callback(error(err,
'Failed to create the path to the new resource'))

// First check if we are above quota
overQuota(root, this.serverUri).then((isOverQuota) => {
if (isOverQuota) {
return callback(error(413,
'User has exceeded their storage quota'))
}
// Directory created, now write the file
const file = stream.pipe(fs.createWriteStream(filePath))
file.on('error', function () {
callback(error(500, 'Error writing data'))
})
file.on('finish', function () {
debug.handlers('PUT -- Wrote data to: ' + filePath)
callback(null)

// Second, create the enclosing directory, if necessary
const dirName = path.dirname(filePath)
mkdirp(dirName, (err) => {
if (err) {
debug.handlers('PUT -- Error creating directory: ' + err)
return callback(error(err,
'Failed to create the path to the new resource'))
}
// Directory created, now write the file
const file = stream.pipe(fs.createWriteStream(filePath))
file.on('error', function () {
callback(error(500, 'Error writing data'))
})
file.on('finish', function () {
debug.handlers('PUT -- Wrote data to: ' + filePath)
callback(null)
})
})
})
}).catch(() => callback(error(500, 'Error finding user quota')))
}

exists (hostname, path, callback) {
Expand Down
63 changes: 63 additions & 0 deletions lib/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,18 @@ module.exports.debrack = debrack
module.exports.stripLineEndings = stripLineEndings
module.exports.fullUrlForReq = fullUrlForReq
module.exports.routeResolvedFile = routeResolvedFile
module.exports.getQuota = getQuota
module.exports.overQuota = overQuota

const fs = require('fs-extra')
const path = require('path')
const util = require('util')
const $rdf = require('rdflib')
const from = require('from2')
const url = require('url')
const debug = require('./debug').fs
const getSize = require('get-folder-size')
var ns = require('solid-namespace')($rdf)

/**
* Returns a fully qualified URL from an Express.js Request object.
Expand Down Expand Up @@ -239,3 +245,60 @@ function routeResolvedFile (router, path, file, appendFileName = true) {
const fullFile = require.resolve(file)
router.get(fullPath, (req, res) => res.sendFile(fullFile))
}

/**
* Returns the number of bytes that the user owning the requested POD
* may store or Infinity if no limit
*/

async function getQuota (root, serverUri) {
const filename = path.join(root, 'settings/serverSide.ttl')
var prefs
try {
prefs = await _asyncReadfile(filename)
} catch (error) {
debug('Setting no quota. While reading serverSide.ttl, got ' + error)
return Infinity
}
var graph = $rdf.graph()
const storageUri = serverUri + '/'
try {
$rdf.parse(prefs, graph, storageUri, 'text/turtle')
} catch (error) {
throw new Error('Failed to parse serverSide.ttl, got ' + error)
}
return Number(graph.anyValue($rdf.sym(storageUri), ns.solid('storageQuota'))) || Infinity
}

/**
* Returns true of the user has already exceeded their quota, i.e. it
* will check if new requests should be rejected, which means they
* could PUT a large file and get away with it.
*/

async function overQuota (root, serverUri) {
let quota = await getQuota(root, serverUri)
if (quota === Infinity) {
return false
}
// TODO: cache this value?
var size = await actualSize(root)
return (size > quota)
}

/**
* Returns the number of bytes that is occupied by the actual files in
* the file system. IMPORTANT NOTE: Since it traverses the directory
* to find the actual file sizes, this does a costly operation, but
* neglible for the small quotas we currently allow. If the quotas
* grow bigger, this will significantly reduce write performance, and
* so it needs to be rewritten.
*/

function actualSize (root) {
return util.promisify(getSize)(root)
}

function _asyncReadfile (filename) {
return util.promisify(fs.readFile)(filename, 'utf-8')
}
Loading