8
8
* Charm instances can represent both environment charms and charm store
9
9
* charms. A charm id is reliably and uniquely associated with a given
10
* charm only within a given context--the environment or the charm store.
10
* charm only within a given context: the environment or the charm store.
12
12
* Therefore, the database keeps these charms separate in two different
13
* CharmList instances. One is db.charms, representing the environment
13
* CharmList instances. One is `db.charms`, representing the environment
14
14
* charms. The other, from the charm store, is maintained by and within the
15
* persistent charm panel instance. As you'd expect, environment charms are
16
* what to use when viewing or manipulating the environment. Charm store
15
* persistent charm panel instance. As you would expect, environment charms
16
* are what to use when viewing or manipulating the environment. Charm store
17
17
* charms are what we can browse to select and deploy new charms to the
20
20
* Charms begin their lives with full charm ids, as provided by
21
21
* services in the environment and the charm store:
23
* [SCHEME]:(~[OWNER]/)?[SERIES]/[PACKAGE NAME]-[REVISION].
23
* `[SCHEME]:(~[OWNER]/)?[SERIES]/[PACKAGE NAME]-[REVISION]`
25
25
* With an id, we can instantiate a charm: typically we use
26
* "db.charms.add({id: [ID]})". Finally, we load the charm's data over the
27
* network using the standard YUI Model method "load," providing an object
26
* `db.charms.add({id: [ID]})`. Finally, we load the charm's data over the
27
* network using the standard YUI Model method `load`, providing an object
28
28
* with a get_charm callable, and an optional callback (see YUI docs). Both
29
* the env and the charm store have a get_charm method, so, by design, it
30
* works easily: "charm.load(env, optionalCallback)" or
31
* "charm.load(charm_store, optionalCallback)". The get_charm method must
29
* the env and the charm store have a `get_charm` method, so, by design, it
30
* works easily: `charm.load(env, optionalCallback)` or
31
* `charm.load(charm_store, optionalCallback)`. The `get_charm` method must
32
32
* either callback using the default YUI approach for this code, a boolean
33
33
* indicating failure, and a result; or it must return what the env version
34
* does: an object with a "result" object containing the charm data, or an
35
* object with an "err" attribute.
34
* does: an object with a `result` object containing the charm data, or an
35
* object with an `err` attribute.
37
37
* In both cases, environment charms and charm store charms, a charm's
38
* "loaded" attribute is set to true once it has all the data from its
38
* `loaded` attribute is set to true once it has all the data from its
44
44
YUI.add('juju-charm-models', function(Y) {
46
46
var models = Y.namespace('juju.models');
47
var charmIdRe = /^(?:(\w+):)?(?:~(\S+)\/)?(\w+)\/(\S+?)-(\d+)$/,
48
idElements = ['scheme', 'owner', 'series', 'package_name', 'revision'],
49
Charm = Y.Base.create('charm', Y.Model, [], {
50
initializer: function() {
51
var id = this.get('id'),
47
var charmIdRe = /^(?:(\w+):)?(?:~(\S+)\/)?(\w+)\/(\S+?)-(\d+)$/;
48
var idElements = ['scheme', 'owner', 'series', 'package_name', 'revision'];
53
var Charm = Y.Base.create('charm', Y.Model, [], {
54
initializer: function() {
55
var id = this.get('id'),
52
56
parts = id && charmIdRe.exec(id),
54
if (!Y.Lang.isValue(id) || !parts) {
55
throw 'Developers must initialize charms with a well-formed id.';
58
this.on('load', function() { this.loaded = true; });
61
Y.Array.zip(idElements, parts),
62
function(pair) { self.set(pair[0], pair[1]); });
64
var tmp = [this.get('series'), this.get('package_name')],
58
if (!Y.Lang.isValue(id) || !parts) {
59
throw 'Developers must initialize charms with a well-formed id.';
62
this.on('load', function() { this.loaded = true; });
65
Y.Array.zip(idElements, parts),
66
function(pair) { self.set(pair[0], pair[1]); });
68
var tmp = [this.get('series'), this.get('package_name')],
65
69
owner = this.get('owner');
67
tmp.unshift('~' + owner);
69
this.set('full_name', tmp.join('/'));
73
[(owner ? '~' + owner : 'charms'),
75
(this.get('package_name') + '-' + this.get('revision')),
79
sync: function(action, options, callback) {
80
if (action !== 'read') {
82
'Only use the "read" action; "' + action + '" not supported.');
84
if (Y.Lang.isValue(options.get_charm)) {
90
callback(true, response);
91
} else if (response.result) {
92
callback(false, response.result);
94
// What's going on? This does not look like either of our
95
// expected signatures. Declare a loading error.
96
callback(true, response);
100
} else if (Y.Lang.isValue(options.loadByPath)) {
101
// This is a charm store.
103
this.get('charm_store_path'),
104
{ success: function(response) {
105
callback(false, response);
107
failure: function(response) {
108
callback(true, response);
112
throw 'You must supply a get_charm or loadByPath function.';
116
var data = Charm.superclass.parse.apply(this, arguments),
71
tmp.unshift('~' + owner);
73
this.set('full_name', tmp.join('/'));
77
[(owner ? '~' + owner : 'charms'),
79
(this.get('package_name') + '-' + this.get('revision')),
83
sync: function(action, options, callback) {
84
if (action !== 'read') {
86
'Only use the "read" action; "' + action + '" not supported.');
88
if (Y.Lang.isValue(options.get_charm)) {
94
callback(true, response);
95
} else if (response.result) {
96
callback(false, response.result);
98
// What's going on? This does not look like either of our
99
// expected signatures. Declare a loading error.
100
callback(true, response);
104
} else if (Y.Lang.isValue(options.loadByPath)) {
105
// This is a charm store.
107
this.get('charm_store_path'),
108
{ success: function(response) {
109
callback(false, response);
111
failure: function(response) {
112
callback(true, response);
116
throw 'You must supply a get_charm or loadByPath function.';
120
var data = Charm.superclass.parse.apply(this, arguments),
118
data.is_subordinate = data.subordinate;
119
Y.each(data, function(value, key) {
122
data.is_subordinate = data.subordinate;
123
Y.each(data, function(value, key) {
121
125
!self.attrAdded(key) ||
122
126
Y.Lang.isValue(self.get(key))) {
126
if (data.owner === 'charmers') {
131
compare: function(other, relevance, otherRelevance) {
132
// Official charms sort before owned charms.
133
// If !X.owner, that means it is owned by charmers.
134
var owner = this.get('owner'),
130
if (data.owner === 'charmers') {
135
compare: function(other, relevance, otherRelevance) {
136
// Official charms sort before owned charms.
137
// If !X.owner, that means it is owned by charmers.
138
var owner = this.get('owner'),
135
139
otherOwner = other.get('owner');
136
if (!owner && otherOwner) {
138
} else if (owner && !otherOwner) {
140
// Relevance is next most important.
141
} else if (relevance && (relevance !== otherRelevance)) {
142
// Higher relevance comes first.
143
return otherRelevance - relevance;
144
// Otherwise sort by package name, then by owner, then by revision.
140
if (!owner && otherOwner) {
142
} else if (owner && !otherOwner) {
144
// Relevance is next most important.
145
} else if (relevance && (relevance !== otherRelevance)) {
146
// Higher relevance comes first.
147
return otherRelevance - relevance;
148
// Otherwise sort by package name, then by owner, then by revision.
147
151
(this.get('package_name').localeCompare(
148
152
other.get('package_name'))) ||
149
153
(owner ? owner.localeCompare(otherOwner) : 0) ||
150
154
(this.get('revision') - other.get('revision')));
161
validator: function(val) {
162
return Y.Lang.isString(val) && !!charmIdRe.exec(val);
157
validator: function(val) {
158
return Y.Lang.isString(val) && !!charmIdRe.exec(val);
161
bzr_branch: {writeOnce: true},
162
charm_store_path: {writeOnce: true},
163
config: {writeOnce: true},
164
description: {writeOnce: true},
165
full_name: {writeOnce: true},
166
is_subordinate: {writeOnce: true},
165
bzr_branch: {writeOnce: true},
166
charm_store_path: {writeOnce: true},
167
config: {writeOnce: true},
168
description: {writeOnce: true},
169
full_name: {writeOnce: true},
170
is_subordinate: {writeOnce: true},
170
174
* Normalize created value from float to date object.
176
* @method last_change.writeOnce.setter
172
setter: function(val) {
173
if (val && val.created) {
174
// Mutating in place should be fine since this should only
175
// come from loading over the wire.
176
val.created = new Date(val.created * 1000);
181
maintainer: {writeOnce: true},
182
metadata: {writeOnce: true},
183
package_name: {writeOnce: true},
184
owner: {writeOnce: true},
185
peers: {writeOnce: true},
186
proof: {writeOnce: true},
187
provides: {writeOnce: true},
188
requires: {writeOnce: true},
178
setter: function(val) {
179
if (val && val.created) {
180
// Mutating in place should be fine since this should only
181
// come from loading over the wire.
182
val.created = new Date(val.created * 1000);
187
maintainer: {writeOnce: true},
188
metadata: {writeOnce: true},
189
package_name: {writeOnce: true},
190
owner: {writeOnce: true},
191
peers: {writeOnce: true},
192
proof: {writeOnce: true},
193
provides: {writeOnce: true},
194
requires: {writeOnce: true},
192
198
* Parse the revision number out of a string.
200
* @method revision.writeOnce.setter
194
setter: function(val) {
195
if (Y.Lang.isValue(val)) {
196
val = parseInt(val, 10);
202
setter: function(val) {
203
if (Y.Lang.isValue(val)) {
204
val = parseInt(val, 10);
205
213
* If no value is given, "cs" is used as the default.
215
* @method scheme.writeOnce.setter
207
setter: function(val) {
208
if (!Y.Lang.isValue(val)) {
214
series: {writeOnce: true},
215
summary: {writeOnce: true},
216
url: {writeOnce: true}
217
setter: function(val) {
218
if (!Y.Lang.isValue(val)) {
224
series: {writeOnce: true},
225
summary: {writeOnce: true},
226
url: {writeOnce: true}
219
230
models.Charm = Charm;
220
231
models.charmIdRe = charmIdRe;
221
236
var CharmList = Y.Base.create('charmList', Y.ModelList, [], {
227
241
models.CharmList = CharmList;