./src/models.js annotated source
Back to indexThis file contains all logic for managing the filesystem backed database of Codeframe files.
3
4const fs = require('fs');
5const path = require('path');
6const crypto = require('crypto');
7const zlib = require('zlib');
8
9const config = require('../config.js');
10
These files are Codeframe files that must be ensured to exist in the database with every deploy, since they're used in demos on the home page. These are checked for existence later.
14const STARTER_FIXTURES = [
15 'blank-torus.js',
16 'blank.frame',
17 'button-effects.html',
18 'canvas.js',
19 'filters-shadows.html',
20 'flexbox.html',
21 'helloworld.html',
22 'helloworld.js',
23 'simple-blog.html',
24 'interactive-input.html',
25 'interactive-input.js',
26 'nametag-torus.js',
27 'see-javascript.html',
28 'todo-torus.js',
29 'welcome.html',
30];
31
Utility method to get a trimmed sha256 hash of a string.
33const hashFile = contents => {
34 const hash = crypto.createHash('sha256');
35 hash.update(contents);
36 // first 12 chars of the hex digest
37 return hash.digest('hex').substr(0, 12);
38}
39
SourceFileStore
is the database that manages the app's communication with the filesystem-backed storage for Codeframe files. For efficiency of data in storage, we compress files stored here with gzip for on-disk storage.
43class SourceFileStore {
44
45 constructor(basePath) {
46 this.basePath = basePath;
47 if (!fs.existsSync(this.basePath)) {
48 fs.mkdirSync(this.basePath);
49 }
The first time the file store is created, we make sure each of the required demo snippets exists.
52 for (const fxt of STARTER_FIXTURES) {
53 fs.readFile(`starter_fixtures/${fxt}`, 'utf8', (err, data) => {
54 if (err) {
55 console.error(err);
56 } else {
57 this.create(data);
58 }
59 });
60 }
61 }
62
63 getPathFromHash(hash) {
64 return path.join(this.basePath, `cf_${hash}.frame`);
65 }
66
67 getHashedFilePath(contents) {
68 return this.getPathFromHash(hashFile(contents));
69 }
70
71 has(sourceFilePath) {
72 return new Promise((res, _rej) => {
73 fs.access(sourceFilePath, fs.constants.R_OK, err => {
74 res(!err);
75 });
76 });
77 }
78
Given a hash, returns a Promise resolving to the contents of the file, or rejects.
80 getFromFS(frameHash) {
81 return new Promise((res, rej) => {
82 fs.readFile(this.getPathFromHash(frameHash), (err, data) => {
83 if (err) {
84 rej(err);
85 } else {
unzip gzip compression of the read data before returning to the caller
88 zlib.gunzip(data, 'utf8', (err, results) => {
89 if (err) {
90 rej(err);
91 } else {
92 res(results.toString('utf8'));
93 }
94 });
95 }
96 });
97 });
98 }
99
First check if the file we're looking to create exists, and if not, create one.
101 async create(sourceFileContents) {
102 const frameHash = hashFile(sourceFileContents);
103 const sourceFilePath = this.getHashedFilePath(sourceFileContents);
104 const exists = await this.has(sourceFilePath);
105 if (!exists) {
106 return new Promise((res, rej) => {
Before saving the file, gzip the text file
108 zlib.gzip(sourceFileContents, 'utf8', (err, results) => {
109 if (err) {
110 rej(err);
111 } else {
112 fs.writeFile(sourceFilePath, results, err => {
113 if (err) {
114 rej(err)
115 } else {
116 res(frameHash);
117 }
118 });
119 }
120 });
121 });
122 }
123 return frameHash;
124 }
125
126}
127
Create a new database from the class, and export that for use.
129const store = new SourceFileStore(config.DATABASE);
130
131module.exports = {
132 store,
133}
134