server.js 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365
  1. /**
  2. * Object for communicating with the external servers. This includes functionality for
  3. * saving and loading files, logging events, saving completions, and retrieving history.
  4. *
  5. * @constructor
  6. * @this {BlockPyServer}
  7. * @param {Object} main - The main BlockPy instance
  8. */
  9. function BlockPyServer(main) {
  10. this.main = main;
  11. // Add the LocalStorage connection
  12. // Presently deprecated, but we should investigate this
  13. this.storage = new LocalStorageWrapper("BLOCKPY");
  14. this.saveTimer = {};
  15. this.presentationTimer = null;
  16. this.overlay = null;
  17. // For managing "walks" that let us rerun stored code
  18. this.inProgressWalks = [];
  19. this.createSubscriptions();
  20. }
  21. BlockPyServer.prototype.createSubscriptions = function() {
  22. var server = this, model = this.main.model;
  23. model.program.subscribe(function() { server.saveCode(); });
  24. model.assignment.name.subscribe(function(e) { server.saveAssignment();});
  25. model.assignment.introduction.subscribe(function(e) { server.saveAssignment(); });
  26. model.assignment.parsons.subscribe(function(e) { server.saveAssignment(); });
  27. model.assignment.importable.subscribe(function(e) { server.saveAssignment(); });
  28. model.assignment.disable_algorithm_errors.subscribe(function(e) { server.saveAssignment(); });
  29. model.assignment.disable_timeout.subscribe(function(e) { server.saveAssignment(); });
  30. model.assignment.initial_view.subscribe(function(e) { server.saveAssignment(); });
  31. model.assignment.files.subscribe(function(e) { server.saveAssignment(); });
  32. //model.settings.editor.subscribe(function(newValue) { server.logEvent('editor', newValue); });
  33. model.execution.show_trace.subscribe(function(newValue) { server.logEvent('trace', newValue); });
  34. model.execution.trace_step.subscribe(function(newValue) { server.logEvent('trace_step', newValue); });
  35. };
  36. /**
  37. *
  38. * Some subscriptions have to happen after other things have been loaded.
  39. * Right now this is just after CORGIS libraries have been loaded, but maybe
  40. * we'll add more later and this will need to be refactored.
  41. *
  42. */
  43. BlockPyServer.prototype.finalizeSubscriptions = function() {
  44. var server = this, model = this.main.model;
  45. model.assignment.modules.subscribe(function(e) { server.saveAssignment(); });
  46. };
  47. BlockPyServer.prototype.TIMER_DELAY = 1000;
  48. BlockPyServer.prototype.createServerData = function() {
  49. var assignment = this.main.model.assignment;
  50. var d = new Date();
  51. var seconds = Math.round(d.getTime() / 1000);
  52. data = {
  53. 'assignment_id': assignment.assignment_id(),
  54. 'group_id': assignment.group_id,
  55. 'course_id': assignment.course_id,
  56. 'student_id': assignment.student_id,
  57. 'version': assignment.version(),
  58. 'timestamp': seconds
  59. };
  60. if (this.main.model.settings.log_id() != null) {
  61. data['log_id'] = this.main.model.settings.log_id();
  62. }
  63. return data;
  64. }
  65. BlockPyServer.prototype.setStatus = function(status, server_error) {
  66. this.main.model.status.server(status);
  67. if (server_error !== undefined) {
  68. this.main.model.status.server_error(server_error);
  69. } else {
  70. this.main.model.status.server_error('');
  71. }
  72. }
  73. BlockPyServer.prototype.defaultResponseWithoutVersioning = function(response) {
  74. if (response.success) {
  75. this.setStatus('Saved');
  76. } else {
  77. console.error(response);
  78. this.setStatus('Error', response.message);
  79. }
  80. }
  81. BlockPyServer.prototype.defaultResponse = function(response) {
  82. /*console.log(response);
  83. if (!response.is_version_correct) {
  84. this.setStatus('Out of date');
  85. } else */if (response.success) {
  86. this.setStatus('Saved');
  87. } else {
  88. console.error(response);
  89. this.setStatus('Error', response.message);
  90. }
  91. }
  92. BlockPyServer.prototype.defaultFailure = function(error, textStatus) {
  93. this.setStatus('Disconnected', "Could not access server!\n"+textStatus);
  94. }
  95. BlockPyServer.prototype.logEvent = function(event_name, action, body) {
  96. if (this.main.model.server_is_connected('log_event')) {
  97. var data = this.createServerData();
  98. data['event'] = event_name;
  99. data['action'] = action;
  100. data['body'] = (body === undefined) ? '' : body;
  101. this.setStatus('Logging');
  102. // Trigger request
  103. $.post(this.main.model.constants.urls.log_event, data,
  104. this.defaultResponse.bind(this))
  105. .fail(this.defaultFailure.bind(this));
  106. } else {
  107. this.setStatus('Offline', "Server is not connected! (Log Event)");
  108. }
  109. }
  110. BlockPyServer.prototype.markSuccess = function(success, callback, hide_correctness) {
  111. var model = this.main.model;
  112. var server = this;
  113. if (model.server_is_connected('save_success')) {
  114. var data = this.createServerData();
  115. data['code'] = model.programs.__main__;
  116. data['status'] = success;
  117. data['hide_correctness'] = hide_correctness;
  118. this.main.components.editor.getPngFromBlocks(function(pngData, img) {
  119. data['image'] = pngData;
  120. if (img.remove) {
  121. img.remove();
  122. }
  123. server.setStatus('Saving');
  124. // Trigger request
  125. $.post(model.constants.urls.save_success, data,
  126. function(response) {
  127. if (response.success) {
  128. if (response.submitted) {
  129. server.setStatus('Saved');
  130. } else {
  131. server.setStatus('Ungraded', response.message);
  132. }
  133. if (success && callback) {
  134. callback(data);
  135. }
  136. } else {
  137. console.error(response);
  138. server.setStatus('Error', response.message);
  139. if (success && callback) {
  140. callback(data);
  141. }
  142. }
  143. })
  144. .fail(server.defaultFailure.bind(server));
  145. });
  146. } else {
  147. server.setStatus('Offline', "Server is not connected! (Mark Success)");
  148. }
  149. };
  150. BlockPyServer.prototype.saveAssignment = function() {
  151. var model = this.main.model;
  152. if (model.server_is_connected('save_assignment') &&
  153. model.settings.auto_upload()) {
  154. var data = this.createServerData();
  155. data['introduction'] = model.assignment.introduction();
  156. data['parsons'] = model.assignment.parsons();
  157. data['initial'] = model.assignment.initial_view();
  158. data['importable'] = model.assignment.importable();
  159. data['disable_algorithm_errors'] = model.assignment.disable_algorithm_errors();
  160. data['disable_timeout'] = model.assignment.disable_timeout();
  161. data['name'] = model.assignment.name();
  162. data['modules'] = model.assignment.modules().join(','); // TODO: hackish, broken if ',' is in name
  163. data['files'] = model.assignment.files().join(','); // TODO: hackish, broken if ',' is in name
  164. var server = this;
  165. this.setStatus('Saving');
  166. clearTimeout(this.presentationTimer);
  167. // Trigger request
  168. this.presentationTimer = setTimeout(function() {
  169. $.post(model.constants.urls.save_assignment, data,
  170. server.defaultResponseWithoutVersioning.bind(server))
  171. .fail(server.defaultFailure.bind(server));
  172. }, this.TIMER_DELAY);
  173. } else {
  174. this.setStatus('Offline', "Server is not connected! (Save Assignment)");
  175. }
  176. }
  177. BlockPyServer.prototype.saveCode = function() {
  178. var model = this.main.model;
  179. if (model.server_is_connected('save_code') &&
  180. model.settings.auto_upload()) {
  181. var data = this.createServerData();
  182. var filename = model.settings.filename();
  183. data['filename'] = filename;
  184. data['code'] = model.programs[filename]();
  185. var server = this;
  186. this.setStatus('Saving');
  187. if (this.saveTimer[filename]) {
  188. clearTimeout(this.saveTimer[filename]);
  189. }
  190. this.saveTimer[filename] = setTimeout(function() {
  191. $.post(model.constants.urls.save_code, data,
  192. filename == '__main__'
  193. ? server.defaultResponse.bind(server)
  194. : server.defaultResponseWithoutVersioning.bind(server))
  195. .fail(server.defaultFailure.bind(server));
  196. }, this.TIMER_DELAY);
  197. } else {
  198. this.setStatus('Offline', "Server is not connected! (Save Code)");
  199. }
  200. }
  201. BlockPyServer.prototype.getHistory = function(callback) {
  202. var model = this.main.model;
  203. if (model.server_is_connected('get_history')) {
  204. var data = this.createServerData();
  205. var server = this;
  206. this.setStatus('Loading History');
  207. $.post(model.constants.urls.get_history, data,
  208. function(response) {
  209. if (response.success) {
  210. server.setStatus('Saved');
  211. callback(response.data);
  212. } else {
  213. console.error(response);
  214. server.setStatus('Error', response.message);
  215. }
  216. })
  217. .fail(server.defaultFailure.bind(server));
  218. } else {
  219. this.setStatus('Offline', "Server is not connected! (Get History)");
  220. callback([]);
  221. }
  222. }
  223. BlockPyServer.prototype.walkOldCode = function() {
  224. var server = this,
  225. main = this.main;
  226. if (this.inProgressWalks.length > 0) {
  227. var response = this.inProgressWalks.pop();
  228. console.log('Processing walk', response.log_id);
  229. main.setCode(response.code, '__main__');
  230. main.setCode(response.feedback, 'give_feedback');
  231. main.model.assignment.assignment_id = response.assignment_id;
  232. main.model.assignment.user_id = response.user_id;
  233. main.model.settings.log_id(response.log_id);
  234. main.components.engine.onExecutionEnd = function(newState) {
  235. console.log(response.log_id, newState);
  236. main.components.engine.onExecutionEnd = null;
  237. setTimeout(function() {
  238. server.walkOldCode()
  239. }, 0);
  240. };
  241. console.log("Running");
  242. main.components.engine.run();
  243. } else {
  244. var data = this.createServerData();
  245. this.setStatus('Retrieving');
  246. if (main.model.server_is_connected('walk_old_code')) {
  247. $.post(server.main.model.constants.urls.walk_old_code, data,
  248. function (response) {
  249. if (response.success) {
  250. if (response.more_to_do) {
  251. server.inProgressWalks = response.walks;
  252. server.walkOldCode();
  253. }
  254. } else {
  255. server.setStatus('Failure', response.message);
  256. }
  257. })
  258. .fail(
  259. function(response) {
  260. console.error(response);
  261. setTimeout(function() {
  262. server.walkOldCode()
  263. }, 3000);
  264. }
  265. );
  266. //server.defaultFailure.bind(server));
  267. } else {
  268. this.setStatus('Offline', "Server is not connected! (Walk Old Code)");
  269. }
  270. }
  271. }
  272. /**
  273. *
  274. */
  275. BlockPyServer.prototype.showOverlay = function() {
  276. this.overlay = $('<div class="blockpy-overlay"> </div>');
  277. this.overlay.appendTo(document.body)
  278. }
  279. BlockPyServer.prototype.hideOverlay = function() {
  280. this.overlay.remove();
  281. }
  282. BlockPyServer.prototype.loadAssignment = function(assignment_id) {
  283. var model = this.main.model;
  284. var server = this;
  285. if (model.server_is_connected('load_assignment')) {
  286. var data = this.createServerData();
  287. data['assignment_id'] = assignment_id;
  288. this.setStatus('Loading');
  289. this.showOverlay();
  290. $.post(model.constants.urls.load_assignment, data,
  291. function(response) {
  292. if (response.success) {
  293. server.main.setAssignment(response.settings,
  294. response.assignment,
  295. response.programs)
  296. server.setStatus('Loaded');
  297. server.hideOverlay();
  298. } else {
  299. server.setStatus('Failure', response.message);
  300. server.hideOverlay();
  301. }
  302. })
  303. .fail(function() {
  304. server.hideOverlay();
  305. server.defaultFailure()
  306. });
  307. } else {
  308. this.setStatus('Offline', "Server is not connected! (Load Assignment)");
  309. }
  310. }
  311. /**
  312. * This function can be used to load files and web resources.
  313. */
  314. BlockPyServer.prototype.loadFile = function(filename, type, callback, errorCallback) {
  315. var model = this.main.model;
  316. var server = this;
  317. if (model.server_is_connected('load_file')) {
  318. var data = this.createServerData();
  319. data['filename'] = filename;
  320. data['type'] = type;
  321. this.setStatus('Loading');
  322. $.post(model.constants.urls.load_file, data,
  323. function(response) {
  324. if (response.success) {
  325. callback(response.data);
  326. server.setStatus('Loaded');
  327. server.hideOverlay();
  328. } else {
  329. errorCallback(response.message);
  330. server.setStatus('Failure', response.message);
  331. server.hideOverlay();
  332. }
  333. })
  334. .fail(function(e, textStatus, errorThrown) {
  335. errorCallback("Server failure! Report to instructor");
  336. console.error(errorThrown);
  337. server.defaultFailure()
  338. });
  339. } else {
  340. errorCallback("No file server available.");
  341. this.setStatus('Offline', "Server is not connected! (Load File)");
  342. }
  343. }