The branch, frodo has been updated
via 8755ee52e6119e7c8ff8dd367819901f4af27f7a (commit)
via 431cfd4572aa50d4fa550b189c59754e10a67d43 (commit)
from efcbf7e37e56d3b263f4cf0466c22b628f954917 (commit)
- Log -----------------------------------------------------------------
http://xbmc.git.sourceforge.net/git/gitweb.cgi?p=xbmc/plugins;a=commit;h=8755ee52e6119e7c8ff8dd367819901f4af27f7a
commit 8755ee52e6119e7c8ff8dd367819901f4af27f7a
Author: beenje <[email protected]>
Date: Sun Jan 5 15:32:37 2014 +0100
[plugin.video.udacity] updated to version 1.0.4
diff --git a/plugin.video.udacity/README.md b/plugin.video.udacity/README.md
index 42c8f19..849d955 100644
--- a/plugin.video.udacity/README.md
+++ b/plugin.video.udacity/README.md
@@ -9,7 +9,7 @@ From http://udacity.com/how-it-works:
## Setup/Installation
-Coming soon
+Available via official repo. Installation info available
[here](http://wiki.xbmc.org/index.php?title=Add-ons).
## XBMC Version
@@ -17,6 +17,7 @@ Frodo
## Tests
+ > pip install xbmcswift2
> python resources/tests/test_addon.py
## License
diff --git a/plugin.video.udacity/addon.py b/plugin.video.udacity/addon.py
index e394530..eca9894 100644
--- a/plugin.video.udacity/addon.py
+++ b/plugin.video.udacity/addon.py
@@ -12,12 +12,30 @@ def index():
items = [
{'label': plugin.get_string(30004),
'path': plugin.url_for('course_catalog')},
- {'label': plugin.get_string(30005),
- 'path': plugin.url_for('my_courses')},
- {'label': plugin.get_string(30006),
- 'path': plugin.url_for('open_settings')}
]
+ auth_storage = plugin.get_storage('auth')
+ auth = UdacityAuth(auth_storage)
+ username = plugin.get_setting('username')
+ password = plugin.get_setting('user_password')
+
+ # Default settings string displays Login unless logged in
+ setting_string_id = 30012
+ if username and password:
+ if auth.authenticate(username, password):
+ items.append(
+ {'label': plugin.get_string(30005),
+ 'path': plugin.url_for('my_courses')})
+ setting_string_id = 30006
+ else:
+ plugin.notify(auth.error)
+ plugin.set_setting('user_password', '')
+
+ items.append(
+ {'label': plugin.get_string(setting_string_id),
+ 'path': plugin.url_for('open_settings')}
+ )
+
return items
@@ -54,11 +72,8 @@ def open_lesson(course_id, lesson_id):
items = []
auth_storage = plugin.get_storage('auth')
auth = UdacityAuth(auth_storage)
- auth.authenticate(
- plugin.get_setting('username'),
- plugin.get_setting('user_password')
- )
udacity = Udacity(auth)
+
contents = udacity.get_lesson_contents(lesson_id)
for content in contents:
if content['model'] == 'Video':
@@ -107,7 +122,17 @@ def my_courses():
@plugin.route('/open_settings/')
def open_settings():
- return plugin.open_settings()
+ old_username = plugin.get_setting('username')
+ old_password = plugin.get_setting('user_password')
+ plugin.open_settings()
+ # If the username or password was changed, flush any saved cookies/tokens
+ if (old_username != plugin.get_setting('username') or
+ old_password != plugin.get_setting('user_password')):
+ auth_storage = plugin.get_storage('auth')
+ auth_storage['cookies'] = None
+ auth_storage['xsrf-token'] = None
+ auth_storage.sync()
+ return plugin.redirect(plugin.url_for('index'))
@plugin.route((
@@ -177,6 +202,7 @@ def play_video(course_id, lesson_id, asset_id, youtube_id):
youtube_url = (
"plugin://plugin.video.youtube/"
"?action=play_video&videoid={0}").format(youtube_id)
+
return plugin.set_resolved_url(youtube_url)
if __name__ == '__main__':
diff --git a/plugin.video.udacity/addon.xml b/plugin.video.udacity/addon.xml
index b10b056..35bc973 100644
--- a/plugin.video.udacity/addon.xml
+++ b/plugin.video.udacity/addon.xml
@@ -1,9 +1,10 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
-<addon id="plugin.video.udacity" name="Udacity" version="1.0.2"
provider-name="Lex Toumbourou (@lexandstuff)">
+<addon id="plugin.video.udacity" name="Udacity" version="1.0.4"
provider-name="Lex Toumbourou (@lexandstuff)">
<requires>
<import addon="xbmc.python" version="2.1.0"/>
<import addon="script.module.xbmcswift2" version="2.4.0"/>
<import addon="script.module.requests" version="1.1.0"/>
+ <import addon="script.module.beautifulsoup" version="3.2.1"/>
<import addon="plugin.video.youtube" version="4.4.6"/>
</requires>
<extension point="xbmc.python.pluginsource" library="addon.py">
diff --git a/plugin.video.udacity/changelog.txt
b/plugin.video.udacity/changelog.txt
index f5eecd8..eccc896 100644
--- a/plugin.video.udacity/changelog.txt
+++ b/plugin.video.udacity/changelog.txt
@@ -1,3 +1,14 @@
+1.0.4
+- Use strings.xml for buttons in quizzes
+- Display Loading... after submitting quiz
+
+1.0.3
+- Added BeautifulSoup dependancy
+- No longer fail when certain steps are missing (fixing some broken lessons)
+- Hide My Courses when user not authenticated
+- Changed strings to better match Udacity's interface
+- Update Udacity's servers when quizzes are submitted
+
1.0.2
- Corrected string ID range
- Removed fake user-agent
diff --git a/plugin.video.udacity/resources/language/English/strings.xml
b/plugin.video.udacity/resources/language/English/strings.xml
index cf6f9a5..31fe410 100644
--- a/plugin.video.udacity/resources/language/English/strings.xml
+++ b/plugin.video.udacity/resources/language/English/strings.xml
@@ -2,7 +2,7 @@
<strings>
<string id="30000">Udacity</string>
<string id="30001">General</string>
- <string id="30002">Username</string>
+ <string id="30002">Email</string>
<string id="30003">Password</string>
<string id="30004">Course Catalog</string>
<string id="30005">My Courses</string>
@@ -12,4 +12,8 @@
<string id="30009">Answer</string>
<string id="30010">Please complete online</string>
<string id="30011">Programming Quizzes are not supported on XBMC</string>
+ <string id="30012">Login</string>
+ <string id="30013">Submit</string>
+ <string id="30014">Cancel</string>
+ <string id="30015">Loading...</string>
</strings>
diff --git a/plugin.video.udacity/resources/lib/controls.py
b/plugin.video.udacity/resources/lib/controls.py
index b48d4d2..9ddd06c 100644
--- a/plugin.video.udacity/resources/lib/controls.py
+++ b/plugin.video.udacity/resources/lib/controls.py
@@ -2,6 +2,8 @@ from xbmcswift2 import xbmcgui, xbmc
import math
import os
+import utils
+
class TextBox(xbmcgui.ControlButton):
"""
@@ -68,9 +70,11 @@ class FormQuiz(xbmcgui.WindowDialog):
self.udacity = udacity
self.data = quiz_data['data']
self.widgets = []
- self.udacity.update_activity(
- course_id, lesson_id, group_id, quiz_id, 'NodeVisit')
self.plugin = plugin
+ self.course_id = course_id
+ self.lesson_id = lesson_id
+ self.group_id = group_id
+ self.quiz_id = quiz_id
bg_image_path = (
plugin.addon.getAddonInfo('path') + os.sep +
@@ -122,11 +126,13 @@ class FormQuiz(xbmcgui.WindowDialog):
self.submit_button = xbmcgui.ControlButton(
x=self.button_x_pos, y=self.button_y_pos, width=self.button_width,
height=self.button_height, shadowColor='0xFF000000',
- label='Submit', font='font13', textColor=self.button_text_colour)
+ label=self.plugin.get_string(30013), font='font13',
+ textColor=self.button_text_colour)
+
self.back_button = xbmcgui.ControlButton(
x=self.button_x_pos - self.button_spacing,
y=self.button_y_pos, width=self.button_width,
- height=self.button_height, label='Back',
+ height=self.button_height, label=self.plugin.get_string(30014),
font='font13', textColor=self.button_text_colour)
self.addControl(self.submit_button)
@@ -137,9 +143,23 @@ class FormQuiz(xbmcgui.WindowDialog):
self.close()
return
elif control == self.submit_button:
- result = self.udacity.submit_quiz(self.data['key'], self.widgets)
+ control.setLabel(self.plugin.get_string(30015))
+ answer_data = utils.widgets_to_answer(self.widgets)
+ result = self.udacity.submit_quiz(self.data['key'], answer_data)
+ if self.udacity.auth.is_authenticated:
+ if not self.udacity.update_submission_activity(
+ self.course_id, self.lesson_id, self.group_id,
+ self.quiz_id, result['evaluation'],
+ answer_data['submission']):
+ # Submission wasn't updated correctly
+ self.plugin.log.error(self.udacity.error)
dialog = xbmcgui.Dialog()
dialog.ok('Result', result['evaluation']['comment'])
+ if result['evaluation']['passed'] is not False:
+ self.close()
+ return
+ else:
+ control.setLabel(self.plugin.get_string(30013))
else:
for count, widget in enumerate(self.widgets):
if control == widget['obj'] and widget['obj'].canUpdateLabel:
diff --git a/plugin.video.udacity/resources/lib/udacity.py
b/plugin.video.udacity/resources/lib/udacity.py
index 2293b8e..79e846c 100644
--- a/plugin.video.udacity/resources/lib/udacity.py
+++ b/plugin.video.udacity/resources/lib/udacity.py
@@ -11,17 +11,33 @@ class Udacity(object):
def __init__(self, auth):
self.auth = auth
- def update_activity(
- self, course_id, lesson_id, group_id, asset_id, activity_type):
+ def update_submission_activity(
+ self, course_id, lesson_id, group_id,
+ quiz_id, quiz_result, answer_data):
+ """
+ Send submitted quiz data to Udacity
+ """
occurence_time = dt.datetime.utcnow().strftime('%Y-%m-%dT%H:%M:%S.%fZ')
-
- data = {'items': [
- {'occurrence_time': occurence_time, 'content_context': [
- {'tag': 'c-', 'node_key': course_id},
- {'tag': 'l-', 'node_key': lesson_id},
- {'tag': "e-", "node_key": group_id},
- {'tag': "m-", "node_key": asset_id}],
- 'data': {'model': activity_type}}],
+ quiz_result['model'] = 'SubmissionEvaluation'
+
+ current_context = [
+ {'tag': 'c-', 'node_key': course_id},
+ {'tag': 'l-', 'node_key': lesson_id},
+ {'tag': "e-", "node_key": group_id},
+ {'tag': "m-", "node_key": quiz_id}
+ ]
+
+ data = {
+ 'items': [{
+ 'occurrence_time': occurence_time,
+ 'content_context': current_context,
+ 'data': answer_data
+ }, {
+ 'occurrence_time': occurence_time,
+ 'content_context': current_context,
+ 'data': quiz_result,
+ }
+ ],
'current_time': occurence_time}
r = requests.post(
@@ -30,7 +46,7 @@ class Udacity(object):
cookies=self.auth.get_cookies())
if not r.status_code == 200:
- self.error = r.text
+ self.error = json.loads(r.text[5:])['error']
return False
return True
@@ -73,7 +89,10 @@ class Udacity(object):
data = json.loads(r.text[5:])['references']['Node']
steps = data[section]['steps_refs']
for step in steps:
- node = data[step['key']]
+ try:
+ node = data[step['key']]
+ except KeyError:
+ continue
# Push the quiz and lecture data into the dictionary
# to support XBMC's stateless nature
@@ -133,26 +152,14 @@ class Udacity(object):
return output
- def submit_quiz(self, quiz_id, widgets):
+ def submit_quiz(self, quiz_id, answer_data):
url = "{0}/api/nodes/{1}/evaluation?_method=GET".format(
UDACITY_URL, quiz_id)
- parts = []
- for widget in widgets:
- parts.append(
- {"model": "SubmissionPart",
- "marker": widget['data']['marker'],
- "content": widget['obj'].getContent()})
-
- answer_data = {
- "submission": {
- "model": "Submission",
- "operation": "GRADE",
- "parts": parts
- }
- }
r = requests.post(
url, data=json.dumps(answer_data),
- headers=self.auth.get_request_headers())
+ headers=self.auth.get_request_headers(),
+ cookies=self.auth.get_cookies())
+
return json.loads(r.text[5:])
def get_last_quiz_submission(self, quiz_id):
@@ -213,14 +220,16 @@ class UdacityAuth(object):
if r.status_code == 200:
self.is_authenticated = True
self.auth_stored['cookies'] = r.cookies
+ self.auth_stored['xsrf_token'] = r.cookies['XSRF-TOKEN']
return True
else:
result = json.loads(r.text[5:])
+ self.is_authenticated = False
self.error = result['error']
return False
def get_request_headers(self):
return {
- 'xsrf_token': self.get_xsrf_token(),
+ 'x-xsrf-token': self.get_xsrf_token(),
'content-type': 'application/json;charset=UTF-8',
}
diff --git a/plugin.video.udacity/resources/tests/test_addon.py
b/plugin.video.udacity/resources/tests/test_addon.py
index 76a63b7..a790952 100644
--- a/plugin.video.udacity/resources/tests/test_addon.py
+++ b/plugin.video.udacity/resources/tests/test_addon.py
@@ -3,6 +3,27 @@ import sys, os
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '../..'))
from resources.lib.udacity import Udacity, UdacityAuth
+from resources.lib.utils import widgets_to_answer
+
+class MockWidget():
+ def getContent(self):
+ return True
+
+
+class UnitTests(unittest.TestCase):
+ def test_return_answer_data_from_list_of_widgets(self):
+ mock_1 = MockWidget()
+ mock_2 = MockWidget()
+ widgets = [
+ {'data': {'marker': True},
+ 'obj': mock_1},
+ {'data': {'marker': True},
+ 'obj': mock_2},
+ ]
+ result = widgets_to_answer(widgets)
+ self.assertTrue('submission' in result)
+ self.assertTrue(result['submission']['parts'][0]['content'])
+
class OfflineTest(unittest.TestCase):
def test_return_true_when_cookies_and_token_are_set(self):
http://xbmc.git.sourceforge.net/git/gitweb.cgi?p=xbmc/plugins;a=commit;h=431cfd4572aa50d4fa550b189c59754e10a67d43
-----------------------------------------------------------------------
Summary of changes:
.../LICENSE.txt | 0
plugin.audio.tilos/addon.py | 195 ++++++++++++++++++++
plugin.audio.tilos/addon.xml | 23 +++
plugin.audio.tilos/changelog.txt | 5 +
plugin.audio.tilos/fanart.jpg | Bin 0 -> 12737 bytes
plugin.audio.tilos/icon.png | Bin 0 -> 26930 bytes
.../resources/language/English/strings.po | 48 +++++
.../resources/language/Hungarian/strings.po | 48 +++++
plugin.video.udacity/README.md | 3 +-
plugin.video.udacity/addon.py | 44 ++++-
plugin.video.udacity/addon.xml | 3 +-
plugin.video.udacity/changelog.txt | 11 +
.../resources/language/English/strings.xml | 6 +-
plugin.video.udacity/resources/lib/controls.py | 30 +++-
plugin.video.udacity/resources/lib/udacity.py | 67 ++++---
plugin.video.udacity/resources/lib/utils.py | 22 +++
plugin.video.udacity/resources/tests/test_addon.py | 21 ++
17 files changed, 480 insertions(+), 46 deletions(-)
copy {plugin.audio.abradio.cz => plugin.audio.tilos}/LICENSE.txt (100%)
create mode 100644 plugin.audio.tilos/addon.py
create mode 100644 plugin.audio.tilos/addon.xml
create mode 100644 plugin.audio.tilos/changelog.txt
create mode 100644 plugin.audio.tilos/fanart.jpg
create mode 100644 plugin.audio.tilos/icon.png
create mode 100644 plugin.audio.tilos/resources/language/English/strings.po
create mode 100644 plugin.audio.tilos/resources/language/Hungarian/strings.po
create mode 100644 plugin.video.udacity/resources/lib/utils.py
hooks/post-receive
--
Plugins
------------------------------------------------------------------------------
Rapidly troubleshoot problems before they affect your business. Most IT
organizations don't have a clear picture of how application performance
affects their revenue. With AppDynamics, you get 100% visibility into your
Java,.NET, & PHP application. Start your 15-day FREE TRIAL of AppDynamics Pro!
http://pubads.g.doubleclick.net/gampad/clk?id=84349831&iu=/4140/ostg.clktrk
_______________________________________________
Xbmc-addons mailing list
[email protected]
https://lists.sourceforge.net/lists/listinfo/xbmc-addons