/
apploader.js
198 lines (169 loc) · 6.1 KB
/
apploader.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
var async = require('async'),
log = require('./helpers/log')('steelmesh-apploader'),
attachmate = require('attachmate'),
fstream = require('fstream'),
fs = require('fs'),
config = require('config'),
dbUrl = config.couchurl.replace(/\/$/, '') + '/' + config.meshdb,
path = require('path'),
util = require('util'),
events = require('events'),
nano = require('nano'),
_ = require('underscore'),
piper = require('piper'),
rimraf = require('rimraf'),
meshEvents = piper('steelmesh'),
reProtected = /^\/(app\.js|package\.json|lib|node_modules|resources|views)/i;
function Apploader(steelmesh) {
this.apps = [];
this.steelmesh = steelmesh;
}
Apploader.prototype._refreshStatic = function(app, callback) {
var srcPath = app.basePath,
targetPath = path.join(this.steelmesh.pathStatic, path.basename(srcPath));
log('copying ' + app.id + ' static resources: ' + srcPath + ' => ' + targetPath);
fs.exists(srcPath, function(exists) {
if (! exists) {
return callback();
}
// clean out the static files for the application, and then load
rimraf(targetPath, function(err) {
if (! err) {
// read valid application files
fstream.Reader({
type: 'Directory',
path: srcPath,
// copy only the files that exist outside of the protected areas
filter: function() {
return !reProtected.test(this.path.slice(srcPath.length));
}
})
.on('error', callback)
// pipe them to the _static directory
.pipe(fstream.Writer({
type: 'Directory',
path: targetPath
}))
.on('error', function(err) {
// TODO: handle this well...
log.error(err);
})
.on('end', callback);
}
else {
callback(err);
}
});
});
};
Apploader.prototype.load = function(db, dbid, app, callback) {
var apploader = this,
docUrl = dbUrl + '/' + dbid;
log('synchronizing ' + docUrl + ' with local application cache (' + app.basePath + ')');
async.series([
// download the couch attachments to the _apps directory
attachmate.download.bind(null, docUrl, app.basePath),
// move static resources
this._refreshStatic.bind(this, app)
], function(err) {
if (!err) {
log('finished synchronizing: ' + app.id);
apploader.apps.push(app);
// Emit the load event
meshEvents.emit('app.load.' + app.id, app);
log('app.load.' + app.id);
}
if (callback) {
callback(err);
}
});
};
/**
Cleans up the application resources for the application
indicated by id'
**/
Apploader.prototype.clean = function(id, app, callback) {
var apploader = this,
steelmesh = this.steelmesh,
app = _.find(apploader.apps, function(item) {
return item._id === id;
});
if (app) {
async.parallel([
rimraf.bind(null, app.basePath),
rimraf.bind(null, path.join(steelmesh.pathStatic, path.basename(app.basePath)))
], function(err) {
log('cleaned up application (' + id + ') resource files');
if (callback) {
callback(err);
}
});
} else {
if (callback) {
callback();
}
}
}
Apploader.prototype.run = function(callback) {
var apploader = this,
db = nano(config.couchurl).use(config.meshdb),
steelmesh = this.steelmesh,
appPrefix = steelmesh.appPrefix;
// listen for application updates
meshEvents.on('app.reload', function(id, data) {
log('update received for application ' + id);
meshEvents('status', 'resyncing');
meshEvents.emit('app.update.' + id, function(err) {
if (err) {
return log('Could not load - unable to unload existing application');
}
// Clean up the application
apploader.clean(id, null, function() {
// load the application
apploader.load(db, id, data, function() {
meshEvents('status', 'online');
});
});
});
});
meshEvents.on('app.unload', apploader.clean.bind(apploader));
// list the applications
db.list(function(err, res) {
if (err) {
callback(err);
}
else {
// get only the application rows
var rows = _.filter(res.rows, function(row) {
return row.id.slice(0, appPrefix.length) === appPrefix;
});
meshEvents('status', 'loading');
// initialise the list of stack apps
async.forEach(
rows,
function(row, itemCallback) {
log('getting app details for appid: ' + row.id);
db.get(row.id, function(err, res) {
if (err) {
itemCallback(err);
}
else {
// remove the attachments from the list
delete res._attachments;
// load the application
apploader.load(db, row.id, apploader.steelmesh.initApp(res), itemCallback);
}
});
},
function(err) {
log('finished application loading, err: ', err);
if (!err) {
meshEvents()
}
callback(err);
}
);
}
});
};
module.exports = Apploader;