middleman 2.0.9.pre.2-x86-mingw32 → 2.0.9.pre.3-x86-mingw32

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (105) hide show
  1. data/CHANGELOG +9 -0
  2. data/features/builder.feature +6 -0
  3. data/features/data.feature +16 -1
  4. data/features/fonts.feature +11 -0
  5. data/features/relative_assets.feature +8 -2
  6. data/features/step_definitions/middleman_steps.rb +9 -2
  7. data/fixtures/data-app/config.rb +3 -0
  8. data/fixtures/data-app/data/pages.yml +6 -0
  9. data/fixtures/data-app/source/index.html.haml +1 -0
  10. data/fixtures/data-app/source/layout.haml +3 -0
  11. data/fixtures/fonts-app/config.rb +0 -0
  12. data/fixtures/fonts-app/source/fonts/StMarie-Thin.otf +0 -0
  13. data/fixtures/fonts-app/source/stylesheets/fonts.css.sass +3 -0
  14. data/fixtures/glob-app/config.rb +1 -0
  15. data/fixtures/glob-app/source/index.html.haml +6 -0
  16. data/fixtures/glob-app/source/stylesheets/site.css.sass +1 -0
  17. data/fixtures/test-app/config.rb +1 -0
  18. data/fixtures/test-app/data/test2.json +4 -0
  19. data/fixtures/test-app/source/data3.html.erb +1 -0
  20. data/lib/middleman/base.rb +8 -3
  21. data/lib/middleman/builder.rb +61 -22
  22. data/lib/middleman/cli.rb +2 -1
  23. data/lib/middleman/core_extensions/assets.rb +1 -1
  24. data/lib/middleman/core_extensions/compass.rb +6 -2
  25. data/lib/middleman/core_extensions/data.rb +20 -3
  26. data/lib/middleman/templates/html5.rb +1 -0
  27. data/lib/middleman/templates/mobile.rb +17 -0
  28. data/lib/middleman/templates/mobile/source/404.html +38 -0
  29. data/lib/middleman/templates/mobile/source/README.markdown +64 -0
  30. data/lib/middleman/templates/mobile/source/crossdomain.xml +25 -0
  31. data/lib/middleman/templates/mobile/source/css/style.css +236 -0
  32. data/lib/middleman/templates/mobile/source/default.appcache +17 -0
  33. data/lib/middleman/templates/mobile/source/humans.txt +43 -0
  34. data/lib/middleman/templates/mobile/source/img/h/apple-touch-icon.png +0 -0
  35. data/lib/middleman/templates/mobile/source/img/h/splash.png +0 -0
  36. data/lib/middleman/templates/mobile/source/img/l/apple-touch-icon-precomposed.png +0 -0
  37. data/lib/middleman/templates/mobile/source/img/l/apple-touch-icon.png +0 -0
  38. data/lib/middleman/templates/mobile/source/img/l/splash.png +0 -0
  39. data/lib/middleman/templates/mobile/source/img/m/apple-touch-icon.png +0 -0
  40. data/lib/middleman/templates/mobile/source/index.html +95 -0
  41. data/lib/middleman/templates/mobile/source/js/libs/jquery-1.5.1.js +8316 -0
  42. data/lib/middleman/templates/mobile/source/js/libs/jquery-1.5.1.min.js +16 -0
  43. data/lib/middleman/templates/mobile/source/js/libs/modernizr-custom.js +14 -0
  44. data/lib/middleman/templates/mobile/source/js/libs/respond.min.js +7 -0
  45. data/lib/middleman/templates/mobile/source/js/mylibs/helper.js +147 -0
  46. data/lib/middleman/templates/mobile/source/js/script.js +0 -0
  47. data/lib/middleman/templates/mobile/source/robots.txt +5 -0
  48. data/lib/middleman/templates/mobile/source/sitemap.xml +10 -0
  49. data/lib/middleman/templates/mobile/source/test/index.html +31 -0
  50. data/lib/middleman/templates/mobile/source/test/qunit/qunit.css +148 -0
  51. data/lib/middleman/templates/mobile/source/test/qunit/qunit.js +1265 -0
  52. data/lib/middleman/templates/mobile/source/test/tests.js +26 -0
  53. data/lib/middleman/templates/mobile/source/tools/googleanalyticsformobile/Readme.PDF +0 -0
  54. data/lib/middleman/templates/mobile/source/tools/googleanalyticsformobile/aspx/aspx1.snippet +31 -0
  55. data/lib/middleman/templates/mobile/source/tools/googleanalyticsformobile/aspx/aspx2.snippet +2 -0
  56. data/lib/middleman/templates/mobile/source/tools/googleanalyticsformobile/aspx/ga.aspx +195 -0
  57. data/lib/middleman/templates/mobile/source/tools/googleanalyticsformobile/aspx/sample.aspx +44 -0
  58. data/lib/middleman/templates/mobile/source/tools/googleanalyticsformobile/jsp/ga.jsp +225 -0
  59. data/lib/middleman/templates/mobile/source/tools/googleanalyticsformobile/jsp/jsp1.snippet +35 -0
  60. data/lib/middleman/templates/mobile/source/tools/googleanalyticsformobile/jsp/jsp2.snippet +2 -0
  61. data/lib/middleman/templates/mobile/source/tools/googleanalyticsformobile/jsp/sample.jsp +51 -0
  62. data/lib/middleman/templates/mobile/source/tools/googleanalyticsformobile/php/ga.php +176 -0
  63. data/lib/middleman/templates/mobile/source/tools/googleanalyticsformobile/php/php1.snippet +30 -0
  64. data/lib/middleman/templates/mobile/source/tools/googleanalyticsformobile/php/php2.snippet +4 -0
  65. data/lib/middleman/templates/mobile/source/tools/googleanalyticsformobile/php/sample.php +44 -0
  66. data/lib/middleman/templates/mobile/source/tools/googleanalyticsformobile/pl/ga.pl +195 -0
  67. data/lib/middleman/templates/mobile/source/tools/googleanalyticsformobile/pl/perl1.snippet +27 -0
  68. data/lib/middleman/templates/mobile/source/tools/googleanalyticsformobile/pl/perl2.snippet +1 -0
  69. data/lib/middleman/templates/mobile/source/tools/googleanalyticsformobile/pl/sample.pl +38 -0
  70. data/lib/middleman/templates/mobile/source/tools/mobile-bookmark-bubble/COPYING +202 -0
  71. data/lib/middleman/templates/mobile/source/tools/mobile-bookmark-bubble/bookmark_bubble.js +559 -0
  72. data/lib/middleman/templates/mobile/source/tools/mobile-bookmark-bubble/example/example.html +43 -0
  73. data/lib/middleman/templates/mobile/source/tools/mobile-bookmark-bubble/example/example.js +57 -0
  74. data/lib/middleman/templates/mobile/source/tools/mobile-bookmark-bubble/images/arrow.png +0 -0
  75. data/lib/middleman/templates/mobile/source/tools/mobile-bookmark-bubble/images/close.png +0 -0
  76. data/lib/middleman/templates/mobile/source/tools/mobile-bookmark-bubble/images/generate_base64_images +33 -0
  77. data/lib/middleman/templates/mobile/source/tools/mobile-bookmark-bubble/images/icon_calendar.png +0 -0
  78. data/lib/middleman/templates/mobile/source/tools/wspl/README +27 -0
  79. data/lib/middleman/templates/mobile/source/tools/wspl/databasefactory.js +45 -0
  80. data/lib/middleman/templates/mobile/source/tools/wspl/dbworker.js +324 -0
  81. data/lib/middleman/templates/mobile/source/tools/wspl/dbworker_test.html +393 -0
  82. data/lib/middleman/templates/mobile/source/tools/wspl/dbworkerstarter.js +32 -0
  83. data/lib/middleman/templates/mobile/source/tools/wspl/dbwrapper_gears.js +595 -0
  84. data/lib/middleman/templates/mobile/source/tools/wspl/dbwrapper_gears_test.html +404 -0
  85. data/lib/middleman/templates/mobile/source/tools/wspl/dbwrapper_html5.js +203 -0
  86. data/lib/middleman/templates/mobile/source/tools/wspl/dbwrapper_html5_test.html +468 -0
  87. data/lib/middleman/templates/mobile/source/tools/wspl/dbwrapperapi.js +202 -0
  88. data/lib/middleman/templates/mobile/source/tools/wspl/dbwrapperapi_test.html +51 -0
  89. data/lib/middleman/templates/mobile/source/tools/wspl/gears_resultset.js +71 -0
  90. data/lib/middleman/templates/mobile/source/tools/wspl/gears_resultset_test.html +86 -0
  91. data/lib/middleman/templates/mobile/source/tools/wspl/gears_transaction.js +196 -0
  92. data/lib/middleman/templates/mobile/source/tools/wspl/gears_transaction_test.html +221 -0
  93. data/lib/middleman/templates/mobile/source/tools/wspl/gearsutils.js +94 -0
  94. data/lib/middleman/templates/mobile/source/tools/wspl/gearsutils_test.html +84 -0
  95. data/lib/middleman/templates/mobile/source/tools/wspl/global_functions.js +72 -0
  96. data/lib/middleman/templates/mobile/source/tools/wspl/simplenotes/index.html +347 -0
  97. data/lib/middleman/templates/mobile/source/tools/wspl/simplenotes/simplenotes.js +503 -0
  98. data/lib/middleman/templates/mobile/source/tools/wspl/simplenotes/styles.css +66 -0
  99. data/lib/middleman/templates/mobile/source/tools/wspl/simplenotes/template.js +75 -0
  100. data/lib/middleman/version.rb +1 -1
  101. data/middleman-x86-mingw32.gemspec +0 -1
  102. data/middleman.gemspec +3 -2
  103. metadata +103 -6
  104. data/lib/middleman/config.ru +0 -2
  105. data/lib/middleman/features/tiny_src.rb +0 -11
@@ -0,0 +1,347 @@
1
+ <!DOCTYPE html>
2
+ <!--
3
+ Copyright 2009 Google Inc.
4
+
5
+ Licensed under the Apache License, Version 2.0 (the "License");
6
+ you may not use this file except in compliance with the License.
7
+ You may obtain a copy of the License at
8
+
9
+ http://www.apache.org/licenses/LICENSE-2.0
10
+
11
+ Unless required by applicable law or agreed to in writing, software
12
+ distributed under the License is distributed on an "AS IS" BASIS,
13
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ See the License for the specific language governing permissions and
15
+ limitations under the License.
16
+ -->
17
+
18
+ <!--
19
+
20
+ A simple application showing the use of the web storage portability layer
21
+ code and cache pattern for offline web application.
22
+
23
+ -->
24
+ <html>
25
+ <head>
26
+ <title>SimpleNotes Demo</title>
27
+ <link href="styles.css" rel="stylesheet" type="text/css" />
28
+ <script src="../global_functions.js" language="javascript"
29
+ type="text/javascript"></script>
30
+ <script src="../dbwrapperapi.js" language="javascript"
31
+ type="text/javascript"></script>
32
+ <script src="../dbwrapper_html5.js" language="javascript"
33
+ type="text/javascript"></script>
34
+ <script src="../databasefactory.js" language="javascript"
35
+ type="text/javascript"></script>
36
+ <script src="template.js" language="javascript"
37
+ type="text/javascript"></script>
38
+ <script src="simplenotes.js" language="javascript"
39
+ type="text/javascript"></script>
40
+ </head>
41
+ <body>
42
+
43
+ <div id="list-note" class="screen list-note-screen" >
44
+ <div class="title-bar">Note List</div>
45
+ <div class="top command-bar">
46
+ <button id="view-note-new">New</button>
47
+ </div>
48
+ <div class="status-bar">status bar</div>
49
+ <div class="list-note-container-empty" style="display:none;"></div>
50
+ <div class="list-note-container" style="display:none;">
51
+ <div class="list-note-item">
52
+ <a href="javascript:showNote(%noteKey%);">%subject%</a>
53
+ </div>
54
+ </div>
55
+ <div class="top command-bar">
56
+ <button id="view-note-new">New</button>
57
+ </div>
58
+ </div>
59
+
60
+ <div id="view-note" class="screen view-note-screen" style="display:none;">
61
+ <div class="title-bar">View Note</div>
62
+ <div class="top command-bar">
63
+ <button id="view-note-back">Back</button>
64
+ <button id="view-note-edit">Edit Note</button>
65
+ </div>
66
+ <div class="status-bar">status bar</div>
67
+ <div class="view-note-container" style="display:none;">
68
+ <div class="subject">%subject%</div>
69
+ <div class="body">%body%</div>
70
+ </div>
71
+ <div class="bottom command-bar">
72
+ <button id="view-note-back">Back</button>
73
+ <button id="view-note-edit">Edit Note</button>
74
+ </div>
75
+ </div>
76
+
77
+ <div id="edit-note" class="screen edit-note-screen" style="display:none;">
78
+ <div class="title-bar">Edit Note</div>
79
+ <div class="top command-bar">
80
+ <button id="edit-back-save">Save Note</button>
81
+ <button id="edit-back-revert">Revert Note</button>
82
+ </div>
83
+ <div class="status-bar">status bar</div>
84
+ <div>Subject:</div>
85
+ <textarea rows="1" style="width:100%;" id="edit-note-subject"></textarea>
86
+ <br>
87
+ <div>Note:</div>
88
+ <textarea rows="20" style="width:100%;" id="edit-note-body"></textarea>
89
+ <div class="bottom command-bar">
90
+ <button id="edit-back-save">Save Note</button>
91
+ <button id="edit-back-revert">Revert Note</button>
92
+ </div>
93
+ </div>
94
+
95
+ </body>
96
+ <script>
97
+
98
+ /**
99
+ * Tests the given DOM element for a specific CSS class.
100
+ * @param {Object} e A DOM element.
101
+ * @param {string} css A CSS class identifier to match.
102
+ * @return {boolean} true if the element has the CSS class.
103
+ */
104
+ function hasCssClass(e, css) {
105
+ return e && e.className && e.className.search(css + '( |$)') > -1;
106
+ };
107
+
108
+ /**
109
+ * Finds the parent element of a given element that has
110
+ * the specified CSS class.
111
+ * @param {Object} e A DOM element.
112
+ * @param {string} css A CSS class identifier to match.
113
+ * @param {Object} opt_stop A parent DOM element to stop at.
114
+ * @return {Object} A DOM element parent of e or null.
115
+ */
116
+ function findParentOfClass(e, css, opt_stop) {
117
+ var stop = opt_stop || document;
118
+ while (e && e != stop && !hasCssClass(e, css)) {
119
+ e = e.parentElement;
120
+ }
121
+ return hasCssClass(e, css) ? e : null;
122
+ };
123
+
124
+ /**
125
+ * Navigates to the note creation screen.
126
+ */
127
+ function editNote(event) {
128
+ google.logger('editNote: <' + currentNote.subject + '>');
129
+ hideBlock('#edit-note .status-bar');
130
+
131
+ var subject = document.getElementById('edit-note-subject');
132
+ var body = document.getElementById('edit-note-body');
133
+ subject.value = currentNote.subject;
134
+ body.value = currentNote.body;
135
+
136
+ google.logger('end of editNote');
137
+ }
138
+
139
+ /**
140
+ * Saves a note.
141
+ */
142
+ function saveNote(event) {
143
+ google.logger('saveNote: do database or other activity here');
144
+ // Copy contents from fields and save.
145
+ var subject = document.getElementById('edit-note-subject');
146
+ var body = document.getElementById('edit-note-body');
147
+
148
+ cache.applyUiChange(currentNote.noteKey, subject.value,
149
+ body.value, showNote);
150
+ google.logger('saveNote');
151
+ }
152
+
153
+ /**
154
+ * Abandons the note.
155
+ */
156
+ function abandonNote(event) {
157
+ google.logger('abandonNote: do database or other activity here');
158
+ showNote(currentNote.noteKey);
159
+ google.logger('abandonNote');
160
+ }
161
+
162
+ /**
163
+ * Prepares to edit a new note.
164
+ */
165
+ function newNote(event) {
166
+ google.logger('newNote: do database or other activity here');
167
+ alert('not implemented');
168
+ // grab the contents and inject into the database.
169
+ var subject = document.getElementById('edit-note-subject');
170
+ var body = document.getElementById('edit-note-body');
171
+ subject.value = '';
172
+ body.value = '';
173
+ google.logger('newNote');
174
+ }
175
+
176
+ /**
177
+ * Maps button names to customizer functions.
178
+ */
179
+ var buttonHandlerSpecification = {
180
+ 'view-note-new': ['edit-note', newNote],
181
+ 'view-note-back': ['list-note', showNoteList],
182
+ 'view-note-edit': ['edit-note', editNote],
183
+ 'edit-back-save': ['view-note', saveNote],
184
+ 'edit-back-revert': ['view-note', abandonNote]
185
+ };
186
+
187
+ /**
188
+ * Hides a block specified by a CSS selector and parent node.
189
+ * @param {string} selector A CSS selector string.
190
+ * @param {Object} opt_el A DOM element.
191
+ */
192
+ function hideBlock(selector, opt_el) {
193
+ var el = opt_el || document;
194
+ var targetEl = el.querySelector(selector);
195
+ targetEl.style.display = 'none';
196
+ }
197
+
198
+ /**
199
+ * Shows a block specified by a CSS selector and parent node, inserting
200
+ * the specified string as the innerHTML property of the selected block.
201
+ * @param {string} selector A CSS selector
202
+ * @param {string} html String to assign to the innerHTML property.
203
+ * @param {Object} opt_el A DOM element.
204
+ */
205
+ function showBlock(selector, html, opt_el) {
206
+ var el = opt_el || document;
207
+ var targetEl = el.querySelector(selector);
208
+ if (html) targetEl.innerHTML = html;
209
+ targetEl.style.display = 'block';
210
+ }
211
+
212
+ /**
213
+ * Handles all button events.
214
+ */
215
+ function buttonHandler(event) {
216
+ google.logger('clicked on: ' + event.target.id);
217
+
218
+ var currentScreen = findParentOfClass(event.target, 'screen');
219
+ var nextScreen = document.getElementById(
220
+ buttonHandlerSpecification[event.target.id][0]);
221
+ var helperFunction = buttonHandlerSpecification[event.target.id][1];
222
+
223
+ // All button handlers have a common implementation.
224
+ // 1. hide current screen
225
+ currentScreen.style.display = 'none';
226
+
227
+ // 2. show new screen
228
+ nextScreen.style.display = 'block';
229
+
230
+ // 3. Activate status
231
+ showBlock('.status-bar', 'Working...', nextScreen);
232
+
233
+ // 4. Call customizer function (which will probably query the model
234
+ // stored in the cache.
235
+ helperFunction(event);
236
+
237
+ // 5. A (possibly asynchronous) callback from the customizer will
238
+ // finish the action by running the appropriate view function.
239
+ google.logger('end of button Handler');
240
+ }
241
+
242
+ /**
243
+ * Call back from a specific JavaScript url to show a specific
244
+ * note.
245
+ * @param {number} noteKey
246
+ * @return {boolean}
247
+ */
248
+ function showNote(noteKey) {
249
+ hideBlock('#list-note');
250
+ showBlock('#view-note .status-bar', 'Working...');
251
+ showBlock('#view-note');
252
+ cache.getValues('fullnote', [noteKey], renderFullNote);
253
+ return false;
254
+ }
255
+
256
+ /**
257
+ * Generates HTML for a note and display it.
258
+ * @param {Object} oneNote
259
+ * @param {boolean} complete True if the note has been provided or
260
+ * if an updated note is arriving. (Necessary for future support for
261
+ * sub-note updates.)
262
+ */
263
+ function renderFullNote(oneNote, complete) {
264
+ google.logger('renderFullNote');
265
+
266
+ showBlock('.view-note-container', noteViewTemplate.process(oneNote));
267
+ if (complete) {
268
+ hideBlock('#view-note .status-bar');
269
+ }
270
+ currentNote = oneNote;
271
+ google.logger('done renderFullNote');
272
+ }
273
+
274
+ /**
275
+ * Generates HTML for a note list and display it.
276
+ * @param {Array.<Object>} noteHeaders
277
+ * @param {boolean} complete True if all in-existence notes that lie
278
+ * in the query range have been provided even if less than what was
279
+ * requested.
280
+ */
281
+ function renderNoteList(noteHeaders, complete) {
282
+ google.logger('renderNoteList: ' + noteHeaders.length);
283
+
284
+ if (noteHeaders.length == 0) {
285
+ showBlock('.list-note-container-empty', 'No notes');
286
+ } else {
287
+ var notes = noteHeaders.map(function(n) {
288
+ return noteListTemplate.process(n);
289
+ });
290
+ showBlock('.list-note-container', notes.join());
291
+ }
292
+ if (complete) {
293
+ hideBlock('#list-note .status-bar');
294
+ }
295
+ google.logger('done renderNoteList');
296
+ }
297
+
298
+ /**
299
+ * Query for an updated note list.
300
+ */
301
+ function showNoteList() {
302
+ google.logger('showNoteList');
303
+ cache.getValues('list', [0,20], renderNoteList);
304
+ currentNote = undefined;
305
+ google.logger('end of showNoteList');
306
+ }
307
+
308
+ /**
309
+ * A constructor for the controller. Sets up the UI and
310
+ * queries the cache for initial notelist contents.
311
+ */
312
+ function createController() {
313
+ google.logger('createController:');
314
+ var buttons = document.querySelectorAll('.command-bar button');
315
+ Array.prototype.forEach.call(buttons, function(b) {
316
+ b.addEventListener('click', buttonHandler);
317
+ });
318
+ google.logger('controller is live');
319
+ showNoteList();
320
+ }
321
+
322
+ google.logger('about to fetch the template');
323
+ var currentNote = undefined;
324
+
325
+ var noteListTemplate = new google.wspl.simplenotes.Template(
326
+ document.querySelector('.list-note-container').innerHTML);
327
+
328
+ var noteViewTemplate = new google.wspl.simplenotes.Template(
329
+ document.querySelector('.view-note-container').innerHTML);
330
+
331
+ // var noteEditTemplate = new google.wspl.simplenotes.Template();
332
+
333
+ google.logger('created the template');
334
+
335
+ // debugging code... tear out...
336
+ // test the template construction...
337
+ // showBlock('.list-note-container', noteListTemplate.process(
338
+ // {noteKey: 123, subject: 'hi rob'}));
339
+
340
+
341
+ google.logger('loaded database');
342
+ var cache = new google.wspl.simplenotes.Cache();
343
+ cache.startCache(createController);
344
+
345
+ google.logger('finished loading page');
346
+ </script>
347
+ </html>
@@ -0,0 +1,503 @@
1
+ /*
2
+ Copyright 2009 Google Inc.
3
+
4
+ Licensed under the Apache License, Version 2.0 (the "License");
5
+ you may not use this file except in compliance with the License.
6
+ You may obtain a copy of the License at
7
+
8
+ http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+ Unless required by applicable law or agreed to in writing, software
11
+ distributed under the License is distributed on an "AS IS" BASIS,
12
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ See the License for the specific language governing permissions and
14
+ limitations under the License.
15
+ */
16
+
17
+ /**
18
+ * @fileoverview A concrete example of the cache pattern for building an offline
19
+ * webapplication: a cache for SimpleNotes.
20
+ */
21
+
22
+ google.wspl.simplenotes = google.wspl.simplenotes || {};
23
+ google.logger('start simplenotes.js');
24
+
25
+ /**
26
+ * Status keys for the write buffer.
27
+ * @enum {number}
28
+ */
29
+ google.wspl.simplenotes.WriteBufferStates = {
30
+ /**
31
+ * The update is in flight to the server.
32
+ */
33
+ INFLIGHT: 1,
34
+
35
+ /**
36
+ * The update needs to be (re)sent to the server but is not in flight.
37
+ */
38
+ SEND: 2,
39
+
40
+ /**
41
+ * The update needs to be applied to the cached notes.
42
+ */
43
+ REAPPLY: 8
44
+ };
45
+
46
+ /**
47
+ * Creates a SimpleNotes cache wrapping a backing database.
48
+ * @constructor
49
+ */
50
+ google.wspl.simplenotes.Cache = function() {
51
+ this.dbms_ = google.wspl.DatabaseFactory.createDatabase(
52
+ 'simple-notes', 'http://yourdomain/dbworker.js');
53
+
54
+ /**
55
+ * Cache directory is a two-tuple over a range. (Holes
56
+ * must be allowed to support delection.)
57
+ * This is the lower (inclusive) bound of the cached range.
58
+ * @type {number}
59
+ * @private
60
+ */
61
+ this.start_ = -1;
62
+
63
+ /**
64
+ *
65
+ * This is the upper (inclusive) bound of the cached range.
66
+ * @type {number}
67
+ * @private
68
+ */
69
+ this.end_ = -1;
70
+
71
+ /**
72
+ * Start of range of notes known to exist on server at time of last
73
+ * response.
74
+ * @param {number}
75
+ */
76
+ this.serverStart_ = -1;
77
+
78
+ /**
79
+ * End of range of notes known to exist on server at time of last
80
+ * response.
81
+ * @param {number}
82
+ */
83
+ this.serverEnd_ = -1;
84
+
85
+ /**
86
+ * Time of last refresh.
87
+ * @type {number}
88
+ * @private
89
+ */
90
+ this.lastRefresh_ = -1;
91
+
92
+ /**
93
+ * Last missing query.
94
+ * @type {Object}
95
+ * @private
96
+ */
97
+ this.lastMiss_ = undefined;
98
+ };
99
+
100
+ /**
101
+ * Interval between refreshes in milliseconds.
102
+ * @type {number}
103
+ * @private
104
+ */
105
+ google.wspl.simplenotes.Cache.TIME_BETWEEN_REFRESH_ = 2000;
106
+
107
+ google.wspl.simplenotes.Cache.CREATE_CACHED_NOTES_ =
108
+ new google.wspl.Statement(
109
+ 'CREATE TABLE IF NOT EXISTS cached_notes (' +
110
+ 'noteKey INTEGER UNIQUE PRIMARY KEY,' +
111
+ 'subject TEXT,' +
112
+ 'body TEXT' +
113
+ ');'
114
+ );
115
+
116
+ google.wspl.simplenotes.Cache.CREATE_WRITE_BUFFER_ =
117
+ new google.wspl.Statement(
118
+ 'CREATE TABLE IF NOT EXISTS write_buffer (' +
119
+ 'sequence INTEGER UNIQUE PRIMARY KEY AUTOINCREMENT,' +
120
+ 'noteKey INTEGER,' +
121
+ 'status INTEGER,' +
122
+ 'subject TEXT,' +
123
+ 'body TEXT' +
124
+ ');'
125
+ );
126
+
127
+ google.wspl.simplenotes.Cache.DETERMINE_MIN_KEY_ =
128
+ new google.wspl.Statement(
129
+ 'SELECT MIN(noteKey) as minNoteKey FROM cached_notes;');
130
+
131
+ google.wspl.simplenotes.Cache.DETERMINE_MAX_KEY_ =
132
+ new google.wspl.Statement(
133
+ 'SELECT MAX(noteKey) as maxNoteKey FROM cached_notes;');
134
+
135
+ /**
136
+ * Builds a cache and writebuffer combination for notes and then
137
+ * invokes the given callback.
138
+ * @param {function) callback.
139
+ */
140
+ google.wspl.simplenotes.Cache.prototype.startCache = function(callback) {
141
+ google.logger('startCache');
142
+ var statc = 0;
143
+ var self = this;
144
+
145
+ var perStatCallback = function(tx, result) {
146
+ google.logger('perStatCallback');
147
+ if (statc == 4) {
148
+ self.start_ = (result.isValidRow()) ? result.getRow().minNoteKey : -1;
149
+ self.serverStart_ = self.start_; // Temporary. Remove when server exists.
150
+ } else if (statc == 5) {
151
+ self.end_ = (result.isValidRow()) ? result.getRow().maxNoteKey : -1;
152
+ self.serverEnd_ = self.end_; // Temporary. Remove when server exists.
153
+ }
154
+ statc++;
155
+ };
156
+
157
+ this.dbms_.executeAll([
158
+ google.wspl.simplenotes.Cache.CREATE_CACHED_NOTES_,
159
+ google.wspl.simplenotes.Cache.CREATE_WRITE_BUFFER_,
160
+ google.wspl.simplenotes.Cache.CREATE_UPDATE_TRIGGER_,
161
+ google.wspl.simplenotes.Cache.CREATE_REPLAY_TRIGGER_,
162
+ google.wspl.simplenotes.Cache.DETERMINE_MIN_KEY_,
163
+ google.wspl.simplenotes.Cache.DETERMINE_MAX_KEY_],
164
+ {onSuccess: perStatCallback, onFailure: this.logError_},
165
+ {onSuccess: callback, onFailure: this.logError_});
166
+ google.logger('finished startCache');
167
+ };
168
+
169
+ /**
170
+ * Stub function to be replaced with a server communication.
171
+ * @param {Array.<Object>} updates Payload to send to server.
172
+ */
173
+ google.wspl.simplenotes.Cache.prototype.sendXhrPost = function(updates) {
174
+ google.logger('Should dispatch XHR to server now.');
175
+ };
176
+
177
+ /**
178
+ * @type {google.wspl.Statement}
179
+ * @private
180
+ */
181
+ google.wspl.simplenotes.Cache.LIST_CACHED_NOTES_ =
182
+ new google.wspl.Statement(
183
+ 'SELECT noteKey, subject from cached_notes WHERE ' +
184
+ 'noteKey >= ? AND ' +
185
+ 'noteKey <= ? ' +
186
+ ';'
187
+ );
188
+
189
+ /**
190
+ * Tests if the given range is stored in the cache.
191
+ * Note that this mechanism requires extension to handle the
192
+ * creation of new notes.
193
+ * @param {number} start Lower bound (inclusive) on range.
194
+ * @param {number} end Uppder bound (inclusive) on range.
195
+ * @private
196
+ */
197
+ google.wspl.simplenotes.Cache.prototype.isCacheHit_ = function(start, end) {
198
+ return start >= this.start_ && end <= this.end_
199
+ };
200
+
201
+ /**
202
+ * Logs a possibly useful error message.
203
+ * @param {Object} error An error descriptor.
204
+ * @private
205
+ */
206
+ google.wspl.simplenotes.Cache.prototype.logError_ = function(error) {
207
+ google.logger('Simple Notes Cache is sad: ' + error);
208
+ };
209
+
210
+ /**
211
+ * Queries the cache for a list of note headers.
212
+ * @param {number} start The lower id in a range of notes.
213
+ * @param {number} end The higher id in a range of notes.
214
+ * @param {function(Array.<Object>)} valuesCallback A function to call
215
+ * with the result set after the transaction has completed.
216
+ * @private
217
+ */
218
+ google.wspl.simplenotes.Cache.prototype.getNoteList_ = function(start, end,
219
+ valuesCallback) {
220
+ var notes = [];
221
+
222
+ var accumulateResults = function(tx, result) {
223
+ for(; result.isValidRow(); result.next()) {
224
+ notes.push(result.getRow());
225
+ google.logger('pushed...');
226
+ }
227
+ };
228
+
229
+ var inTransactionGetNotes = function(tx) {
230
+ tx.execute(google.wspl.simplenotes.Cache.LIST_CACHED_NOTES_.
231
+ createStatement([start, end]), {
232
+ onSuccess: accumulateResults,
233
+ onFailure: this.logError_});
234
+ };
235
+
236
+ var hit = this.isCacheHit_(start, end);
237
+ this.dbms_.createTransaction(inTransactionGetNotes, {onSuccess: function() {
238
+ valuesCallback(notes, hit);
239
+ }, onFailure: this.logError_});
240
+
241
+ if (hit) {
242
+ this.fetchFromServer(this.start_, this.end_); // Refresh
243
+ } else {
244
+ this.fetchFromServer(Math.min(this.start_, start), Math.max(this.end_, end));
245
+ this.lastMiss_ = {callback: valuesCallback, start: start, end: end};
246
+ }
247
+ };
248
+
249
+ /**
250
+ * @type {google.wspl.Statement}
251
+ * @private
252
+ */
253
+ google.wspl.simplenotes.Cache.GET_ONE_NOTE_ =
254
+ new google.wspl.Statement(
255
+ 'SELECT noteKey, subject, body from cached_notes WHERE ' +
256
+ 'noteKey = ? ' +
257
+ ';'
258
+ );
259
+
260
+ /**
261
+ * Queries the cache for a list of note headers.
262
+ * @param {number} noteId The note to get from the cache.
263
+ * @param {function(Array.<Object>)} valuesCallback A function to call
264
+ * with the result set after the transaction has completed.
265
+ * @private
266
+ */
267
+ google.wspl.simplenotes.Cache.prototype.getOneNote_ = function(noteId,
268
+ callback) {
269
+ var note;
270
+
271
+ this.dbms_.execute(google.wspl.simplenotes.Cache.GET_ONE_NOTE_.
272
+ createStatement([noteId]),
273
+ {onSuccess: function(tx, result) { note = result.getRow(); },
274
+ onFailure: this.logError_},
275
+ {onSuccess: function() { callback(note, true); },
276
+ onFailure: this.logError_});
277
+ };
278
+
279
+ /**
280
+ * Queries the cache for either a list of notes or a single note including
281
+ * its body.
282
+ * @param {string} type The kind of values desired: 'list' or 'fullnote'.
283
+ * @param {Array.<number>} query The query for the values.
284
+ * @param {function(Array.<Object>)} valuesCallback A function to call
285
+ * with the result set after the transaction has completed.
286
+ */
287
+ google.wspl.simplenotes.Cache.prototype.getValues = function(type,
288
+ query, valuesCallback) {
289
+
290
+ // Reduce any query to what would be available from the server
291
+ query[0] = Math.max(this.serverStart_, query[0]);
292
+ query[1] = Math.min(this.serverEnd_, query[1]);
293
+
294
+ if (type == 'list') {
295
+ this.getNoteList_(query[0], query[1], valuesCallback);
296
+ } else if (type == 'fullnote') {
297
+ this.getOneNote_(query[0], valuesCallback);
298
+ }
299
+ };
300
+
301
+ /**
302
+ * SQL trigger to insert a new change from write buffer to
303
+ * cache.
304
+ * @private
305
+ */
306
+ google.wspl.simplenotes.Cache.CREATE_UPDATE_TRIGGER_ =
307
+ new google.wspl.Statement(
308
+ 'CREATE TRIGGER IF NOT EXISTS updateTrigger ' +
309
+ 'AFTER INSERT ON write_buffer ' +
310
+ 'BEGIN ' +
311
+ ' REPLACE INTO cached_notes ' +
312
+ ' SELECT noteKey, subject, body ' +
313
+ ' FROM write_buffer WHERE status & 8 = 8; ' +
314
+ ' UPDATE write_buffer SET status = status & ~ 8; ' +
315
+ 'END;'
316
+ );
317
+
318
+ /**
319
+ * SQL trigger to replay changes from the write buffer to
320
+ * the cache.
321
+ * @private
322
+ */
323
+ google.wspl.simplenotes.Cache.CREATE_REPLAY_TRIGGER_ =
324
+ new google.wspl.Statement(
325
+ 'CREATE TRIGGER IF NOT EXISTS replayTrigger ' +
326
+ 'AFTER UPDATE ON write_buffer WHEN NEW.status & 8 = 8 ' +
327
+ 'BEGIN ' +
328
+ ' REPLACE INTO cached_notes ' +
329
+ ' SELECT noteKey, subject, body ' +
330
+ ' FROM write_buffer ' +
331
+ ' WHERE noteKey = NEW.noteKey ' +
332
+ ' ORDER BY sequence ASC;' +
333
+ ' UPDATE write_buffer SET status = status & ~ 8; ' +
334
+ 'END;'
335
+ );
336
+
337
+
338
+ /**
339
+ * SQL statement to mark actions for replay.
340
+ */
341
+ google.wspl.simplenotes.Cache.MARK_FOR_REPLAY =
342
+ new google.wspl.Statement(
343
+ 'UPDATE write_buffer SET status = status | 8;');
344
+
345
+ /**
346
+ * SQL statement to insert notes updates.
347
+ * @type {!google.wspl.Statement}
348
+ * @private
349
+ */
350
+ google.wspl.simplenotes.Cache.INSERT_UI_UPDATE_ =
351
+ new google.wspl.Statement(
352
+ 'INSERT INTO write_buffer (' +
353
+ 'noteKey, status, subject, body' +
354
+ ') ' + 'VALUES(?,?,?,?);');
355
+
356
+ /**
357
+ * Updates the given entry and write a new write buffer entry.
358
+ * @param {number} noteKey
359
+ * @param {string} subject
360
+ * @param {string} body
361
+ * @param {function(number)} ackCallback
362
+ */
363
+ google.wspl.simplenotes.Cache.prototype.applyUiChange = function(noteKey,
364
+ subject, body, ackCallback) {
365
+ var self = this;
366
+ var update = [noteKey, 2 | 8, subject, body];
367
+ var stat = google.wspl.simplenotes.Cache.INSERT_UI_UPDATE_.createStatement(
368
+ update);
369
+
370
+ this.dbms_.execute(stat, null, {onSuccess: function() {
371
+ google.logger('applyUiChange cb');
372
+ ackCallback(noteKey);
373
+ }, onFailure: function (error) {
374
+ self.logError_(error);
375
+ ackCallback(-1);
376
+ }});
377
+ };
378
+
379
+ /**
380
+ * SQL statement to insert notes updates.
381
+ * @type {!google.wspl.Statement}
382
+ * @private
383
+ */
384
+ google.wspl.simplenotes.Cache.INSERT_NOTE_ =
385
+ new google.wspl.Statement(
386
+ 'REPLACE INTO cached_notes (noteKey, subject, body) ' +
387
+ 'VALUES(?,?,?);' );
388
+
389
+ /**
390
+ * SQL statement to force replay of pending actions by setting a bit
391
+ * flag on each write-buffer row indicating that it should be reapplied
392
+ * to the contents of the cache.
393
+ * @type {!google.wspl.Statement}
394
+ * @private
395
+ */
396
+ google.wspl.simplenotes.Cache.FORCE_REPLAY_ =
397
+ new google.wspl.Statement(
398
+ 'UPDATE write_buffer SET status = status | 8;' );
399
+
400
+ /**
401
+ * SQL statement to delete notes no longer to be cached.
402
+ * @type {!google.wspl.Statement}
403
+ * @private
404
+ */
405
+ google.wspl.simplenotes.Cache.EVICT_ =
406
+ new google.wspl.Statement(
407
+ 'DELETE FROM cached_notes WHERE noteKey < ? OR noteKey > ?;');
408
+
409
+ /**
410
+ * Applies the changes delivered from the server by first inserting
411
+ * them into the cache and reapplying the write-buffer to the cache.
412
+ * @param {!Array.<Object>} notes An array of arrays.
413
+ */
414
+ google.wspl.simplenotes.Cache.prototype.insertUpdate = function(notes) {
415
+ var self = this; var stats = [];
416
+ var start = notes[0].noteKey;
417
+ var end = notes[0].noteKey;
418
+
419
+ for (var i = 0; i < notes.length; i++) {
420
+ stats.push(google.wspl.simplenotes.Cache.INSERT_NOTE_.
421
+ createStatement([notes[i].noteKey, notes[i].subject, notes[i].body]));
422
+ start = Math.min(start, notes[0].noteKey);
423
+ end = Math.max(end, notes[0].noteKey);
424
+ }
425
+ stats.push(google.wspl.simplenotes.Cache.EVICT_.createStatement([start, end]));
426
+ stats.push(google.wspl.simplenotes.Cache.FORCE_REPLAY_);
427
+
428
+ var inTrans = function(tx) {
429
+ self.start_ = start;
430
+ self.end_ = end;
431
+ tx.executeAll(stats);
432
+ };
433
+
434
+ var afterInsert = function(tx) {
435
+ if (this.lastMiss_ &&
436
+ this.isCacheHit_(this.lastMiss_.start, this.lastMiss_.end)) {
437
+ this.lastMiss_.callback(notes);
438
+ this.lastMiss_ = undefined;
439
+ }
440
+ };
441
+
442
+ this.dbms_.createTransaction(inTrans, {onSuccess: afterInsert,
443
+ onError: this.logError_});
444
+ };
445
+
446
+ /**
447
+ * SQL statement to force replay of pending actions by setting a bit
448
+ * flag on each write-buffer row indicating that it should be reapplied
449
+ * to the contents of the cache.
450
+ * @type {!google.wspl.Statement}
451
+ * @private
452
+ */
453
+ google.wspl.simplenotes.Cache.GET_UPDATES_TO_RESEND_ =
454
+ new google.wspl.Statement(
455
+ 'SELECT noteKey, subject, body FROM write_buffer WHERE status & 2 = 2;');
456
+
457
+
458
+
459
+ /**
460
+ * SQL statement to mark write buffer statements as inflight.
461
+ * @type {!google.wspl.Statement}
462
+ * @private
463
+ */
464
+ google.wspl.simplenotes.Cache.MARK_AS_INFLIGHT_ =
465
+ new google.wspl.Statement(
466
+ 'UPDATE write_buffer SET status = status & ~2 | 1 WHERE status & 2 = 2;');
467
+
468
+ /**
469
+ * Fetches new material from the server as required.
470
+ * @param {number} start
471
+ * @param {number} end
472
+ * @param {function} opt_valueCallBack
473
+ */
474
+ google.wspl.simplenotes.Cache.prototype.fetchFromServer = function(start,
475
+ end) {
476
+ google.logger('fetchFromServer');
477
+ var now = this.dbms_.getCurrentTime();
478
+ if (start >= this.start_ && end <= this.end_ &&
479
+ now - this.lastRefresh_ <
480
+ google.wspl.simplenotes.Cache.TIME_BETWEEN_REFRESH_) {
481
+ return;
482
+ }
483
+
484
+ var updates = []; var self = this; var flag = 1; var sql = []
485
+ sql.push(google.wspl.simplenotes.Cache.GET_UPDATES_TO_RESEND_);
486
+ sql.push(google.wspl.simplenotes.Cache.MARK_AS_INFLIGHT_);
487
+
488
+ var accumulateUpdates = function(tx, rs) {
489
+ if (flag == 1) {
490
+ for(; rs.isValidRow(); rs.next()) { updates.push(['u', rs.getRow()]); }
491
+ flag++;
492
+ }
493
+ };
494
+
495
+ var ackAndPost = function() {
496
+ updates.push(['q', {start: start, end: end}]);
497
+ self.sendXhrPost(updates);
498
+ };
499
+
500
+ this.dbms_.executeAll(sql,
501
+ {onSuccess: accumulateUpdates, onFailure: this.logError_},
502
+ {onSuccess: ackAndPost, onFailure: this.logError_});
503
+ };