This app provides monitoring and information features for the common freifunk user and the technical stuff of a freifunk community.
Code base is taken from a TUM Practical Course project and added here to see if Freifunk Altdorf can use it.
https://www.freifunk-altdorf.de
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
445 lines
10 KiB
445 lines
10 KiB
'use strict'; |
|
|
|
// adapted from http://code.google.com/p/plist/source/browse/trunk/src/main/java/com/dd/plist/BinaryPropertyListWriter.java |
|
|
|
var streamBuffers = require("stream-buffers"); |
|
|
|
var debug = false; |
|
|
|
function Real(value) { |
|
this.value = value; |
|
} |
|
|
|
module.exports = function(dicts) { |
|
var buffer = new streamBuffers.WritableStreamBuffer(); |
|
buffer.write(new Buffer("bplist00")); |
|
|
|
if (debug) { |
|
console.log('create', require('util').inspect(dicts, false, 10)); |
|
} |
|
|
|
if (dicts instanceof Array && dicts.length === 1) { |
|
dicts = dicts[0]; |
|
} |
|
|
|
var entries = toEntries(dicts); |
|
if (debug) { |
|
console.log('entries', entries); |
|
} |
|
var idSizeInBytes = computeIdSizeInBytes(entries.length); |
|
var offsets = []; |
|
var offsetSizeInBytes; |
|
var offsetTableOffset; |
|
|
|
updateEntryIds(); |
|
|
|
entries.forEach(function(entry, entryIdx) { |
|
offsets[entryIdx] = buffer.size(); |
|
if (!entry) { |
|
buffer.write(0x00); |
|
} else { |
|
write(entry); |
|
} |
|
}); |
|
|
|
writeOffsetTable(); |
|
writeTrailer(); |
|
return buffer.getContents(); |
|
|
|
function updateEntryIds() { |
|
var strings = {}; |
|
var entryId = 0; |
|
entries.forEach(function(entry) { |
|
if (entry.id) { |
|
return; |
|
} |
|
if (entry.type === 'string') { |
|
if (!entry.bplistOverride && strings.hasOwnProperty(entry.value)) { |
|
entry.type = 'stringref'; |
|
entry.id = strings[entry.value]; |
|
} else { |
|
strings[entry.value] = entry.id = entryId++; |
|
} |
|
} else { |
|
entry.id = entryId++; |
|
} |
|
}); |
|
|
|
entries = entries.filter(function(entry) { |
|
return (entry.type !== 'stringref'); |
|
}); |
|
} |
|
|
|
function writeTrailer() { |
|
if (debug) { |
|
console.log('0x' + buffer.size().toString(16), 'writeTrailer'); |
|
} |
|
// 6 null bytes |
|
buffer.write(new Buffer([0, 0, 0, 0, 0, 0])); |
|
|
|
// size of an offset |
|
if (debug) { |
|
console.log('0x' + buffer.size().toString(16), 'writeTrailer(offsetSizeInBytes):', offsetSizeInBytes); |
|
} |
|
writeByte(offsetSizeInBytes); |
|
|
|
// size of a ref |
|
if (debug) { |
|
console.log('0x' + buffer.size().toString(16), 'writeTrailer(offsetSizeInBytes):', idSizeInBytes); |
|
} |
|
writeByte(idSizeInBytes); |
|
|
|
// number of objects |
|
if (debug) { |
|
console.log('0x' + buffer.size().toString(16), 'writeTrailer(number of objects):', entries.length); |
|
} |
|
writeLong(entries.length); |
|
|
|
// top object |
|
if (debug) { |
|
console.log('0x' + buffer.size().toString(16), 'writeTrailer(top object)'); |
|
} |
|
writeLong(0); |
|
|
|
// offset table offset |
|
if (debug) { |
|
console.log('0x' + buffer.size().toString(16), 'writeTrailer(offset table offset):', offsetTableOffset); |
|
} |
|
writeLong(offsetTableOffset); |
|
} |
|
|
|
function writeOffsetTable() { |
|
if (debug) { |
|
console.log('0x' + buffer.size().toString(16), 'writeOffsetTable'); |
|
} |
|
offsetTableOffset = buffer.size(); |
|
offsetSizeInBytes = computeOffsetSizeInBytes(offsetTableOffset); |
|
offsets.forEach(function(offset) { |
|
writeBytes(offset, offsetSizeInBytes); |
|
}); |
|
} |
|
|
|
function write(entry) { |
|
switch (entry.type) { |
|
case 'dict': |
|
writeDict(entry); |
|
break; |
|
case 'number': |
|
case 'double': |
|
writeNumber(entry); |
|
break; |
|
case 'UID': |
|
writeUID(entry); |
|
break; |
|
case 'array': |
|
writeArray(entry); |
|
break; |
|
case 'boolean': |
|
writeBoolean(entry); |
|
break; |
|
case 'string': |
|
case 'string-utf16': |
|
writeString(entry); |
|
break; |
|
case 'date': |
|
writeDate(entry); |
|
break; |
|
case 'data': |
|
writeData(entry); |
|
break; |
|
default: |
|
throw new Error("unhandled entry type: " + entry.type); |
|
} |
|
} |
|
|
|
function writeDate(entry) { |
|
writeByte(0x33); |
|
var date = (Date.parse(entry.value)/1000) - 978307200 |
|
writeDouble(date) |
|
} |
|
|
|
function writeDict(entry) { |
|
if (debug) { |
|
var keysStr = entry.entryKeys.map(function(k) {return k.id;}); |
|
var valsStr = entry.entryValues.map(function(k) {return k.id;}); |
|
console.log('0x' + buffer.size().toString(16), 'writeDict', '(id: ' + entry.id + ')', '(keys: ' + keysStr + ')', '(values: ' + valsStr + ')'); |
|
} |
|
writeIntHeader(0xD, entry.entryKeys.length); |
|
entry.entryKeys.forEach(function(entry) { |
|
writeID(entry.id); |
|
}); |
|
entry.entryValues.forEach(function(entry) { |
|
writeID(entry.id); |
|
}); |
|
} |
|
|
|
function writeNumber(entry) { |
|
if (debug) { |
|
console.log('0x' + buffer.size().toString(16), 'writeNumber', entry.value, ' (type: ' + entry.type + ')', '(id: ' + entry.id + ')'); |
|
} |
|
|
|
if (entry.type !== 'double' && parseFloat(entry.value.toFixed()) == entry.value) { |
|
if (entry.value < 0) { |
|
writeByte(0x13); |
|
writeBytes(entry.value, 8); |
|
} else if (entry.value <= 0xff) { |
|
writeByte(0x10); |
|
writeBytes(entry.value, 1); |
|
} else if (entry.value <= 0xffff) { |
|
writeByte(0x11); |
|
writeBytes(entry.value, 2); |
|
} else if (entry.value <= 0xffffffff) { |
|
writeByte(0x12); |
|
writeBytes(entry.value, 4); |
|
} else { |
|
writeByte(0x13); |
|
writeBytes(entry.value, 8); |
|
} |
|
} else { |
|
writeByte(0x23); |
|
writeDouble(entry.value); |
|
} |
|
} |
|
|
|
function writeUID(entry) { |
|
if (debug) { |
|
console.log('0x' + buffer.size().toString(16), 'writeUID', entry.value, ' (type: ' + entry.type + ')', '(id: ' + entry.id + ')'); |
|
} |
|
|
|
writeIntHeader(0x8, 0x0); |
|
writeID(entry.value); |
|
} |
|
|
|
function writeArray(entry) { |
|
if (debug) { |
|
console.log('0x' + buffer.size().toString(16), 'writeArray (length: ' + entry.entries.length + ')', '(id: ' + entry.id + ')'); |
|
} |
|
writeIntHeader(0xA, entry.entries.length); |
|
entry.entries.forEach(function(e) { |
|
writeID(e.id); |
|
}); |
|
} |
|
|
|
function writeBoolean(entry) { |
|
if (debug) { |
|
console.log('0x' + buffer.size().toString(16), 'writeBoolean', entry.value, '(id: ' + entry.id + ')'); |
|
} |
|
writeByte(entry.value ? 0x09 : 0x08); |
|
} |
|
|
|
function writeString(entry) { |
|
if (debug) { |
|
console.log('0x' + buffer.size().toString(16), 'writeString', entry.value, '(id: ' + entry.id + ')'); |
|
} |
|
if (entry.type === 'string-utf16' || mustBeUtf16(entry.value)) { |
|
var utf16 = new Buffer(entry.value, 'ucs2'); |
|
writeIntHeader(0x6, utf16.length / 2); |
|
// needs to be big endian so swap the bytes |
|
for (var i = 0; i < utf16.length; i += 2) { |
|
var t = utf16[i + 0]; |
|
utf16[i + 0] = utf16[i + 1]; |
|
utf16[i + 1] = t; |
|
} |
|
buffer.write(utf16); |
|
} else { |
|
var utf8 = new Buffer(entry.value, 'ascii'); |
|
writeIntHeader(0x5, utf8.length); |
|
buffer.write(utf8); |
|
} |
|
} |
|
|
|
function writeData(entry) { |
|
if (debug) { |
|
console.log('0x' + buffer.size().toString(16), 'writeData', entry.value, '(id: ' + entry.id + ')'); |
|
} |
|
writeIntHeader(0x4, entry.value.length); |
|
buffer.write(entry.value); |
|
} |
|
|
|
function writeLong(l) { |
|
writeBytes(l, 8); |
|
} |
|
|
|
function writeByte(b) { |
|
buffer.write(new Buffer([b])); |
|
} |
|
|
|
function writeDouble(v) { |
|
var buf = new Buffer(8); |
|
buf.writeDoubleBE(v, 0); |
|
buffer.write(buf); |
|
} |
|
|
|
function writeIntHeader(kind, value) { |
|
if (value < 15) { |
|
writeByte((kind << 4) + value); |
|
} else if (value < 256) { |
|
writeByte((kind << 4) + 15); |
|
writeByte(0x10); |
|
writeBytes(value, 1); |
|
} else if (value < 65536) { |
|
writeByte((kind << 4) + 15); |
|
writeByte(0x11); |
|
writeBytes(value, 2); |
|
} else { |
|
writeByte((kind << 4) + 15); |
|
writeByte(0x12); |
|
writeBytes(value, 4); |
|
} |
|
} |
|
|
|
function writeID(id) { |
|
writeBytes(id, idSizeInBytes); |
|
} |
|
|
|
function writeBytes(value, bytes) { |
|
// write low-order bytes big-endian style |
|
var buf = new Buffer(bytes); |
|
var z = 0; |
|
// javascript doesn't handle large numbers |
|
while (bytes > 4) { |
|
buf[z++] = 0; |
|
bytes--; |
|
} |
|
for (var i = bytes - 1; i >= 0; i--) { |
|
buf[z++] = value >> (8 * i); |
|
} |
|
buffer.write(buf); |
|
} |
|
|
|
function mustBeUtf16(string) { |
|
return Buffer.byteLength(string, 'utf8') != string.length; |
|
} |
|
}; |
|
|
|
function toEntries(dicts) { |
|
if (dicts.bplistOverride) { |
|
return [dicts]; |
|
} |
|
|
|
if (dicts instanceof Array) { |
|
return toEntriesArray(dicts); |
|
} else if (dicts instanceof Buffer) { |
|
return [ |
|
{ |
|
type: 'data', |
|
value: dicts |
|
} |
|
]; |
|
} else if (dicts instanceof Real) { |
|
return [ |
|
{ |
|
type: 'double', |
|
value: dicts.value |
|
} |
|
]; |
|
} else if (typeof(dicts) === 'object') { |
|
if (dicts instanceof Date) { |
|
return [ |
|
{ |
|
type: 'date', |
|
value: dicts |
|
} |
|
] |
|
} else if (Object.keys(dicts).length == 1 && typeof(dicts.UID) === 'number') { |
|
return [ |
|
{ |
|
type: 'UID', |
|
value: dicts.UID |
|
} |
|
] |
|
} else { |
|
return toEntriesObject(dicts); |
|
} |
|
} else if (typeof(dicts) === 'string') { |
|
return [ |
|
{ |
|
type: 'string', |
|
value: dicts |
|
} |
|
]; |
|
} else if (typeof(dicts) === 'number') { |
|
return [ |
|
{ |
|
type: 'number', |
|
value: dicts |
|
} |
|
]; |
|
} else if (typeof(dicts) === 'boolean') { |
|
return [ |
|
{ |
|
type: 'boolean', |
|
value: dicts |
|
} |
|
]; |
|
} else { |
|
throw new Error('unhandled entry: ' + dicts); |
|
} |
|
} |
|
|
|
function toEntriesArray(arr) { |
|
if (debug) { |
|
console.log('toEntriesArray'); |
|
} |
|
var results = [ |
|
{ |
|
type: 'array', |
|
entries: [] |
|
} |
|
]; |
|
arr.forEach(function(v) { |
|
var entry = toEntries(v); |
|
results[0].entries.push(entry[0]); |
|
results = results.concat(entry); |
|
}); |
|
return results; |
|
} |
|
|
|
function toEntriesObject(dict) { |
|
if (debug) { |
|
console.log('toEntriesObject'); |
|
} |
|
var results = [ |
|
{ |
|
type: 'dict', |
|
entryKeys: [], |
|
entryValues: [] |
|
} |
|
]; |
|
Object.keys(dict).forEach(function(key) { |
|
var entryKey = toEntries(key); |
|
results[0].entryKeys.push(entryKey[0]); |
|
results = results.concat(entryKey[0]); |
|
}); |
|
Object.keys(dict).forEach(function(key) { |
|
var entryValue = toEntries(dict[key]); |
|
results[0].entryValues.push(entryValue[0]); |
|
results = results.concat(entryValue); |
|
}); |
|
return results; |
|
} |
|
|
|
function computeOffsetSizeInBytes(maxOffset) { |
|
if (maxOffset < 256) { |
|
return 1; |
|
} |
|
if (maxOffset < 65536) { |
|
return 2; |
|
} |
|
if (maxOffset < 4294967296) { |
|
return 4; |
|
} |
|
return 8; |
|
} |
|
|
|
function computeIdSizeInBytes(numberOfIds) { |
|
if (numberOfIds < 256) { |
|
return 1; |
|
} |
|
if (numberOfIds < 65536) { |
|
return 2; |
|
} |
|
return 4; |
|
} |
|
|
|
module.exports.Real = Real;
|
|
|