First draft of storage APIs (#122)
* First draft of storage APIs * bumped pxt-core * using fixed instances to seprate temp from permanent * send console to storage * sim support * missing sim stubs * adding storage blocks * more sim support * remove storage from default package * fix rendering of ms * render raw ms * slize at better place * duplicate bundled dir * refactor limit * simplify limit logic
This commit is contained in:
parent
966fd81870
commit
20a4673f98
@ -10,10 +10,12 @@
|
|||||||
"MMap.getNumber": "Read a number in specified format from the buffer.",
|
"MMap.getNumber": "Read a number in specified format from the buffer.",
|
||||||
"MMap.ioctl": "Perform ioctl(2) on the underlaying file",
|
"MMap.ioctl": "Perform ioctl(2) on the underlaying file",
|
||||||
"MMap.length": "Returns the length of a Buffer object.",
|
"MMap.length": "Returns the length of a Buffer object.",
|
||||||
|
"MMap.lseek": "Set pointer on the underlaying file.",
|
||||||
"MMap.read": "Perform read(2) on the underlaying file",
|
"MMap.read": "Perform read(2) on the underlaying file",
|
||||||
"MMap.setNumber": "Write a number in specified format in the buffer.",
|
"MMap.setNumber": "Write a number in specified format in the buffer.",
|
||||||
"MMap.slice": "Read a range of bytes into a buffer.",
|
"MMap.slice": "Read a range of bytes into a buffer.",
|
||||||
"MMap.write": "Perform write(2) on the underlaying file",
|
"MMap.write": "Perform write(2) on the underlaying file",
|
||||||
|
"SeekWhence": "Mode for lseek()",
|
||||||
"brick.Button": "Generic button class, for device buttons and sensors.",
|
"brick.Button": "Generic button class, for device buttons and sensors.",
|
||||||
"brick.Button.isPressed": "Check if button is currently pressed or not.",
|
"brick.Button.isPressed": "Check if button is currently pressed or not.",
|
||||||
"brick.Button.onEvent": "Do something when a button or sensor is clicked, up or down.",
|
"brick.Button.onEvent": "Do something when a button or sensor is clicked, up or down.",
|
||||||
|
11
libs/core/enums.d.ts
vendored
11
libs/core/enums.d.ts
vendored
@ -1,6 +1,17 @@
|
|||||||
// Auto-generated. Do not edit.
|
// Auto-generated. Do not edit.
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mode for lseek()
|
||||||
|
*/
|
||||||
|
|
||||||
|
declare const enum SeekWhence {
|
||||||
|
Set = 0,
|
||||||
|
Current = 1,
|
||||||
|
End = 2,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Drawing modes
|
* Drawing modes
|
||||||
*/
|
*/
|
||||||
|
@ -7,6 +7,16 @@
|
|||||||
#include <fcntl.h>
|
#include <fcntl.h>
|
||||||
#include <sys/ioctl.h>
|
#include <sys/ioctl.h>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mode for lseek()
|
||||||
|
*/
|
||||||
|
enum class SeekWhence {
|
||||||
|
Set = 0,
|
||||||
|
Current = 1,
|
||||||
|
End = 2,
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
namespace pxt {
|
namespace pxt {
|
||||||
PXT_VTABLE_CTOR(MMap) {
|
PXT_VTABLE_CTOR(MMap) {
|
||||||
length = 0;
|
length = 0;
|
||||||
@ -111,4 +121,10 @@ int read(MMap *mmap, Buffer data) {
|
|||||||
return ::read(mmap->fd, data->data, data->length);
|
return ::read(mmap->fd, data->data, data->length);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Set pointer on the underlaying file. */
|
||||||
|
//%
|
||||||
|
int lseek(MMap *mmap, int offset, SeekWhence whence) {
|
||||||
|
return ::lseek(mmap->fd, offset, (int)whence);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
4
libs/core/shims.d.ts
vendored
4
libs/core/shims.d.ts
vendored
@ -41,6 +41,10 @@ declare interface MMap {
|
|||||||
/** Perform read(2) on the underlaying file */
|
/** Perform read(2) on the underlaying file */
|
||||||
//% shim=MMapMethods::read
|
//% shim=MMapMethods::read
|
||||||
read(data: Buffer): int32;
|
read(data: Buffer): int32;
|
||||||
|
|
||||||
|
/** Set pointer on the underlaying file. */
|
||||||
|
//% shim=MMapMethods::lseek
|
||||||
|
lseek(offset: int32, whence: SeekWhence): int32;
|
||||||
}
|
}
|
||||||
declare namespace control {
|
declare namespace control {
|
||||||
|
|
||||||
|
@ -57,3 +57,8 @@ namespace loops {
|
|||||||
namespace light {
|
namespace light {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//% color="#b0b0b0" advanced=true weight=5
|
||||||
|
namespace storage {
|
||||||
|
|
||||||
|
}
|
26
libs/storage/_locales/storage-jsdoc-strings.json
Normal file
26
libs/storage/_locales/storage-jsdoc-strings.json
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
{
|
||||||
|
"storage.Storage.append": "Append string data to a new or existing file.",
|
||||||
|
"storage.Storage.appendBuffer": "Append a buffer to a new or existing file.",
|
||||||
|
"storage.Storage.appendCSV": "Append a row of CSV data",
|
||||||
|
"storage.Storage.appendCSV|param|data": "the data to append",
|
||||||
|
"storage.Storage.appendCSV|param|filename": "the file name to append data, eg: \"data.txt\"",
|
||||||
|
"storage.Storage.appendLine": "Appends a new line of data in the file",
|
||||||
|
"storage.Storage.appendLine|param|data": "the data to append",
|
||||||
|
"storage.Storage.appendLine|param|filename": "the file name to append data, eg: \"data.txt\"",
|
||||||
|
"storage.Storage.append|param|data": "the data to append",
|
||||||
|
"storage.Storage.append|param|filename": "the file name to append data, eg: \"data.txt\"",
|
||||||
|
"storage.Storage.exists": "Tests if a file exists",
|
||||||
|
"storage.Storage.exists|param|filename": "the file name to append data, eg: \"data.txt\"",
|
||||||
|
"storage.Storage.limit": "Resizing the size of a file to stay under the limit",
|
||||||
|
"storage.Storage.limit|param|filename": "name of the file to drop",
|
||||||
|
"storage.Storage.limit|param|size": "maximum length",
|
||||||
|
"storage.Storage.overwrite": "Overwrite file with string data.",
|
||||||
|
"storage.Storage.overwriteWithBuffer": "Overwrite file with a buffer.",
|
||||||
|
"storage.Storage.overwrite|param|data": "the data to append",
|
||||||
|
"storage.Storage.overwrite|param|filename": "the file name to append data, eg: \"data.txt\"",
|
||||||
|
"storage.Storage.read": "Read contents of file as a string.",
|
||||||
|
"storage.Storage.readAsBuffer": "Read contents of file as a buffer.",
|
||||||
|
"storage.Storage.remove": "Delete a file, or do nothing if it doesn't exist.",
|
||||||
|
"storage.Storage.size": "Return the size of the file, or -1 if it doesn't exists.",
|
||||||
|
"storage.temporary": "Temporary storage in memory, deleted when the device restarts."
|
||||||
|
}
|
14
libs/storage/_locales/storage-strings.json
Normal file
14
libs/storage/_locales/storage-strings.json
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"storage.Storage.appendCSV|block": "storage %source|%filename|append CSV %data",
|
||||||
|
"storage.Storage.appendLine|block": "storage %source|%filename|append line %data",
|
||||||
|
"storage.Storage.append|block": "storage %source|%filename|append %data",
|
||||||
|
"storage.Storage.exists|block": "storage %source|%filename|exists",
|
||||||
|
"storage.Storage.limit|block": "storage %source|limit %filename|to %size|bytes",
|
||||||
|
"storage.Storage.overwrite|block": "storage %source|%filename|overwrite with|%data",
|
||||||
|
"storage.Storage.read|block": "storage %source|read %filename|as string",
|
||||||
|
"storage.Storage.remove|block": "storage %source|remove %filename",
|
||||||
|
"storage.Storage.size|block": "storage %source|%filename|size",
|
||||||
|
"storage.temporary|block": "temporary",
|
||||||
|
"storage|block": "storage",
|
||||||
|
"{id:category}Storage": "Storage"
|
||||||
|
}
|
14
libs/storage/pxt.json
Normal file
14
libs/storage/pxt.json
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"name": "storage",
|
||||||
|
"description": "USB Pen-drive support and flash storage",
|
||||||
|
"files": [
|
||||||
|
"storage.cpp",
|
||||||
|
"storage-core.ts",
|
||||||
|
"storage.ts",
|
||||||
|
"shims.d.ts"
|
||||||
|
],
|
||||||
|
"public": true,
|
||||||
|
"dependencies": {
|
||||||
|
"core": "file:../core"
|
||||||
|
}
|
||||||
|
}
|
17
libs/storage/shims.d.ts
vendored
Normal file
17
libs/storage/shims.d.ts
vendored
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
// Auto-generated. Do not edit.
|
||||||
|
declare namespace storage {
|
||||||
|
|
||||||
|
/** Will be moved. */
|
||||||
|
//% shim=storage::__stringToBuffer
|
||||||
|
function __stringToBuffer(s: string): Buffer;
|
||||||
|
|
||||||
|
/** Will be moved. */
|
||||||
|
//% shim=storage::__bufferToString
|
||||||
|
function __bufferToString(s: Buffer): string;
|
||||||
|
|
||||||
|
/** Create named directory. */
|
||||||
|
//% shim=storage::__mkdir
|
||||||
|
function __mkdir(filename: string): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Auto-generated. Do not edit. Really.
|
178
libs/storage/storage-core.ts
Normal file
178
libs/storage/storage-core.ts
Normal file
@ -0,0 +1,178 @@
|
|||||||
|
namespace storage {
|
||||||
|
//% shim=storage::__unlink
|
||||||
|
function __unlink(filename: string): void { }
|
||||||
|
//% shim=storage::__truncate
|
||||||
|
function __truncate(filename: string): void { }
|
||||||
|
|
||||||
|
//% fixedInstances
|
||||||
|
export class Storage {
|
||||||
|
constructor() { }
|
||||||
|
|
||||||
|
protected mapFilename(filename: string) {
|
||||||
|
return filename;
|
||||||
|
}
|
||||||
|
|
||||||
|
private getFile(filename: string): MMap {
|
||||||
|
filename = this.mapFilename(filename)
|
||||||
|
let r = control.mmap(filename, 0, 0)
|
||||||
|
if (!r) {
|
||||||
|
__mkdir(this.dirname(filename))
|
||||||
|
__truncate(filename)
|
||||||
|
r = control.mmap(filename, 0, 0)
|
||||||
|
}
|
||||||
|
if (!r)
|
||||||
|
control.panic(906)
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
dirname(filename: string) {
|
||||||
|
let last = 0
|
||||||
|
for (let i = 0; i < filename.length; ++i)
|
||||||
|
if (filename[i] == "/")
|
||||||
|
last = i
|
||||||
|
return filename.substr(0, last)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Append string data to a new or existing file.
|
||||||
|
* @param filename the file name to append data, eg: "data.txt"
|
||||||
|
* @param data the data to append
|
||||||
|
*/
|
||||||
|
//% blockId=storageAppend block="storage %source|%filename|append %data"
|
||||||
|
append(filename: string, data: string): void {
|
||||||
|
this.appendBuffer(filename, __stringToBuffer(data))
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Appends a new line of data in the file
|
||||||
|
* @param filename the file name to append data, eg: "data.txt"
|
||||||
|
* @param data the data to append
|
||||||
|
*/
|
||||||
|
//% blockId=storageAppendLine block="storage %source|%filename|append line %data"
|
||||||
|
appendLine(filename: string, data: string): void {
|
||||||
|
this.append(filename, data + "\r\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Append a buffer to a new or existing file. */
|
||||||
|
appendBuffer(filename: string, data: Buffer): void {
|
||||||
|
let f = this.getFile(filename);
|
||||||
|
f.lseek(0, SeekWhence.End)
|
||||||
|
f.write(data)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Append a row of CSV data
|
||||||
|
* @param filename the file name to append data, eg: "data.txt"
|
||||||
|
* @param data the data to append
|
||||||
|
*/
|
||||||
|
//% blockId=storageAppendCSV block="storage %source|%filename|append CSV %data"
|
||||||
|
appendCSV(filename: string, data: number[]) {
|
||||||
|
let s = ""
|
||||||
|
for (const d of data) {
|
||||||
|
if (s) s += "\t"
|
||||||
|
s = s + d;
|
||||||
|
}
|
||||||
|
s += "\r\n"
|
||||||
|
this.append(filename, s)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Overwrite file with string data.
|
||||||
|
* @param filename the file name to append data, eg: "data.txt"
|
||||||
|
* @param data the data to append
|
||||||
|
*/
|
||||||
|
//% blockId=storageOverwrite block="storage %source|%filename|overwrite with|%data"
|
||||||
|
overwrite(filename: string, data: string): void {
|
||||||
|
this.overwriteWithBuffer(filename, __stringToBuffer(data))
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Overwrite file with a buffer. */
|
||||||
|
overwriteWithBuffer(filename: string, data: Buffer): void {
|
||||||
|
__truncate(this.mapFilename(filename))
|
||||||
|
this.appendBuffer(filename, data)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Tests if a file exists
|
||||||
|
* @param filename the file name to append data, eg: "data.txt"
|
||||||
|
*/
|
||||||
|
//% blockId=storageExists block="storage %source|%filename|exists"
|
||||||
|
exists(filename: string): boolean {
|
||||||
|
return !!control.mmap(this.mapFilename(filename), 0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Delete a file, or do nothing if it doesn't exist. */
|
||||||
|
//% blockId=storageRemove block="storage %source|remove %filename"
|
||||||
|
remove(filename: string): void {
|
||||||
|
__unlink(this.mapFilename(filename))
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Return the size of the file, or -1 if it doesn't exists. */
|
||||||
|
//% blockId=storageSize block="storage %source|%filename|size"
|
||||||
|
size(filename: string): int32 {
|
||||||
|
let f = control.mmap(this.mapFilename(filename), 0, 0)
|
||||||
|
if (!f) return -1;
|
||||||
|
return f.lseek(0, SeekWhence.End)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Read contents of file as a string. */
|
||||||
|
//% blockId=storageRead block="storage %source|read %filename|as string"
|
||||||
|
read(filename: string): string {
|
||||||
|
return __bufferToString(this.readAsBuffer(filename))
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Read contents of file as a buffer. */
|
||||||
|
//%
|
||||||
|
readAsBuffer(filename: string): Buffer {
|
||||||
|
let f = this.getFile(filename)
|
||||||
|
let sz = f.lseek(0, SeekWhence.End)
|
||||||
|
let b = output.createBuffer(sz)
|
||||||
|
f.lseek(0, SeekWhence.Set);
|
||||||
|
f.read(b)
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resizing the size of a file to stay under the limit
|
||||||
|
* @param filename name of the file to drop
|
||||||
|
* @param size maximum length
|
||||||
|
*/
|
||||||
|
//% blockId=storageLimit block="storage %source|limit %filename|to %size|bytes"
|
||||||
|
limit(filename: string, size: number) {
|
||||||
|
if (!this.exists(filename) || size < 0) return;
|
||||||
|
|
||||||
|
const sz = storage.temporary.size(filename);
|
||||||
|
if (sz > size) {
|
||||||
|
let buf = storage.temporary.readAsBuffer(filename)
|
||||||
|
buf = buf.slice(buf.length / 2);
|
||||||
|
storage.temporary.overwriteWithBuffer(filename, buf);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class TemporaryStorage extends Storage {
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected mapFilename(filename: string) {
|
||||||
|
if (filename[0] == '/') filename = filename.substr(1);
|
||||||
|
return '/tmp/logs/' + filename;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Temporary storage in memory, deleted when the device restarts.
|
||||||
|
*/
|
||||||
|
//% whenUsed fixedInstance block="temporary"
|
||||||
|
export const temporary: Storage = new TemporaryStorage();
|
||||||
|
|
||||||
|
class PermanentStorage extends Storage {
|
||||||
|
constructor() {
|
||||||
|
super()
|
||||||
|
}
|
||||||
|
|
||||||
|
protected mapFilename(filename: string) {
|
||||||
|
if (filename[0] == '/') return filename;
|
||||||
|
return '/' + filename;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
44
libs/storage/storage.cpp
Normal file
44
libs/storage/storage.cpp
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
#include "pxt.h"
|
||||||
|
|
||||||
|
#include <sys/types.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <sys/stat.h>
|
||||||
|
#include <fcntl.h>
|
||||||
|
|
||||||
|
namespace storage {
|
||||||
|
|
||||||
|
/** Will be moved. */
|
||||||
|
//%
|
||||||
|
Buffer __stringToBuffer(String s) {
|
||||||
|
return mkBuffer((uint8_t *)s->data, s->length);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Will be moved. */
|
||||||
|
//%
|
||||||
|
String __bufferToString(Buffer s) {
|
||||||
|
return mkString((char*)s->data, s->length);
|
||||||
|
}
|
||||||
|
|
||||||
|
//%
|
||||||
|
void __init() {
|
||||||
|
// do nothing
|
||||||
|
}
|
||||||
|
|
||||||
|
//%
|
||||||
|
void __unlink(String filename) {
|
||||||
|
::unlink(filename->data);
|
||||||
|
}
|
||||||
|
|
||||||
|
//%
|
||||||
|
void __truncate(String filename) {
|
||||||
|
int fd = open(filename->data, O_CREAT | O_TRUNC | O_WRONLY, 0777);
|
||||||
|
close(fd);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Create named directory. */
|
||||||
|
//%
|
||||||
|
void __mkdir(String filename) {
|
||||||
|
::mkdir(filename->data, 0777);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace storage
|
11
libs/storage/storage.ts
Normal file
11
libs/storage/storage.ts
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
namespace storage {
|
||||||
|
// automatically send console output to temp storage
|
||||||
|
storage.temporary.remove("console.txt");
|
||||||
|
console.addListener(function(line) {
|
||||||
|
const fn = "console.txt";
|
||||||
|
const mxs = 65536;
|
||||||
|
const t = control.millis();
|
||||||
|
storage.temporary.appendLine(fn, `${t}> ${line}`);
|
||||||
|
storage.temporary.limit(fn, 65536);
|
||||||
|
})
|
||||||
|
}
|
@ -16,6 +16,7 @@
|
|||||||
"libs/infrared-sensor",
|
"libs/infrared-sensor",
|
||||||
"libs/gyro-sensor",
|
"libs/gyro-sensor",
|
||||||
"libs/chassis",
|
"libs/chassis",
|
||||||
|
"libs/storage",
|
||||||
"libs/ev3",
|
"libs/ev3",
|
||||||
"libs/tests",
|
"libs/tests",
|
||||||
"libs/behaviors"
|
"libs/behaviors"
|
||||||
|
@ -8,6 +8,7 @@ namespace pxsim.MMapMethods {
|
|||||||
read?: (d: Buffer) => number;
|
read?: (d: Buffer) => number;
|
||||||
write?: (d: Buffer) => number;
|
write?: (d: Buffer) => number;
|
||||||
ioctl?: (id: number, d: Buffer) => number;
|
ioctl?: (id: number, d: Buffer) => number;
|
||||||
|
lseek?: (offset: number, whence: number) => number;
|
||||||
}
|
}
|
||||||
|
|
||||||
import BM = pxsim.BufferMethods
|
import BM = pxsim.BufferMethods
|
||||||
@ -23,6 +24,7 @@ namespace pxsim.MMapMethods {
|
|||||||
if (!impl.read) impl.read = () => 0
|
if (!impl.read) impl.read = () => 0
|
||||||
if (!impl.write) impl.write = () => 0
|
if (!impl.write) impl.write = () => 0
|
||||||
if (!impl.ioctl) impl.ioctl = () => -1
|
if (!impl.ioctl) impl.ioctl = () => -1
|
||||||
|
if (!impl.lseek) impl.lseek = (offset, whence) => -1
|
||||||
}
|
}
|
||||||
destroy() {
|
destroy() {
|
||||||
}
|
}
|
||||||
@ -68,6 +70,10 @@ namespace pxsim.MMapMethods {
|
|||||||
export function read(m: MMap, data: Buffer): number {
|
export function read(m: MMap, data: Buffer): number {
|
||||||
return m.impl.read(data)
|
return m.impl.read(data)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function lseek(m: MMap, offset: number, whence: number): number {
|
||||||
|
return m.impl.lseek(offset, whence);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace pxsim.control {
|
namespace pxsim.control {
|
||||||
|
23
sim/state/storage.ts
Normal file
23
sim/state/storage.ts
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
namespace pxsim.storage {
|
||||||
|
export function __stringToBuffer(s: string): RefBuffer {
|
||||||
|
// TODO
|
||||||
|
return new RefBuffer(new Uint8Array([]));
|
||||||
|
}
|
||||||
|
|
||||||
|
export function __bufferToString(b: RefBuffer): string {
|
||||||
|
// TODO
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
export function __mkdir(fn: string) {
|
||||||
|
// TODO
|
||||||
|
}
|
||||||
|
|
||||||
|
export function __unlink(filename: string): void {
|
||||||
|
// TODO
|
||||||
|
}
|
||||||
|
|
||||||
|
export function __truncate(filename: string): void {
|
||||||
|
// TODO
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user