Skip to content

Commit 224b624

Browse files
committed
MOBILE-925 remoteaddons: First implementation of remote addons
1 parent 1e7b360 commit 224b624

File tree

13 files changed

+296
-14
lines changed

13 files changed

+296
-14
lines changed

www/addons/mod/quiz/services/accessrulesdelegate.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -358,7 +358,8 @@ angular.module('mm.addons.mod_quiz')
358358
return self;
359359
})
360360

361-
.run(function($mmEvents, mmCoreEventLogin, mmCoreEventSiteUpdated, $mmaModQuizAccessRulesDelegate) {
361+
.run(function($mmEvents, mmCoreEventLogin, mmCoreEventSiteUpdated, $mmaModQuizAccessRulesDelegate, mmCoreEventRemoteAddonsLoaded) {
362362
$mmEvents.on(mmCoreEventLogin, $mmaModQuizAccessRulesDelegate.updateHandlers);
363363
$mmEvents.on(mmCoreEventSiteUpdated, $mmaModQuizAccessRulesDelegate.updateHandlers);
364+
$mmEvents.on(mmCoreEventRemoteAddonsLoaded, $mmaModQuizAccessRulesDelegate.updateHandlers);
364365
});

www/core/components/course/main.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,8 @@ angular.module('mm.core.course', ['mm.core.courses'])
6767
$mmCoursesDelegateProvider.registerNavHandler('mmCourse', '$mmCourseCoursesNavHandler', mmCoreCoursePriority);
6868
})
6969

70-
.run(function($mmEvents, mmCoreEventLogin, mmCoreEventSiteUpdated, $mmCourseDelegate) {
70+
.run(function($mmEvents, mmCoreEventLogin, mmCoreEventSiteUpdated, $mmCourseDelegate, mmCoreEventRemoteAddonsLoaded) {
7171
$mmEvents.on(mmCoreEventLogin, $mmCourseDelegate.updateContentHandlers);
7272
$mmEvents.on(mmCoreEventSiteUpdated, $mmCourseDelegate.updateContentHandlers);
73+
$mmEvents.on(mmCoreEventRemoteAddonsLoaded, $mmCourseDelegate.updateContentHandlers);
7374
});

www/core/components/course/services/prefetchdelegate.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -527,9 +527,10 @@ angular.module('mm.core')
527527
})
528528

529529
.run(function($mmEvents, mmCoreEventLogin, mmCoreEventSiteUpdated, mmCoreEventLogout, $mmCoursePrefetchDelegate, $mmSite,
530-
mmCoreEventPackageStatusChanged) {
530+
mmCoreEventPackageStatusChanged, mmCoreEventRemoteAddonsLoaded) {
531531
$mmEvents.on(mmCoreEventLogin, $mmCoursePrefetchDelegate.updatePrefetchHandlers);
532532
$mmEvents.on(mmCoreEventSiteUpdated, $mmCoursePrefetchDelegate.updatePrefetchHandlers);
533+
$mmEvents.on(mmCoreEventRemoteAddonsLoaded, $mmCoursePrefetchDelegate.updatePrefetchHandlers);
533534
$mmEvents.on(mmCoreEventLogout, $mmCoursePrefetchDelegate.clearStatusCache);
534535
$mmEvents.on(mmCoreEventPackageStatusChanged, function(data) {
535536
if (data.siteid === $mmSite.getId()) {

www/core/components/courses/main.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,9 +67,11 @@ angular.module('mm.core.courses', [])
6767
$mmContentLinksDelegateProvider.registerLinkHandler('mmCourses', '$mmCoursesHandlers.linksHandler');
6868
})
6969

70-
.run(function($mmEvents, mmCoreEventLogin, mmCoreEventSiteUpdated, mmCoreEventLogout, $mmCoursesDelegate, $mmCourses) {
70+
.run(function($mmEvents, mmCoreEventLogin, mmCoreEventSiteUpdated, mmCoreEventLogout, $mmCoursesDelegate, $mmCourses,
71+
mmCoreEventRemoteAddonsLoaded) {
7172
$mmEvents.on(mmCoreEventLogin, $mmCoursesDelegate.updateNavHandlers);
7273
$mmEvents.on(mmCoreEventSiteUpdated, $mmCoursesDelegate.updateNavHandlers);
74+
$mmEvents.on(mmCoreEventRemoteAddonsLoaded, $mmCoursesDelegate.updateNavHandlers);
7375
$mmEvents.on(mmCoreEventLogout, function() {
7476
$mmCoursesDelegate.clearCoursesHandlers();
7577
$mmCourses.clearCurrentCourses();

www/core/components/question/main.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,14 @@ angular.module('mm.core.question', [])
1616

1717
.constant('mmQuestionComponent', 'mmQuestion')
1818

19-
.run(function($mmEvents, mmCoreEventLogin, mmCoreEventSiteUpdated, $mmQuestionDelegate, $mmQuestionBehaviourDelegate) {
19+
.run(function($mmEvents, mmCoreEventLogin, mmCoreEventSiteUpdated, $mmQuestionDelegate, $mmQuestionBehaviourDelegate,
20+
mmCoreEventRemoteAddonsLoaded) {
2021
function updateHandlers() {
2122
$mmQuestionDelegate.updateQuestionHandlers();
2223
$mmQuestionBehaviourDelegate.updateQuestionBehaviourHandlers();
2324
}
2425

2526
$mmEvents.on(mmCoreEventLogin, updateHandlers);
2627
$mmEvents.on(mmCoreEventSiteUpdated, updateHandlers);
28+
$mmEvents.on(mmCoreEventRemoteAddonsLoaded, updateHandlers);
2729
});

www/core/components/sidemenu/main.js

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ angular.module('mm.core.sidemenu', [])
2424
controller: 'mmSideMenuCtrl',
2525
abstract: true,
2626
cache: false,
27-
onEnter: function($ionicHistory, $state, $mmSite, $timeout) {
27+
onEnter: function($ionicHistory, $state, $mmSite) {
2828
// Remove the login page from the history stack.
2929
$ionicHistory.clearHistory();
3030

@@ -37,8 +37,10 @@ angular.module('mm.core.sidemenu', [])
3737

3838
})
3939

40-
.run(function($mmEvents, mmCoreEventLogin, mmCoreEventSiteUpdated, mmCoreEventLogout, $mmSideMenuDelegate) {
40+
.run(function($mmEvents, mmCoreEventLogin, mmCoreEventSiteUpdated, mmCoreEventLogout, $mmSideMenuDelegate,
41+
mmCoreEventRemoteAddonsLoaded) {
4142
$mmEvents.on(mmCoreEventLogin, $mmSideMenuDelegate.updateNavHandlers);
4243
$mmEvents.on(mmCoreEventSiteUpdated, $mmSideMenuDelegate.updateNavHandlers);
44+
$mmEvents.on(mmCoreEventRemoteAddonsLoaded, $mmSideMenuDelegate.updateNavHandlers);
4345
$mmEvents.on(mmCoreEventLogout, $mmSideMenuDelegate.clearSiteHandlers);
4446
});

www/core/components/user/main.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,9 +40,11 @@ angular.module('mm.core.user', [])
4040

4141
})
4242

43-
.run(function($mmEvents, mmCoreEventLogin, mmCoreEventSiteUpdated, $mmUserDelegate, $mmSite, mmCoreEventUserDeleted, $mmUser) {
43+
.run(function($mmEvents, mmCoreEventLogin, mmCoreEventSiteUpdated, $mmUserDelegate, $mmSite, mmCoreEventUserDeleted, $mmUser,
44+
mmCoreEventRemoteAddonsLoaded) {
4445
$mmEvents.on(mmCoreEventLogin, $mmUserDelegate.updateProfileHandlers);
4546
$mmEvents.on(mmCoreEventSiteUpdated, $mmUserDelegate.updateProfileHandlers);
47+
$mmEvents.on(mmCoreEventRemoteAddonsLoaded, $mmUserDelegate.updateProfileHandlers);
4648

4749
$mmEvents.on(mmCoreEventUserDeleted, function(data) {
4850
if (data.siteid && data.siteid === $mmSite.getId() && data.params) {

www/core/lib/addonmanager.js

Lines changed: 245 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,19 +14,125 @@
1414

1515
angular.module('mm.core')
1616

17+
.constant('mmAddonManagerComponent', 'mmAddonManager')
18+
1719
/**
1820
* @ngdoc service
1921
* @name $mmAddonManager
2022
* @module mm.core
2123
* @description
2224
* This service provides functions related to addons, like checking if an addon is available.
2325
*/
24-
.factory('$mmAddonManager', function($log, $injector) {
26+
.factory('$mmAddonManager', function($log, $injector, $ocLazyLoad, $mmFilepool, $mmSite, $mmFS, $mmLang, $mmSitesManager, $q,
27+
$mmUtil, mmAddonManagerComponent, mmCoreNotDownloaded) {
2528

2629
$log = $log.getInstance('$mmAddonManager');
2730

2831
var self = {},
29-
instances = {};
32+
instances = {},
33+
remoteAddonsFolderName = 'remoteaddons',
34+
remoteAddonFilename = 'addon.js',
35+
remoteAddonCssFilename = 'styles.css',
36+
pathWildcardRegex = /\$ADDONPATH\$/g,
37+
headEl = angular.element(document.querySelector('head')),
38+
loadedAddons = [];
39+
40+
/**
41+
* Download a remote addon if it's not downloaded already.
42+
*
43+
* @module mm.core
44+
* @ngdoc method
45+
* @name $mmAddonManager#downloadRemoteAddon
46+
* @param {Object} addon Addon to download.
47+
* @param {String} [siteId] Site ID. If not defined, current site.
48+
* @return {Promise} Promise resolved when the file is downloaded. Data returned is not reliable.
49+
*/
50+
self.downloadRemoteAddon = function(addon, siteId) {
51+
siteId = siteId || $mmSite.getId();
52+
53+
var name = self.getRemoteAddonName(addon),
54+
dirPath = self.getRemoteAddonDirectoryPath(addon),
55+
revision = addon.filehash,
56+
file = {
57+
filename: name + '.zip',
58+
fileurl: addon.fileurl
59+
};
60+
61+
// Get the status to check if it's already downloaded.
62+
return $mmFilepool.getPackageStatus(siteId, mmAddonManagerComponent, name, revision, 0).then(function(status) {
63+
if (status !== $mmFilepool.FILEDOWNLOADED) {
64+
// Not downloaded or outdated. Download the ZIP file in the filepool folder.
65+
return $mmFilepool.downloadPackage(siteId, [file], mmAddonManagerComponent, name, revision, 0).then(function() {
66+
// Remove the destination folder to prevent having old unused files.
67+
return $mmFS.removeDir(dirPath).catch(function() {});
68+
}).then(function() {
69+
// Get the ZIP file path.
70+
return $mmFilepool.getFilePathByUrl(siteId, addon.fileurl);
71+
}).then(function(zipPath) {
72+
// Unzip and delete the zip when finished.
73+
return $mmFS.unzipFile(zipPath, dirPath).then(function() {
74+
return $mmFilepool.removeFileByUrl(siteId, addon.fileurl).catch(function() {});
75+
});
76+
}).then(function() {
77+
// Get the directory to get the absolute dirPath.
78+
return $mmFS.getDir(dirPath);
79+
}).then(function(dir) {
80+
var absoluteDirPath = $mmFS.getInternalURL(dir);
81+
82+
// Remove / in the end if it's there.
83+
if (absoluteDirPath.slice(-1) == '/') {
84+
absoluteDirPath = absoluteDirPath.substring(0, absoluteDirPath.length - 1);
85+
}
86+
87+
// Replace path wildcards with the right path.
88+
var addonMainFile = $mmFS.concatenatePaths(dirPath, remoteAddonFilename);
89+
return $mmFS.replaceInFile(addonMainFile, pathWildcardRegex, absoluteDirPath);
90+
}).catch(function() {
91+
// Error, set status as it was before.
92+
return self.setRemoteAddonStatus(addon, status).then(function() {
93+
return $q.reject();
94+
});
95+
});
96+
}
97+
});
98+
};
99+
100+
/**
101+
* Download the remote addons for a certain site.
102+
*
103+
* @module mm.core
104+
* @ngdoc method
105+
* @name $mmAddonManager#downloadRemoteAddons
106+
* @param {String} [siteId] Site ID. If not defined, current site.
107+
* @return {Promise} Promise resolved when done. Returns the list of downloaded addons.
108+
*/
109+
self.downloadRemoteAddons = function(siteId) {
110+
siteId = siteId || $mmSite.getId();
111+
112+
var downloaded = [],
113+
preSets = {};
114+
115+
return $mmSitesManager.getSite(siteId).then(function(site) {
116+
// Get the list of addons. Try not to use cache.
117+
preSets.getFromCache = 0;
118+
return site.read('tool_mobile_get_plugins_supporting_mobile', {}, preSets).then(function(data) {
119+
var promises = [];
120+
121+
angular.forEach(data.plugins, function(addon) {
122+
promises.push(self.downloadRemoteAddon(addon, siteId).then(function() {
123+
downloaded.push(addon);
124+
}));
125+
});
126+
127+
return $mmUtil.allPromises(promises).then(function() {
128+
return downloaded;
129+
}).catch(function() {
130+
// Some download failed, return the downloaded ones anyway.
131+
return downloaded;
132+
});
133+
});
134+
});
135+
};
30136

31137
/**
32138
* Get a service instance if it's available.
@@ -43,6 +149,49 @@ angular.module('mm.core')
43149
}
44150
};
45151

152+
/**
153+
* Gets the relative path of an addon directory.
154+
* It should be {FILEPOOLPATH}/{remoteAddonsFolderName}/{addonname}.
155+
*
156+
* @module mm.core
157+
* @ngdoc method
158+
* @name $mmAddonManager#getRemoteAddonDirectoryPath
159+
* @param {Object} addon Remote addon.
160+
* @param {String} [siteId] Site ID. If not defined, current site.
161+
* @return {String} Directory path.
162+
*/
163+
self.getRemoteAddonDirectoryPath = function(addon, siteId) {
164+
siteId = siteId || $mmSite.getId();
165+
166+
var subPath = remoteAddonsFolderName + '/' + self.getRemoteAddonName(addon);
167+
return $mmFilepool._getFilePath(siteId, subPath);
168+
};
169+
170+
/**
171+
* Get the name of a remote addon.
172+
*
173+
* @module mm.core
174+
* @ngdoc method
175+
* @name $mmAddonManager#getRemoteAddonName
176+
* @param {Object} addon Remote addon.
177+
* @return {String} Name.
178+
*/
179+
self.getRemoteAddonName = function(addon) {
180+
return addon.component + '_' + addon.addon;
181+
};
182+
183+
/**
184+
* Check if there are remote addons loaded.
185+
*
186+
* @module mm.core
187+
* @ngdoc method
188+
* @name $mmAddonManager#hasRemoteAddonsLoaded
189+
* @return {Boolean} True if remote addons loaded, false otherwise.
190+
*/
191+
self.hasRemoteAddonsLoaded = function() {
192+
return loadedAddons.length;
193+
};
194+
46195
/**
47196
* Check if a service is available.
48197
*
@@ -70,5 +219,99 @@ angular.module('mm.core')
70219
}
71220
};
72221

222+
/**
223+
* Load a remote addon.
224+
*
225+
* @module mm.core
226+
* @ngdoc method
227+
* @name $mmAddonManager#loadRemoteAddon
228+
* @param {Object} addon Addon to load.
229+
* @return {Promise} Promise resolved when loaded.
230+
*/
231+
self.loadRemoteAddon = function(addon) {
232+
var dirPath = self.getRemoteAddonDirectoryPath(addon),
233+
absoluteDirPath;
234+
235+
// Get the absolute path to the directory.
236+
return $mmFS.getDir(dirPath).then(function(dir) {
237+
absoluteDirPath = $mmFS.getInternalURL(dir);
238+
239+
// Register language folder so the language strings of the addon are loaded.
240+
$mmLang.registerLanguageFolder($mmFS.concatenatePaths(absoluteDirPath, 'lang'));
241+
// Load the addon.
242+
return $ocLazyLoad.load($mmFS.concatenatePaths(absoluteDirPath, remoteAddonFilename)).then(function() {
243+
loadedAddons.push(addon);
244+
// Check if the addon has a CSS file.
245+
return $mmFS.getFile($mmFS.concatenatePaths(dirPath, remoteAddonCssFilename)).then(function(file) {
246+
// The file exists, add it in the head.
247+
headEl.append('<link class="remoteaddonstyles" rel="stylesheet" href="' + $mmFS.getInternalURL(file) + '">');
248+
}).catch(function() {});
249+
});
250+
}, function() {
251+
// Directory not found, set status as not downloaded.
252+
return self.setRemoteAddonStatus(addon, mmCoreNotDownloaded).then(function() {
253+
return $q.reject();
254+
});
255+
});
256+
};
257+
258+
/**
259+
* Load a list of remote addons.
260+
*
261+
* @module mm.core
262+
* @ngdoc method
263+
* @name $mmAddonManager#loadRemoteAddons
264+
* @param {Object[]} addons Addons to load.
265+
* @return {Promise} Promise resolved when all have been loaded.
266+
*/
267+
self.loadRemoteAddons = function(addons) {
268+
var promises = [];
269+
angular.forEach(addons, function(addon) {
270+
promises.push(self.loadRemoteAddon(addon));
271+
});
272+
return $mmUtil.allPromises(promises);
273+
};
274+
275+
/**
276+
* Set status of a remote addon.
277+
*
278+
* @module mm.core
279+
* @ngdoc method
280+
* @name $mmAddonManager#setRemoteAddonStatus
281+
* @param {Object} addon Addon.
282+
* @param {String} status Status to set.
283+
* @param {String} [siteId] Site ID. If not defined, current site.
284+
* @return {Promise} Promise resolved when set.
285+
*/
286+
self.setRemoteAddonStatus = function(addon, status, siteId) {
287+
siteId = siteId || $mmSite.getId();
288+
289+
var name = self.getRemoteAddonName(addon),
290+
revision = addon.filehash;
291+
return $mmFilepool.storePackageStatus(siteId, mmAddonManagerComponent, name, status, revision, 0);
292+
};
293+
73294
return self;
295+
})
296+
297+
.run(function($mmAddonManager, $mmEvents, mmCoreEventLogin, mmCoreEventLogout, mmCoreEventRemoteAddonsLoaded, $mmSite, $window) {
298+
// Download and load remote addons on login.
299+
$mmEvents.on(mmCoreEventLogin, function() {
300+
var siteId = $mmSite.getId();
301+
$mmAddonManager.downloadRemoteAddons(siteId).then(function(addons) {
302+
return $mmAddonManager.loadRemoteAddons(addons).finally(function() {
303+
if ($mmSite.getId() == siteId && $mmAddonManager.hasRemoteAddonsLoaded()) {
304+
$mmEvents.trigger(mmCoreEventRemoteAddonsLoaded);
305+
}
306+
});
307+
});
308+
});
309+
310+
// Unload remote addons on logout if any.
311+
$mmEvents.on(mmCoreEventLogout, function() {
312+
if ($mmAddonManager.hasRemoteAddonsLoaded()) {
313+
// Temporary fix. Reload the page to unload all remote addons.
314+
$window.location.reload();
315+
}
316+
});
74317
});

www/core/lib/events.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ angular.module('mm.core')
2828
.constant('mmCoreEventUserDeleted', 'user_deleted')
2929
.constant('mmCoreEventPackageStatusChanged', 'filepool_package_status_changed')
3030
.constant('mmCoreEventSectionStatusChanged', 'section_status_changed')
31+
.constant('mmCoreEventRemoteAddonsLoaded', 'remote_addons_loaded')
3132

3233
/**
3334
* Service to send and listen to events.

0 commit comments

Comments
 (0)