Web   ·   Wiki   ·   Activities   ·   Blog   ·   Lists   ·   Chat   ·   Meeting   ·   Bugs   ·   Git   ·   Translate   ·   Archive   ·   People   ·   Donate
summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Workshop.activity/MANIFEST5
-rw-r--r--Workshop.activity/TutorialStoreCategories.py28
-rw-r--r--Workshop.activity/TutorialStoreDetails.py74
-rw-r--r--Workshop.activity/TutorialStoreHome.py109
-rw-r--r--Workshop.activity/TutorialStoreResults.py124
-rw-r--r--Workshop.activity/TutorialStoreSearch.py33
-rw-r--r--Workshop.activity/TutorialStoreSuggestion.py139
-rwxr-xr-xWorkshop.activity/TutoriusActivity.py77
-rw-r--r--Workshop.activity/Workshop.py285
-rw-r--r--Workshop.activity/WorkshopController.py119
-rw-r--r--Workshop.activity/WorkshopListItem.py78
-rw-r--r--Workshop.activity/WorkshopModel.py162
-rw-r--r--Workshop.activity/activity/activity.info8
-rw-r--r--Workshop.activity/activity/someicon.svg21
-rw-r--r--Workshop.activity/arrow_back.pngbin0 -> 310 bytes
-rw-r--r--Workshop.activity/dialogs.py139
-rw-r--r--Workshop.activity/full_star.pngbin0 -> 1031 bytes
-rw-r--r--Workshop.activity/grayed_star.pngbin0 -> 930 bytes
-rw-r--r--Workshop.activity/half_star.pngbin0 -> 890 bytes
-rw-r--r--Workshop.activity/icon.svg21
-rwxr-xr-xWorkshop.activity/setup.py3
-rw-r--r--addons/gtkwidgettypefilter.py4
-rw-r--r--addons/triggereventfilter.py4
-rw-r--r--tests/bundlertests.py134
-rw-r--r--tests/coretests.py51
-rw-r--r--tests/filterstests.py20
-rw-r--r--tests/linear_creatortests.py8
-rw-r--r--tests/propertiestests.py2
-rw-r--r--tests/serializertests.py196
-rw-r--r--tests/vaulttests.py516
-rw-r--r--tutorius/core.py20
-rw-r--r--tutorius/vault.py (renamed from tutorius/bundler.py)483
32 files changed, 2316 insertions, 547 deletions
diff --git a/Workshop.activity/MANIFEST b/Workshop.activity/MANIFEST
new file mode 100644
index 0000000..c5c6d42
--- /dev/null
+++ b/Workshop.activity/MANIFEST
@@ -0,0 +1,5 @@
+TutoriusActivity.py
+activity/someicon.svg
+activity/activity.info
+setup.py
+MANIFEST
diff --git a/Workshop.activity/TutorialStoreCategories.py b/Workshop.activity/TutorialStoreCategories.py
new file mode 100644
index 0000000..2bf843d
--- /dev/null
+++ b/Workshop.activity/TutorialStoreCategories.py
@@ -0,0 +1,28 @@
+import sys, os
+import gtk
+
+class TutorialStoreCategories:
+
+ def __init__(self):
+ categorie_math = gtk.Label('Math (8)')
+ categorie_physics = gtk.Label('Phyisics (16)')
+ categorie_history = gtk.Label('History (32)')
+ categorie_learning = gtk.Label('Learning (53)')
+
+ categorie_box = gtk.VBox(True, 5)
+ self.categorie_box_frame = gtk.Frame('Categories')
+ self.categorie_box_frame.modify_bg(gtk.STATE_NORMAL, gtk.gdk.Color(0,0,0))
+
+ categorie_box.pack_start(categorie_math, True, True, 4)
+ categorie_box.pack_start(categorie_physics, True, True, 4)
+ categorie_box.pack_start(categorie_history, True, True, 4)
+ categorie_box.pack_start(categorie_learning, True, True, 4)
+
+ self.categorie_box_frame.add(categorie_box)
+
+ categorie_math.show()
+ categorie_physics.show()
+ categorie_history.show()
+ categorie_learning.show()
+ categorie_box.show()
+ self.categorie_box_frame.show() \ No newline at end of file
diff --git a/Workshop.activity/TutorialStoreDetails.py b/Workshop.activity/TutorialStoreDetails.py
new file mode 100644
index 0000000..67b85d0
--- /dev/null
+++ b/Workshop.activity/TutorialStoreDetails.py
@@ -0,0 +1,74 @@
+import sys, os
+import gtk
+
+class TutorialStoreDetails:
+
+ def __init__(self):
+ tuto_icon = gtk.Image()
+ tuto_icon.set_from_file('icon.svg')
+
+ full_star_icon1 = gtk.Image()
+ full_star_icon1.set_from_file('full_star.svg')
+ full_star_icon2 = gtk.Image()
+ full_star_icon2.set_from_file('full_star.svg')
+ full_star_icon3 = gtk.Image()
+ full_star_icon3.set_from_file('full_star.svg')
+
+ grayed_star_icon = gtk.Image()
+ grayed_star_icon.set_from_file('grayed_star.svg')
+
+ half_star_icon= gtk.Image()
+ half_star_icon.set_from_file('half_star.svg')
+
+ title_autor_box = gtk.VBox()
+ tuto_title_label = gtk.Label('<Title>')
+ tuto_author_label = gtk.Label('by <Author>')
+ title_autor_box.pack_start(tuto_title_label)
+ title_autor_box.pack_start(tuto_author_label)
+
+ tuto_descrip_label = gtk.Label('Description 1 : Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.')
+ tuto_descrip_label.set_line_wrap(True)
+ tuto_descrip_label.set_width_chars(50)
+
+ download_button = gtk.Button('Download')
+ infos_button = gtk.Button('Informations')
+ comp_button = gtk.Button('Compatibility')
+
+ tutorial_title_bar = gtk.HBox (False, 4)
+ tutorial_title_bar.pack_start(tuto_icon)
+ tutorial_title_bar.pack_start(title_autor_box)
+ tutorial_title_bar.pack_start(full_star_icon1)
+ tutorial_title_bar.pack_start(full_star_icon2)
+ tutorial_title_bar.pack_start(full_star_icon3)
+ tutorial_title_bar.pack_start(half_star_icon)
+ tutorial_title_bar.pack_start(grayed_star_icon)
+ tutorial_button_bar = gtk.HBox(True, 25)
+ tutorial_button_bar.pack_start(download_button)
+ tutorial_button_bar.pack_start(infos_button)
+ tutorial_button_bar.pack_start(comp_button)
+
+ self.tutorial_store_details = gtk.VBox (False, 5)
+ back_button = gtk.Button('Back')
+
+ self.tutorial_store_details.pack_start(back_button)
+ self.tutorial_store_details.pack_start(tutorial_title_bar)
+ self.tutorial_store_details.pack_start(tuto_descrip_label)
+ self.tutorial_store_details.pack_start(tutorial_button_bar)
+
+
+ tuto_icon.show()
+ full_star_icon1.show()
+ full_star_icon2.show()
+ full_star_icon3.show()
+ grayed_star_icon.show()
+ half_star_icon.show()
+ tuto_title_label.show()
+ tuto_author_label.show()
+ title_autor_box.show()
+ tuto_descrip_label.show()
+ tutorial_title_bar.show()
+ tutorial_button_bar.show()
+ download_button.show()
+ infos_button.show()
+ comp_button.show()
+ self.tutorial_store_details.show()
diff --git a/Workshop.activity/TutorialStoreHome.py b/Workshop.activity/TutorialStoreHome.py
new file mode 100644
index 0000000..8a361dc
--- /dev/null
+++ b/Workshop.activity/TutorialStoreHome.py
@@ -0,0 +1,109 @@
+import logging
+import TutorialStoreCategories
+import TutorialStoreSearch
+import TutorialStoreSuggestion
+import TutorialStoreResults
+import TutorialStoreDetails
+
+import sys, os
+import gtk
+
+class TutorialStoreHome:
+ def log(self,widget,data=None):
+ logging.info('Tutorial Store Home start')
+
+ def __init__(self):
+
+ self.categories = TutorialStoreCategories.TutorialStoreCategories()
+ categories_frame = self.categories.categorie_box_frame
+
+ self.search = TutorialStoreSearch.TutorialStoreSearch()
+ tutorial_store_search = self.search.tutorial_store_search
+
+ self.search_button = self.search.search_button_access()
+
+ self.suggestion = TutorialStoreSuggestion.TutorialStoreSuggestion()
+
+ tut_store_suggestion = gtk.HBox(homogeneous=True, spacing=5)
+ tut_store_suggestion.pack_start(self.suggestion.top_five_frame, expand=False, fill=False, padding=0)
+ tut_store_suggestion.pack_start(self.suggestion.also_like_frame, expand=False, fill=False, padding=0)
+
+ self.results = TutorialStoreResults.TutorialStoreResults()
+
+ tut_store_home_base = gtk.VBox(False, 5)
+ tut_store_home_base.pack_start(tutorial_store_search, False, False, 25)
+ tut_store_home_base.pack_start(tut_store_suggestion, False, False, 0)
+
+ self.labeltest = gtk.Label('Test')
+
+ self.tutorial_store_home = gtk.HBox(False, 5)
+ self.tutorial_store_home.pack_start(categories_frame, True, True, 5)
+ self.tutorial_store_home.pack_start(tut_store_home_base, True, True, 5)
+ self.tutorial_store_home.pack_start(tut_store_home_base, True, True, 5)
+
+ tut_store_suggestion.show()
+ categories_frame.show()
+ tut_store_home_base.show()
+ self.tutorial_store_home.show()
+
+ def get_search_button(self):
+
+ return self.search_button
+
+ def get_more_button(self):
+ return self.suggestion.get_more_button()
+
+ def get_results_widget(self):
+
+ self.search = TutorialStoreSearch.TutorialStoreSearch()
+ tutorial_store_search = self.search.tutorial_store_search
+
+ self.results = TutorialStoreResults.TutorialStoreResults()
+ tutorial_store_results = self.results.tutorial_store_results
+
+ self.categories = TutorialStoreCategories.TutorialStoreCategories()
+ categories_frame = self.categories.categorie_box_frame
+
+ tut_store_home_base = gtk.VBox(False, 5)
+ tut_store_home_base.pack_start(tutorial_store_search, False, False, 25)
+ tut_store_home_base.pack_start(tutorial_store_results, False, False, 0)
+
+ self.tutorial_store_home = gtk.HBox(False, 5)
+ self.tutorial_store_home.pack_start(categories_frame, True, True, 5)
+ self.tutorial_store_home.pack_start(tut_store_home_base, True, True, 5)
+
+ tut_store_home_base.show()
+ tutorial_store_search.show()
+ tutorial_store_results.show()
+ categories_frame.show()
+ self.tutorial_store_home.show()
+
+ return self.tutorial_store_home
+
+ def get_details_widget(self):
+
+ self.search = TutorialStoreSearch.TutorialStoreSearch()
+ tutorial_store_search = self.search.tutorial_store_search
+
+ self.details = TutorialStoreDetails.TutorialStoreDetails()
+ tutorial_store_details = self.details.tutorial_store_details
+
+ self.categories = TutorialStoreCategories.TutorialStoreCategories()
+ categories_frame = self.categories.categorie_box_frame
+
+ tut_store_home_base = gtk.VBox(False, 5)
+ tut_store_home_base.pack_start(tutorial_store_search, False, False, 25)
+ tut_store_home_base.pack_start(tutorial_store_details, False, False, 0)
+
+ self.tutorial_store_home = gtk.HBox(False, 5)
+ self.tutorial_store_home.pack_start(categories_frame, True, True, 5)
+ self.tutorial_store_home.pack_start(tut_store_home_base, True, True, 5)
+
+ tut_store_home_base.show()
+ tutorial_store_search.show()
+ tutorial_store_details.show()
+ categories_frame.show()
+ self.tutorial_store_home.show()
+
+ return self.tutorial_store_home
+
diff --git a/Workshop.activity/TutorialStoreResults.py b/Workshop.activity/TutorialStoreResults.py
new file mode 100644
index 0000000..0e8e54c
--- /dev/null
+++ b/Workshop.activity/TutorialStoreResults.py
@@ -0,0 +1,124 @@
+import sys, os
+import gtk
+
+class TutorialStoreResults:
+
+ def __init__(self):
+ tuto_icon1 = gtk.Image()
+ tuto_icon1.set_from_file('icon.svg')
+ tuto_icon2 = gtk.Image()
+ tuto_icon2.set_from_file('icon.svg')
+
+ full_star_icon1 = gtk.Image()
+ full_star_icon1.set_from_file('full_star.svg')
+ full_star_icon2 = gtk.Image()
+ full_star_icon2.set_from_file('full_star.svg')
+ full_star_icon3 = gtk.Image()
+ full_star_icon3.set_from_file('full_star.svg')
+ full_star_icon4 = gtk.Image()
+ full_star_icon4.set_from_file('full_star.svg')
+ full_star_icon5 = gtk.Image()
+ full_star_icon5.set_from_file('full_star.svg')
+
+ grayed_star_icon1 = gtk.Image()
+ grayed_star_icon1.set_from_file('grayed_star.svg')
+ grayed_star_icon2 = gtk.Image()
+ grayed_star_icon2.set_from_file('grayed_star.svg')
+ grayed_star_icon3 = gtk.Image()
+ grayed_star_icon3.set_from_file('grayed_star.svg')
+ grayed_star_icon4 = gtk.Image()
+ grayed_star_icon4.set_from_file('grayed_star.svg')
+
+ half_star_icon1= gtk.Image()
+ half_star_icon1.set_from_file('half_star.svg')
+
+ tuto_title_label1 = gtk.Label('Titre 1')
+ tuto_title_label2 = gtk.Label('Titre 2')
+
+ tuto_descrip_label1 = gtk.Label('Description 1 : Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.')
+ tuto_descrip_label1.set_line_wrap(True)
+ tuto_descrip_label1.set_width_chars(50)
+ tuto_descrip_label2 = gtk.Label('Description 2 : Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.')
+ tuto_descrip_label2.set_line_wrap(True)
+ tuto_descrip_label2.set_width_chars(50)
+
+ download_button1 = gtk.Button('Download')
+ download_button2 = gtk.Button('Download')
+ details_button1 = gtk.Button('Details')
+ details_button2 = gtk.Button('Details')
+
+ show_resuls_label = gtk.Label('Showing results 1-29 of 109')
+ # TODO : Probably must be something else than a label, or find a way to listen to click event on label ...
+ next_results_label = gtk.Label('< 1 2 3 4 >')
+
+ tutorial_title_bar1 = gtk.HBox (False, 0)
+ tutorial_title_bar1.pack_start(tuto_icon1)
+ tutorial_title_bar1.pack_start(tuto_title_label1)
+ tutorial_title_bar1.pack_start(full_star_icon1)
+ tutorial_title_bar1.pack_start(full_star_icon2)
+ tutorial_title_bar1.pack_start(full_star_icon3)
+ tutorial_title_bar1.pack_start(grayed_star_icon1)
+ tutorial_title_bar1.pack_start(grayed_star_icon2)
+
+ tutorial_button_bar1 = gtk.HBox(True, 25)
+ tutorial_button_bar1.pack_start(download_button1)
+ tutorial_button_bar1.pack_start(details_button1)
+
+ tutorial1 = gtk.VBox (False, 5)
+ tutorial1.pack_start(tutorial_title_bar1)
+ tutorial1.pack_start(tuto_descrip_label1)
+ tutorial1.pack_start(tutorial_button_bar1)
+
+ tutorial_title_bar2 = gtk.HBox (False, 5)
+ tutorial_title_bar2.pack_start(tuto_icon2)
+ tutorial_title_bar2.pack_start(tuto_title_label2)
+ tutorial_title_bar2.pack_start(full_star_icon4)
+ tutorial_title_bar2.pack_start(full_star_icon5)
+ tutorial_title_bar2.pack_start(half_star_icon1)
+ tutorial_title_bar2.pack_start(grayed_star_icon3)
+ tutorial_title_bar2.pack_start(grayed_star_icon4)
+
+ tutorial_button_bar2 = gtk.HBox(True, 55)
+ tutorial_button_bar2.pack_start(download_button2)
+ tutorial_button_bar2.pack_start(details_button2)
+
+ tutorial2 = gtk.VBox (False, 5)
+ tutorial2.pack_start(tutorial_title_bar2)
+ tutorial2.pack_start(tuto_descrip_label2)
+ tutorial2.pack_start(tutorial_button_bar2)
+
+ self.tutorial_store_results = gtk.VBox(False, 10)
+ self.tutorial_store_results.pack_start(tutorial1, True, True, 10)
+ self.tutorial_store_results.pack_start(tutorial2, True, True, 10)
+ self.tutorial_store_results.pack_start(show_resuls_label)
+ self.tutorial_store_results.pack_start(next_results_label)
+
+ tuto_icon1.show()
+ tuto_icon2.show()
+ full_star_icon1.show()
+ full_star_icon2.show()
+ full_star_icon3.show()
+ full_star_icon4.show()
+ full_star_icon5.show()
+ grayed_star_icon1.show()
+ grayed_star_icon2.show()
+ grayed_star_icon3.show()
+ grayed_star_icon4.show()
+ half_star_icon1.show()
+ tuto_title_label1.show()
+ tuto_title_label2.show()
+ tuto_descrip_label1.show()
+ tuto_descrip_label2.show()
+ download_button1.show()
+ download_button2.show()
+ details_button1.show()
+ details_button2.show()
+ show_resuls_label.show()
+ next_results_label.show()
+ tutorial_title_bar1.show()
+ tutorial_title_bar2.show()
+ tutorial_button_bar1.show()
+ tutorial_button_bar2.show()
+ tutorial1.show()
+ tutorial2.show()
+ self.tutorial_store_results.show() \ No newline at end of file
diff --git a/Workshop.activity/TutorialStoreSearch.py b/Workshop.activity/TutorialStoreSearch.py
new file mode 100644
index 0000000..1663f80
--- /dev/null
+++ b/Workshop.activity/TutorialStoreSearch.py
@@ -0,0 +1,33 @@
+import sys, os
+import gtk
+
+class TutorialStoreSearch:
+
+ def __init__(self):
+ search_label = gtk.Label('Search :')
+ search_box = gtk.Entry(400)
+ in_label = gtk.Label('in')
+ search_combobox = gtk.combo_box_new_text()
+ search_combobox.insert_text(0, 'all Categories (109)')
+ search_combobox.insert_text(1, 'Math (8)')
+ search_combobox.insert_text(2, 'Physics (16)')
+ search_combobox.insert_text(3, 'History (32)')
+ search_combobox.insert_text(4, 'Learning (53)')
+ self.search_button = gtk.Button('Search')
+
+ self.tutorial_store_search = gtk.HBox(False, 5)
+ self.tutorial_store_search.pack_start(search_label, True, True, 5)
+ self.tutorial_store_search.pack_start(search_box, True, True, 5)
+ self.tutorial_store_search.pack_start(in_label, True, True, 5)
+ self.tutorial_store_search.pack_start(search_combobox, True, True, 5)
+ self.tutorial_store_search.pack_start(self.search_button, True, True, 5)
+
+ search_label.show()
+ search_box.show()
+ in_label.show()
+ search_combobox.show()
+ self.search_button.show()
+ self.tutorial_store_search.show()
+
+ def search_button_access(self):
+ return self.search_button \ No newline at end of file
diff --git a/Workshop.activity/TutorialStoreSuggestion.py b/Workshop.activity/TutorialStoreSuggestion.py
new file mode 100644
index 0000000..9164ca0
--- /dev/null
+++ b/Workshop.activity/TutorialStoreSuggestion.py
@@ -0,0 +1,139 @@
+import sys, os
+import gtk
+
+class TutorialStoreSuggestion:
+
+ def __init__(self):
+
+ tutorial1 = gtk.HBox(homogeneous=True, spacing=0)
+ tutorial2 = gtk.HBox(homogeneous=True, spacing=0)
+ tutorial3 = gtk.HBox(homogeneous=True, spacing=0)
+ tutorial4 = gtk.HBox(homogeneous=True, spacing=0)
+ tutorial5 = gtk.HBox(homogeneous=True, spacing=0)
+ tutorial6 = gtk.HBox(homogeneous=True, spacing=0)
+
+ icon1 = gtk.Image()
+ icon1.set_from_file('icon.svg')
+ icon2 = gtk.Image()
+ icon2.set_from_file('icon.svg')
+ icon3 = gtk.Image()
+ icon3.set_from_file('icon.svg')
+ icon4 = gtk.Image()
+ icon4.set_from_file('icon.svg')
+ icon5 = gtk.Image()
+ icon5.set_from_file('icon.svg')
+ icon6 = gtk.Image()
+ icon6.set_from_file('icon.svg')
+
+ label1 = gtk.Label('Tuto 1')
+ label2 = gtk.Label('Tuto 2')
+ label3 = gtk.Label('Tuto 3')
+
+ label4 = gtk.Label('Tuto 4')
+ label5 = gtk.Label('Tuto 5')
+ label6 = gtk.Label('Tuto 6')
+
+ self.more_button1 = gtk.Button('More')
+ more_button2 = gtk.Button('More')
+ more_button3 = gtk.Button('More')
+ more_button4 = gtk.Button('More')
+ more_button5 = gtk.Button('More')
+ more_button6 = gtk.Button('More')
+
+ tutorial1.pack_start(icon1, expand=True, fill=True, padding=4)
+ tutorial1.pack_start(label1, expand=True, fill=True, padding=0)
+ tutorial1.pack_start(self.more_button1, expand=False, fill=False, padding=5)
+ tutorial1_frame = gtk.Frame()
+ tutorial1_frame.add(tutorial1)
+ tutorial1_frame.modify_bg(gtk.STATE_NORMAL, gtk.gdk.Color(0,0,0))
+
+ tutorial2.pack_start(icon2, expand=True, fill=True, padding=4)
+ tutorial2.pack_start(label2, expand=True, fill=True, padding=0)
+ tutorial2.pack_start(more_button2, expand=False, fill=False, padding=5)
+ tutorial2_frame = gtk.Frame()
+ tutorial2_frame.add(tutorial2)
+ tutorial2_frame.modify_bg(gtk.STATE_NORMAL, gtk.gdk.Color(0,0,0))
+
+ tutorial3.pack_start(icon3, expand=True, fill=True, padding=4)
+ tutorial3.pack_start(label3, expand=True, fill=True, padding=0)
+ tutorial3.pack_start(more_button3, expand=False, fill=False, padding=5)
+ tutorial3_frame = gtk.Frame()
+ tutorial3_frame.add(tutorial3)
+ tutorial3_frame.modify_bg(gtk.STATE_NORMAL, gtk.gdk.Color(0,0,0))
+
+ tutorial4.pack_start(icon4, expand=True, fill=True, padding=4)
+ tutorial4.pack_start(label4, expand=True, fill=True, padding=0)
+ tutorial4.pack_start(more_button4, expand=False, fill=False, padding=5)
+ tutorial4_frame = gtk.Frame()
+ tutorial4_frame.add(tutorial4)
+ tutorial4_frame.modify_bg(gtk.STATE_NORMAL, gtk.gdk.Color(0,0,0))
+
+ tutorial5.pack_start(icon5, expand=True, fill=True, padding=4)
+ tutorial5.pack_start(label5, expand=True, fill=True, padding=0)
+ tutorial5.pack_start(more_button5, expand=False, fill=False, padding=5)
+ tutorial5_frame = gtk.Frame()
+ tutorial5_frame.add(tutorial5)
+ tutorial5_frame.modify_bg(gtk.STATE_NORMAL, gtk.gdk.Color(0,0,0))
+
+ tutorial6.pack_start(icon6, expand=True, fill=True, padding=4)
+ tutorial6.pack_start(label6, expand=True, fill=True, padding=0)
+ tutorial6.pack_start(more_button6, expand=False, fill=False, padding=5)
+ tutorial6_frame = gtk.Frame()
+ tutorial6_frame.add(tutorial6)
+ tutorial6_frame.modify_bg(gtk.STATE_NORMAL, gtk.gdk.Color(0,0,0))
+
+ top_five = gtk.VBox(homogeneous=True, spacing=0)
+ self.top_five_frame = gtk.Frame('Top 5 Most Popular')
+## top_five_frame.modify_bg(gtk.STATE_NORMAL, gtk.gdk.Color(0,0,0))
+
+ top_five.pack_start(tutorial1_frame, expand=True, fill=True, padding=0)
+ top_five.pack_start(tutorial2_frame, expand=True, fill=True, padding=0)
+ top_five.pack_start(tutorial3_frame, expand=True, fill=True, padding=0)
+
+ also_like = gtk.VBox(homogeneous=True, spacing=0)
+ self.also_like_frame = gtk.Frame('You might also like :')
+
+ also_like.pack_start(tutorial4_frame, expand=True, fill=True, padding=0)
+ also_like.pack_start(tutorial5_frame, expand=True, fill=True, padding=0)
+ also_like.pack_start(tutorial6_frame, expand=True, fill=True, padding=0)
+
+ self.top_five_frame.add(top_five)
+ self.also_like_frame.add(also_like)
+
+ label1.show()
+ label2.show()
+ label3.show()
+ label4.show()
+ label5.show()
+ label6.show()
+ icon1.show()
+ icon2.show()
+ icon3.show()
+ icon4.show()
+ icon5.show()
+ icon6.show()
+ self.more_button1.show()
+ more_button2.show()
+ more_button3.show()
+ more_button4.show()
+ more_button5.show()
+ more_button6.show()
+ tutorial1_frame.show()
+ tutorial2_frame.show()
+ tutorial3_frame.show()
+ tutorial4_frame.show()
+ tutorial5_frame.show()
+ tutorial6_frame.show()
+ tutorial1.show()
+ tutorial2.show()
+ tutorial3.show()
+ tutorial4.show()
+ tutorial5.show()
+ tutorial6.show()
+ top_five.show()
+ self.top_five_frame.show()
+ also_like.show()
+ self.also_like_frame.show()
+
+ def get_more_button(self):
+ return self.more_button1 \ No newline at end of file
diff --git a/Workshop.activity/TutoriusActivity.py b/Workshop.activity/TutoriusActivity.py
new file mode 100755
index 0000000..b262a9f
--- /dev/null
+++ b/Workshop.activity/TutoriusActivity.py
@@ -0,0 +1,77 @@
+from sugar.activity import activity
+import TutorialStoreHome
+from Workshop import WorkshopView
+import logging
+
+import sys, os
+import gtk
+from dialogs import LoginDialog
+
+class TutoriusActivity(activity.Activity):
+ def hello(self,widget,data=None):
+ logging.info('Hello world')
+
+ def callback(self, widget, button_string):
+
+ if button_string == 'search_button':
+ self.right_container.remove(self.tutorial_store_home.tutorial_store_home)
+
+ results_widget = self.tutorial_store_home.get_results_widget()
+
+ self.right_container.pack_start(results_widget)
+
+ results_widget.show()
+ self.right_container.show()
+
+ elif button_string == 'more_button':
+ self.right_container.remove(self.tutorial_store_home.tutorial_store_home)
+
+ details_widget = self.tutorial_store_home.get_details_widget()
+
+ self.right_container.pack_start(details_widget)
+
+ details_widget.show()
+ self.right_container.show()
+
+ def __init__(self,handle):
+ print "running activity init", handle
+ activity.Activity.__init__(self,handle)
+ print "actiity running"
+
+ toolbox = activity.ActivityToolbox(self)
+ self.set_toolbox(toolbox)
+ toolbox.show()
+
+ self.table = gtk.HPaned()
+ self.table.set_position(100)
+ self.left_container = gtk.VBox()
+ btn1 = gtk.Button("My tutorials")
+ btn2 = gtk.Button("Tutorial Store")
+ btn3 = gtk.Button("test button")
+
+ self.left_container.pack_start(btn1,expand=False)
+ self.left_container.pack_start(btn2,expand=False)
+ self.tutorial_store_home = TutorialStoreHome.TutorialStoreHome()
+
+ tutorial_store_search_button = self.tutorial_store_home.get_search_button()
+ tutorial_store_search_button.connect("clicked", self.callback, 'search_button')
+
+ tutorial_store_more_button = self.tutorial_store_home.get_more_button()
+ tutorial_store_more_button.connect("clicked", self.callback, 'more_button')
+
+ self.right_container = gtk.VBox()
+ #self.right_container.pack_start(self.tutorial_store_home.tutorial_store_home)
+
+ self.workshop = WorkshopView()
+
+ self.table.add1(self.left_container)
+ self.table.add2(self.workshop)
+ self.set_canvas(self.table)
+ btn3.show()
+ btn1.show()
+ btn2.show()
+ self.left_container.show()
+ self.workshop.show()
+ self.table.show()
+
+ print "AT THE END OF THE CLASS"
diff --git a/Workshop.activity/Workshop.py b/Workshop.activity/Workshop.py
new file mode 100644
index 0000000..1ca6be2
--- /dev/null
+++ b/Workshop.activity/Workshop.py
@@ -0,0 +1,285 @@
+import gtk
+
+from WorkshopListItem import WorkshopListItem,Rating
+from WorkshopModel import WorkshopModel
+from WorkshopController import WorkshopController
+
+class WorkshopView(gtk.Alignment):
+ def __init__(self):
+ gtk.Alignment.__init__(self,0.0,0.0,1.0,1.0)
+ self.model = WorkshopModel(self)
+ self.controller = WorkshopController(self,self.model)
+
+ self.mainView = WorkshopMain(self.controller)
+ self.detailView = WorkshopDetail(None,self.controller)
+
+ self.add(self.mainView)
+
+ self.mainView.show()
+ self.detailView.show()
+
+ self.controller.tutorial_query(None,None)
+
+ def set_tutorial_list(self,tutorial_list):
+ """
+ Set the list of tutorial to display in the main View
+ Refresh the View
+
+ @param tutorial_list the list of tutorial
+ """
+ self.mainView.set_tutorial_list(tutorial_list)
+
+
+ def change_sorting(self,sorting_key):
+ """
+ Sort the list of tutorial base on the sorting_key
+
+ @param sorting_key the tutorial metadata to use to sort the tutorials
+ """
+ self.mainView.change_sorting(sorting_key)
+
+ def refresh_tutorial_info(self):
+ """
+ Tell the view to refresh the content of the tutorial list because some info have changed
+
+ Call this function when information about tutorial have changed, but the tutorial to display are the same
+ """
+ pass
+
+ def toggle_tutorial_publish(self,tutorial,published):
+ """
+ Change the publish/unpublish status of the tutorial on the view
+
+ @param tutorial the tutorial to change the status
+ @param published True if the tutorial is published, False if not
+ """
+ pass
+
+ def display_detail(self,tutorial):
+ """
+ Displays the detail view of a tutorial
+
+ @param tutorial the tutorial to display
+ """
+ self.mainView.hide()
+ self.remove(self.mainView)
+ print tutorial.name
+ self.detailView = WorkshopDetail(tutorial,self.controller)
+ self.add(self.detailView)
+ self.detailView.show()
+
+ def display_main_view(self):
+ """
+ Displays the main view of the Workshop
+ """
+ self.detailView.hide()
+ self.remove(self.detailView)
+ self.add(self.mainView)
+ self.mainView.show()
+
+ def display_info_dialog(self,tutorial):
+ """
+ Displays the infos dialog on a tutorial
+
+ @param tutorial the tutorial to edit
+ """
+ pass
+
+class WorkshopMain(gtk.VBox):
+ def __init__(self,controller):
+ gtk.VBox.__init__(self,False,10)
+
+ self.controller = controller
+
+ self.set_border_width(10)
+ self.search_bar = SearchBar(self.controller)
+ self.pack_start(self.search_bar,False,False)
+
+ sep = gtk.HSeparator()
+ self.pack_start(sep,False,False)
+ self.main_container = gtk.ScrolledWindow()
+ self.main_container.set_policy(gtk.POLICY_AUTOMATIC,gtk.POLICY_AUTOMATIC)
+
+ self.list_container= gtk.VBox()
+
+ self.main_container.add_with_viewport(self.list_container)
+ self.pack_start(self.main_container)
+
+ self.search_bar.show()
+ self.list_container.show()
+ self.main_container.show()
+ sep.show()
+ self.tutorial_list = []
+ self.sorting_key = 'Name'
+
+
+ def change_sorting(self,sorting):
+ self.sorting_key = sorting
+ self.sort_tutorial()
+
+ def sort_tutorial(self):
+ self.tutorial_list.sort(lambda x, y:
+ cmp(str(getattr(x,self.sorting_key.lower())).lower(),str(getattr(y,self.sorting_key.lower())).lower()))
+ self.refresh_tutorial_display()
+
+ def set_tutorial_list(self,tutorial_list):
+ self.tutorial_list = tutorial_list
+ self.sort_tutorial()
+
+ def refresh_tutorial_display(self):
+ for child in self.list_container.get_children():
+ self.list_container.remove(child)
+ for tuto in self.tutorial_list:
+ item = WorkshopListItem(tuto,self.controller)
+ self.list_container.pack_start(item)
+ item.show()
+
+class SearchBar(gtk.HBox):
+ def __init__(self,controller):
+ gtk.HBox.__init__(self,False,10)
+ self.set_border_width(5)
+ self.controller = controller
+
+ self.search_entry = gtk.Entry(400)
+ self.search_button = gtk.Button("Go")
+ self.separator = gtk.VSeparator()
+ self.sort_label = gtk.Label("Sort by")
+ self.sort_combo = gtk.combo_box_new_text()
+ self.sort_combo.insert_text(0,"Name")
+ self.sort_combo.insert_text(1,"Rating")
+ self.sort_combo.set_active(0)
+ self.selected_sorting = self.sort_combo.get_active_text()
+
+ self.pack_start(self.search_entry,padding=5)
+ self.pack_start(self.search_button,False,False,padding=10)
+ self.pack_start(self.separator,False,False,padding=10)
+ self.pack_start(self.sort_label,False,False,padding=5)
+ self.pack_start(self.sort_combo,)
+
+ self.search_entry.show()
+ self.search_button.show()
+ self.separator.show()
+ self.sort_label.show()
+ self.sort_combo.show()
+
+ self.search_button.connect("clicked",self.controller.tutorial_query,self.search_entry.get_text())
+ self.sort_combo.connect("changed",self.controller.sort_selection_changed,None)
+
+ def get_sorting(self):
+ return self.selected_sorting
+
+ sorting = property(get_sorting)
+
+
+
+class WorkshopDetail(gtk.VBox):
+ def __init__(self,tutorial,controller):
+ if tutorial is None:
+ return
+
+ self.title_text = '<span size="xx-large">%(title)s</span>'
+ self.author_text = '<span size="large">by %(author)s</span>'
+ self.desc_text = 'Description: %(description)s'
+ self.controller = controller
+ gtk.VBox.__init__(self,False,10)
+ self.set_border_width(10)
+
+ first_row = gtk.HBox(False)
+ back_image = gtk.Image()
+ back_image.set_from_file('arrow_back.png')
+ self.back_button = gtk.Button("Back")
+ self.back_button.set_image(back_image)
+
+ first_row.pack_start(self.back_button,False,False)
+
+ second_row = gtk.HBox(False)
+ icon = gtk.Image()
+ icon.set_from_file('icon.svg')
+
+ label_holder = gtk.VBox(False,10)
+
+ self.title_label = gtk.Label(tutorial.name)
+ self.title_label.set_alignment(0.0,0.5)
+ self.author_label = gtk.Label(tutorial.author)
+ self.author_label.set_alignment(0.05,0.5)
+
+ label_holder.pack_start(self.title_label)
+ label_holder.pack_start(self.author_label)
+
+ self.rating = Rating(tutorial.rating)
+
+ second_row.pack_start(icon,False,False)
+ second_row.pack_start(label_holder)
+ second_row.pack_end(self.rating,False,False)
+
+ self.desc_view = gtk.TextView()
+ self.desc_buff = gtk.TextBuffer()
+ self.desc_buff.set_text(tutorial.description)
+ self.desc_view.set_buffer(self.desc_buff)
+ self.desc_view.set_editable(False)
+ self.desc_view.set_wrap_mode(gtk.WRAP_WORD)
+ self.desc_view.set_cursor_visible(False)
+ self.desc_view.connect("realize",self.realize_cb,None)
+ self.desc_view.modify_base(gtk.STATE_NORMAL, gtk.gdk.color_parse("gray") )
+
+ fourth_row = gtk.HBox(False,15)
+ self.launch_button = gtk.Button('Launch')
+ self.launch_button.get_child().set_markup(self.title_text %{"title":"Launch"})
+ self.edit_button = gtk.Button('Edit')
+ self.edit_button.get_child().set_markup(self.title_text %{"title":"Edit"})
+ self.update_button = gtk.Button('Update')
+ self.update_button.get_child().set_markup(self.title_text %{"title":"Update"})
+ self.info_button = gtk.Button('Infos')
+ self.info_button.get_child().set_markup(self.title_text %{"title":"Infos"})
+ self.delete_button = gtk.Button('Delete')
+ self.delete_button.get_child().set_markup(self.title_text %{"title":"Delete"})
+
+ fourth_row.pack_start(self.launch_button,False,False)
+ fourth_row.pack_start(self.edit_button,False,False)
+ fourth_row.pack_start(self.update_button,False,False)
+ fourth_row.pack_start(self.info_button,False,False)
+ fourth_row.pack_end(self.delete_button,False,False)
+
+ fifth_row = gtk.HBox(False,15)
+ self.publish_button = gtk.Button('')
+ self.publish_button.get_child().set_markup(self.title_text %{"title":"Publish"})
+ self.unpublish_button = gtk.Button('')
+ self.unpublish_button.get_child().set_markup(self.title_text %{"title":"Unpublish"})
+
+ fifth_row.pack_start(self.publish_button,False,False)
+ fifth_row.pack_start(self.unpublish_button,False,False)
+
+ self.pack_start(first_row,False,False)
+ self.pack_start(second_row,False,False)
+ self.pack_start(self.desc_view)
+ self.pack_end(fifth_row,False,False)
+ self.pack_end(fourth_row,False,False)
+
+
+ self.back_button.show()
+ first_row.show()
+ self.title_label.show()
+ self.author_label.show()
+ self.rating.show()
+ label_holder.show()
+ second_row.show()
+ icon.show()
+ self.desc_view.show()
+ self.launch_button.show()
+ self.edit_button.show()
+ self.update_button.show()
+ self.info_button.show()
+ self.delete_button.show()
+ fourth_row.show()
+
+ self.publish_button.show()
+ self.unpublish_button.show()
+ fifth_row.show()
+
+ self.title_label.set_markup(self.title_text % {"title":tutorial.name})
+ self.author_label.set_markup(self.author_text % {"author":tutorial.author})
+
+ self.back_button.connect("clicked",self.controller.back_pressed,None)
+
+ def realize_cb(self,widget,data=None):
+ widget.get_window(gtk.TEXT_WINDOW_TEXT).set_cursor(gtk.gdk.Cursor(gtk.gdk.LEFT_PTR)) \ No newline at end of file
diff --git a/Workshop.activity/WorkshopController.py b/Workshop.activity/WorkshopController.py
new file mode 100644
index 0000000..ba192cf
--- /dev/null
+++ b/Workshop.activity/WorkshopController.py
@@ -0,0 +1,119 @@
+"""
+WorkshopController
+
+This module handles user action from the workshop view
+
+"""
+import logging
+from WorkshopModel import Tutorial
+class WorkshopController():
+ def __init__(self,view,model):
+ self.view = view
+ self.model = model
+
+ def tutorial_query(self,widget,keyword):
+ """
+ Handles query from the view
+
+ @param widget the widget that sent the query
+ @param keyword the keyword for the query, empty to get all tutorials
+ """
+ self.model.query(keyword)
+
+ def sort_selection_changed(self,widget,data):
+ """
+ Handles selection changes in the sorting selection
+
+ @param widget the widget that sent the query
+ @param sort the property to use to sort tutorial
+ """
+ sorting = widget.get_active_text()
+ self.view.change_sorting(sorting)
+
+
+ def launch_tutorial_triggered(self,widget,tutorial):
+ """
+ Handles start tutorial action
+
+ @param widget the widget that triggered the action
+ @param tutorial the tutorial to launch
+ """
+ pass
+
+ def show_details(self,widget,tutorial):
+ """
+ show the details for a tutorial
+
+ @param widget the widget that made the call
+ @param tutorial the tutorial to
+ """
+ self.view.display_detail(tutorial)
+
+ def back_pressed(self,widget,data):
+ """
+ When in detail view, go back to general view
+
+ @param widget the widget that made the call
+ @param data not used
+ """
+ self.view.display_main_view()
+
+ def rate_tutorial(self,widget,data):
+ """
+ Change the rating for a tutorial
+
+ @param widget the widget that made the call
+ @param data a tuple (tutorial,new_rating)
+ """
+ pass
+
+ def edit_tutorial(self,widget,tutorial):
+ """
+ Edit the tutorial in the detail view
+
+ @param widget the widget that made the call
+ @param tutorial the tutorial to edit
+ """
+ pass
+
+ def update_tutorial(self,widget,tutorial):
+ """
+ Need to know what this do
+ """
+ pass
+
+ def info_tutorial(self,widget,tutorial):
+ """
+ Edit the infos about the tutorial
+
+ @param widget the widget that made the call
+ @param tutorial the tutorial to edit
+ """
+ pass
+
+ def delete_tutorial(self,widget,tutorial):
+ """
+ Delete a tutorial
+
+ @param widget the widget that made the call
+ @param tutorial the tutorial to delete
+ """
+ self.model.delete_tutorial(tutorial)
+
+ def publish_tutorial(self,widget,tutorial):
+ """
+ Publish a tutorial
+
+ @param widget the widget that made the call
+ @param tutorial the tutorial to publish
+ """
+ self.model.publish_tutorial(tutorial)
+
+ def unpublish_tutorial(self,widget,tutorial):
+ """
+ Unpublish a tutorial
+
+ @param widget the widget that made the call
+ @param tutorial the tutorial to unpublish
+ """
+ self.model.unpublish_tutorial(tutorial)
diff --git a/Workshop.activity/WorkshopListItem.py b/Workshop.activity/WorkshopListItem.py
new file mode 100644
index 0000000..0828f2c
--- /dev/null
+++ b/Workshop.activity/WorkshopListItem.py
@@ -0,0 +1,78 @@
+import gtk
+
+class WorkshopListItem(gtk.Alignment):
+ def __init__(self,tutorial,controller):
+ gtk.Alignment.__init__(self,0.0,0.0,1.0,1.0)
+ self.tutorial = tutorial
+ self.controller = controller
+
+ self.title_text = '<span size="xx-large">%(title)s</span>'
+ self.table = gtk.Table(3,3,False)
+
+ self.lbl_title = gtk.Label('')
+ self.lbl_desc = gtk.Label(tutorial.description)
+ self.lbl_desc.set_line_wrap(True)
+ self.btn_launch = gtk.Button('Launch')
+ launch_align= gtk.Alignment(0.0,1.0,0.0,0.0)
+ launch_align.add(self.btn_launch)
+ self.btn_details = gtk.Button('Details')
+ self.icon = gtk.Image()
+ self.icon.set_from_file('icon.svg')
+ self.rating = Rating(tutorial.rating)
+
+ self.table.attach(self.icon,0,1,0,1,0,0)
+ self.table.attach(self.lbl_title,1,2,0,1,yoptions=0)
+ self.table.attach(self.lbl_desc,1,2,1,2,xoptions=gtk.FILL,yoptions=gtk.EXPAND)
+ self.table.attach(launch_align,1,2,2,3,gtk.FILL)
+ self.table.attach(self.btn_details,2,3,2,3,0,0)
+ self.table.attach(self.rating,2,3,0,2,0,0)
+
+ self.table.set_row_spacing(1,10)
+
+ self.lbl_title.set_alignment(0.0,0.5)
+ self.lbl_title.set_markup(self.title_text % {'title':tutorial.name})
+ self.lbl_desc.set_alignment(0.0,0.5)
+
+ self.table.show()
+ self.icon.show()
+ self.lbl_title.show()
+ launch_align.show()
+ self.lbl_desc.show()
+ self.btn_launch.show()
+ self.btn_details.show()
+ self.rating.show()
+
+ self.add(self.table)
+
+ self.btn_details.connect("clicked",self.controller.show_details,self.tutorial)
+
+class Rating(gtk.HBox):
+ def __init__(self,rating):
+ gtk.HBox.__init__(self,False,4)
+ value = rating
+ self.stars = [0,0,0,0,0]
+ for x in range(5):
+ if value -1 > 0:
+ self.stars[x]=1
+ elif value -1 == -0.5:
+ self.stars[x] = 0.5
+ break
+ else:
+ self.stars[x]=1
+ break
+ value -= 1
+ self.prepare_image()
+
+ def prepare_image(self):
+ for x in self.stars:
+ if x == 0:
+ filename='grayed_star.png'
+ elif x == 0.5:
+ filename='half_star.png'
+ elif x == 1:
+ filename='full_star.png'
+ image = gtk.Image()
+ image.set_from_file(filename)
+ self.pack_start(image)
+ image.show()
+
diff --git a/Workshop.activity/WorkshopModel.py b/Workshop.activity/WorkshopModel.py
new file mode 100644
index 0000000..ebc5579
--- /dev/null
+++ b/Workshop.activity/WorkshopModel.py
@@ -0,0 +1,162 @@
+"""
+WorkshopModel
+
+This module is the model of the Workshop Activity
+"""
+
+from sugar.tutorius.vault import *
+
+class WorkshopModel():
+ def __init__(self,view):
+ self.view = view
+
+ def query(self,keyword):
+ """
+ Query the vault for tutorial that are linked with the keyword
+ Update the currently managed tutorials and notifies the view that the managed tutorials have changed
+
+ @param keyword the keyword for the query
+ """
+## t1 = Tutorial({'name':'tuto 1',"description":"This is the description","rating":5})
+## t2 = Tutorial({'name':'tuto 2',"description":"This is the description of another","rating":4})
+## t3 = Tutorial({'name':'tuto 3',"description":"This is the description oh the last","rating":3})
+## t4 = Tutorial({'name':'tuto 4',"description":"This is the description oh the last","rating":1})
+## t5 = Tutorial({'name':'tuto 5',"description":"This is the description oh the last","rating":1})
+## t6 = Tutorial({'name':'tuto 6',"description":"This is the description oh the last","rating":1})
+## t7 = Tutorial({'name':'tuto 7',"description":"This is the description oh the last","rating":1})
+## tutorial_list = [t1,t2,t3,t4,t5,t6,t7]
+
+ vault_return = Vault.query()
+ tutorial_list = []
+ for tuto in vault_return:
+ tutorial_list.append(Tutorial(tuto))
+
+ self.view.set_tutorial_list(tutorial_list)
+
+ def delete_tutorial(self,tutorial):
+ """
+ Delete a tutorial and updated the currently managed tutorials
+ Notifies the view that the manages tutorials have changed
+
+ @param tutorial the tutorial to delete
+ """
+ pass
+
+ def update_tutorial_infos(self,tutorial, new_infos):
+ """
+ Updates the metadata on a tutorial and updates the currently managed tutorials
+ Notifies the view tha the managed tutorials have changed
+
+ @param tutorial the tutorial to update
+ @param new_infos a dictionnary of the new informations i.e. {"title":"tut1","author":"Max Power"}
+ """
+ pass
+
+ def publish_tutorial(self,tutorial):
+ """
+ Publishes a tutorial
+
+ Details to come
+ """
+ pass
+
+ def unpublish_tutorial(self,tutorial):
+ """
+ Unpublishes a tutorial
+
+ Details to come
+ """
+ pass
+
+
+class Tutorial():
+ """
+ Wrapper for tutorial metadata
+ """
+ def __init__(self,metadata_dict):
+ self.__original_dict = metadata_dict
+ self.__update_dict = metadata_dict
+
+ if 'name' in self.__original_dict:
+ self.__name = self.__original_dict['name']
+ else:
+ self.__description = name
+
+ if 'description' in self.__original_dict:
+ self.__description = self.__original_dict['description']
+ else:
+ self.__description = None
+
+ if 'author' in self.__original_dict:
+ self.__author = self.__original_dict['author']
+ else:
+ self.__author = None
+
+ if 'rating' in self.__original_dict:
+ self.__rating = self.__original_dict['rating']
+ else:
+ self.__rating = 0
+
+ if 'publish_state' in self.__original_dict:
+ self.__published_state = self.__original_dict['publish_state']
+ else:
+ self.__published_state = None
+
+ if 'guid' in self.__original_dict:
+ self.__id = self.__original_dict['guid']
+ else:
+ self.__id = None
+
+ def get_name(self):
+ return self.__name
+
+ def set_name(self,name):
+ self.__name = name
+
+ def get_description(self):
+ return self.__description
+
+ def set_description(self,description):
+ self.__description = description
+ self.__update_dict['Description'] = description
+
+ def get_author(self):
+ return self.__author
+
+ def set_author(self,author):
+ self.__author = author
+ self.__update_dict['Author'] = author
+
+ def get_rating(self):
+ return self.__rating
+
+ def set_rating(self,rating):
+ self.__rating = rating
+ self.__update_dict['Rating'] = rating
+
+ def get_published_state(self):
+ return self.__published_state
+
+ def set_published_state(self,published_state):
+ self.__published_state = published_state
+ self.__update_dict['PublishedState'] = published_state
+
+ def get_id(self):
+ return self.__id
+
+ def set_id(self,id):
+ self.__id = id
+ self.__update_dict['TutorialId'] = id
+
+ def get_updated_metadata(self):
+ return self.__update_dict
+
+ name = property(get_name,set_name)
+ description = property(get_description,set_description)
+ author = property(get_author,set_author)
+ rating = property(get_rating,set_rating)
+ published_state = property(get_published_state,set_published_state)
+ id = property(get_id,set_id)
+ updated_metadata = property(get_updated_metadata)
+
+
diff --git a/Workshop.activity/activity/activity.info b/Workshop.activity/activity/activity.info
new file mode 100644
index 0000000..02dbee1
--- /dev/null
+++ b/Workshop.activity/activity/activity.info
@@ -0,0 +1,8 @@
+[Activity]
+name = Tutorius
+bundle_id = org.laptop.TutoriusActivity
+class = TutoriusActivity.TutoriusActivity
+icon = someicon
+activity_version = 1
+host_version = 1
+show_launcher = yes
diff --git a/Workshop.activity/activity/someicon.svg b/Workshop.activity/activity/someicon.svg
new file mode 100644
index 0000000..bb28f04
--- /dev/null
+++ b/Workshop.activity/activity/someicon.svg
@@ -0,0 +1,21 @@
+<?xml version="1.0" ?><!-- Created with Inkscape (http://www.inkscape.org/) --><!DOCTYPE svg PUBLIC '-//W3C//DTD SVG 1.1//EN' 'http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd' [
+ <!ENTITY stroke_color "#000000">
+ <!ENTITY fill_color "#ffffff">
+]><svg height="55px" id="svg2393" inkscape:output_extension="org.inkscape.output.svg.inkscape" inkscape:version="0.47pre1 r21720" sodipodi:docname="tutortool.svg" sodipodi:version="0.32" version="1.1" width="55px" xmlns="http://www.w3.org/2000/svg" xmlns:cc="http://creativecommons.org/ns#" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" xmlns:svg="http://www.w3.org/2000/svg">
+ <defs id="defs2395">
+ <inkscape:perspective id="perspective2401" inkscape:persp3d-origin="16 : 10.666667 : 1" inkscape:vp_x="0 : 16 : 1" inkscape:vp_y="0 : 1000 : 0" inkscape:vp_z="32 : 16 : 1" sodipodi:type="inkscape:persp3d"/>
+ </defs>
+ <sodipodi:namedview bordercolor="#666666" borderopacity="1.0" id="base" inkscape:current-layer="layer1" inkscape:cx="3.7661233" inkscape:cy="33.132055" inkscape:document-units="px" inkscape:grid-bbox="true" inkscape:pageopacity="0.0" inkscape:pageshadow="2" inkscape:window-height="675" inkscape:window-maximized="0" inkscape:window-width="1057" inkscape:window-x="108" inkscape:window-y="45" inkscape:zoom="3.9590209" pagecolor="#ffffff" showgrid="true"/>
+ <metadata id="metadata2398">
+ <rdf:RDF>
+ <cc:Work rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage"/>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <g id="layer1" inkscape:groupmode="layer" inkscape:label="Layer 1" transform="translate(0,23)">
+ <path d="m 38.01548,1.5478747 c 0,7.1837999 -7.3667,13.0141283 -16.443525,13.0141283 -2.269208,0 -8.124729,3.152936 -13.9608513,4.252763 C 13.382177,14.110994 11.434521,11.926642 9.9463815,10.748864 6.9701032,8.3933076 5.1284282,5.1397735 5.1284282,1.5478747 c 0,-7.1837994 7.3666998,-13.0141297 16.4435268,-13.0141297 9.076825,0 16.443525,5.8303303 16.443525,13.0141297 z" id="path2403" sodipodi:nodetypes="cscsssc" style="fill:&fill_color;;fill-opacity:1;fill-rule:nonzero;stroke:&stroke_color;;stroke-width:1.96931934;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"/>
+ <path d="m 50.150276,6.4721386 c 0,2.621116 -1.428036,4.9953144 -3.735846,6.7142344 -1.153905,0.85946 -1.824287,2.434433 1.398853,6.784273 -6.258422,-3.991066 -8.65379,-4.001712 -10.413335,-4.001712 -7.03818,0 -12.750327,-4.254565 -12.750327,-9.4967954 0,-5.2422321 5.712147,-9.4967971 12.750327,-9.4967971 7.038182,0 12.750328,4.254565 12.750328,9.4967971 z" id="path3175" sodipodi:nodetypes="cccsssc" style="fill:&fill_color;;fill-opacity:1;fill-rule:nonzero;stroke:&stroke_color;;stroke-width:1.96931934;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"/>
+ </g>
+</svg> \ No newline at end of file
diff --git a/Workshop.activity/arrow_back.png b/Workshop.activity/arrow_back.png
new file mode 100644
index 0000000..8263674
--- /dev/null
+++ b/Workshop.activity/arrow_back.png
Binary files differ
diff --git a/Workshop.activity/dialogs.py b/Workshop.activity/dialogs.py
new file mode 100644
index 0000000..1cf135d
--- /dev/null
+++ b/Workshop.activity/dialogs.py
@@ -0,0 +1,139 @@
+import gtk
+
+class LoginDialog(gtk.Dialog):
+ def __init__(self):
+ gtk.Dialog.__init__(self,"Login",None,gtk.DIALOG_MODAL)
+ ok_button = gtk.Button("Login")
+ ok_button.connect("clicked",self.ok_clicked,None)
+ ok_button.show()
+ self.action_area.pack_start(ok_button)
+ self.add_button("Cancel",gtk.RESPONSE_REJECT)
+ self.set_resizable(False)
+ self.prepare_dialog()
+
+
+ def prepare_dialog(self):
+ self.user_line = gtk.HBox(False,10)
+ self.pass_line = gtk.HBox(False,10)
+ self.register_line = gtk.HBox(False)
+ self.remember_line = gtk.HBox(False)
+
+ self.username_lbl = gtk.Label("Username:")
+ self.username_entry = gtk.Entry()
+ self.username_entry.set_width_chars(40)
+
+ self.password_lbl = gtk.Label("Password:")
+ self.password_entry = gtk.Entry()
+ self.password_entry.set_visibility(False)
+ self.password_entry.set_width_chars(40)
+
+ self.register_me = gtk.LinkButton("","Register Now!")
+ self.not_registered = gtk.Label("Not Registered? ")
+
+ self.remember_user = gtk.CheckButton("Remember my username",False)
+
+ self.register_me.connect("clicked",self.click_link,None)
+
+ self.user_line.pack_start(self.username_lbl,False)
+ self.user_line.pack_end(self.username_entry,False)
+
+ self.pass_line.pack_start(self.password_lbl,False)
+ self.pass_line.pack_end(self.password_entry,False)
+
+ self.register_line.pack_end(self.register_me,False)
+ self.register_line.pack_end(self.not_registered,False)
+
+ self.remember_line.pack_start(self.remember_user,False,padding=80)
+
+ self.vbox.pack_start(self.register_line,False)
+ self.vbox.pack_start(self.user_line,False,padding=10)
+ self.vbox.pack_start(self.pass_line,False,padding=10)
+ self.vbox.pack_start(self.remember_line,False)
+
+ self.vbox.show()
+ self.user_line.show()
+ self.pass_line.show()
+ self.register_line.show()
+ self.remember_line.show()
+
+ self.username_lbl.show()
+ self.username_entry.show()
+
+ self.password_lbl.show()
+ self.password_entry.show()
+
+ self.register_me.show()
+ self.not_registered.show()
+
+ self.remember_user.show()
+
+ def ok_clicked(self,widget,data=None):
+ self.response(gtk.RESPONSE_ACCEPT)
+
+ def click_link(self,widget,data=None):
+ self.register_dialog = RegisterDialog()
+ self.register_dialog.run()
+ self.register_dialog.destroy()
+
+class RegisterDialog(gtk.Dialog):
+ def __init__(self):
+ gtk.Dialog.__init__(self,"Register",None,gtk.DIALOG_MODAL)
+ ok_button = gtk.Button("Register")
+ ok_button.connect("clicked",self.ok_clicked,None)
+ ok_button.show()
+ self.action_area.pack_start(ok_button)
+ self.add_button("Cancel",gtk.RESPONSE_REJECT)
+ self.set_resizable(False)
+ self.create_content()
+
+ def ok_clicked(self,widget,data=None):
+ self.response(gtk.RESPONSE_ACCEPT)
+
+ def create_content(self):
+ entry_length = 40
+ table = gtk.Table(10,4,False)
+ labels = ["Email address","Name","Username","Password","Confirmation","Location","Web Site"]
+ required=[True,True,True,True,True,False,False]
+ hidden=[False,False,False,True,True,False,False]
+ for x in range(6):
+ lbl = gtk.Label(labels[x]+":")
+ entry = gtk.Entry()
+ entry.set_width_chars(entry_length)
+ table.attach(lbl,0,1,x,x+1,xpadding=10)
+ table.attach(entry,1,2,x,x+1,ypadding=10)
+ if hidden[x]:
+ entry.set_visibility(False)
+ if required[x]:
+ required_lbl = gtk.Label("*")
+ table.attach(required_lbl,2,3,x,x+1)
+ required_lbl.show()
+ lbl.set_alignment(0.0,0.5)
+ lbl.show()
+ entry.show()
+
+
+ if_required = gtk.Label(" * Required Field")
+ table.attach(if_required,3,4,0,1)
+
+ under_13 = gtk.CheckButton("I am 13 years old or younger",False)
+ legal = gtk.CheckButton("I have read the",False)
+ legal_notices=gtk.LinkButton('',"legal notices")
+ and_lbl = gtk.Label("and")
+ privacy = gtk.LinkButton('',"privacy statement")
+ hbox = gtk.HBox(False,0)
+ hbox.pack_start(legal)
+ hbox.pack_start(legal_notices)
+ hbox.pack_start(and_lbl)
+ hbox.pack_start(privacy)
+
+ table.attach(under_13,1,2,7,8)
+ table.attach(hbox,1,2,8,9)
+ under_13.show()
+ legal.show()
+ legal_notices.show()
+ and_lbl.show()
+ privacy.show()
+ hbox.show()
+ if_required.show()
+ table.show()
+ self.vbox.pack_start(table)
diff --git a/Workshop.activity/full_star.png b/Workshop.activity/full_star.png
new file mode 100644
index 0000000..9f7d095
--- /dev/null
+++ b/Workshop.activity/full_star.png
Binary files differ
diff --git a/Workshop.activity/grayed_star.png b/Workshop.activity/grayed_star.png
new file mode 100644
index 0000000..7f8b1e1
--- /dev/null
+++ b/Workshop.activity/grayed_star.png
Binary files differ
diff --git a/Workshop.activity/half_star.png b/Workshop.activity/half_star.png
new file mode 100644
index 0000000..48ebae3
--- /dev/null
+++ b/Workshop.activity/half_star.png
Binary files differ
diff --git a/Workshop.activity/icon.svg b/Workshop.activity/icon.svg
new file mode 100644
index 0000000..bb28f04
--- /dev/null
+++ b/Workshop.activity/icon.svg
@@ -0,0 +1,21 @@
+<?xml version="1.0" ?><!-- Created with Inkscape (http://www.inkscape.org/) --><!DOCTYPE svg PUBLIC '-//W3C//DTD SVG 1.1//EN' 'http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd' [
+ <!ENTITY stroke_color "#000000">
+ <!ENTITY fill_color "#ffffff">
+]><svg height="55px" id="svg2393" inkscape:output_extension="org.inkscape.output.svg.inkscape" inkscape:version="0.47pre1 r21720" sodipodi:docname="tutortool.svg" sodipodi:version="0.32" version="1.1" width="55px" xmlns="http://www.w3.org/2000/svg" xmlns:cc="http://creativecommons.org/ns#" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" xmlns:svg="http://www.w3.org/2000/svg">
+ <defs id="defs2395">
+ <inkscape:perspective id="perspective2401" inkscape:persp3d-origin="16 : 10.666667 : 1" inkscape:vp_x="0 : 16 : 1" inkscape:vp_y="0 : 1000 : 0" inkscape:vp_z="32 : 16 : 1" sodipodi:type="inkscape:persp3d"/>
+ </defs>
+ <sodipodi:namedview bordercolor="#666666" borderopacity="1.0" id="base" inkscape:current-layer="layer1" inkscape:cx="3.7661233" inkscape:cy="33.132055" inkscape:document-units="px" inkscape:grid-bbox="true" inkscape:pageopacity="0.0" inkscape:pageshadow="2" inkscape:window-height="675" inkscape:window-maximized="0" inkscape:window-width="1057" inkscape:window-x="108" inkscape:window-y="45" inkscape:zoom="3.9590209" pagecolor="#ffffff" showgrid="true"/>
+ <metadata id="metadata2398">
+ <rdf:RDF>
+ <cc:Work rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage"/>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <g id="layer1" inkscape:groupmode="layer" inkscape:label="Layer 1" transform="translate(0,23)">
+ <path d="m 38.01548,1.5478747 c 0,7.1837999 -7.3667,13.0141283 -16.443525,13.0141283 -2.269208,0 -8.124729,3.152936 -13.9608513,4.252763 C 13.382177,14.110994 11.434521,11.926642 9.9463815,10.748864 6.9701032,8.3933076 5.1284282,5.1397735 5.1284282,1.5478747 c 0,-7.1837994 7.3666998,-13.0141297 16.4435268,-13.0141297 9.076825,0 16.443525,5.8303303 16.443525,13.0141297 z" id="path2403" sodipodi:nodetypes="cscsssc" style="fill:&fill_color;;fill-opacity:1;fill-rule:nonzero;stroke:&stroke_color;;stroke-width:1.96931934;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"/>
+ <path d="m 50.150276,6.4721386 c 0,2.621116 -1.428036,4.9953144 -3.735846,6.7142344 -1.153905,0.85946 -1.824287,2.434433 1.398853,6.784273 -6.258422,-3.991066 -8.65379,-4.001712 -10.413335,-4.001712 -7.03818,0 -12.750327,-4.254565 -12.750327,-9.4967954 0,-5.2422321 5.712147,-9.4967971 12.750327,-9.4967971 7.038182,0 12.750328,4.254565 12.750328,9.4967971 z" id="path3175" sodipodi:nodetypes="cccsssc" style="fill:&fill_color;;fill-opacity:1;fill-rule:nonzero;stroke:&stroke_color;;stroke-width:1.96931934;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"/>
+ </g>
+</svg> \ No newline at end of file
diff --git a/Workshop.activity/setup.py b/Workshop.activity/setup.py
new file mode 100755
index 0000000..f95b630
--- /dev/null
+++ b/Workshop.activity/setup.py
@@ -0,0 +1,3 @@
+#! /usr/bin/env python
+from sugar.activity import bundlebuilder
+bundlebuilder.start()
diff --git a/addons/gtkwidgettypefilter.py b/addons/gtkwidgettypefilter.py
index 16673c1..816a754 100644
--- a/addons/gtkwidgettypefilter.py
+++ b/addons/gtkwidgettypefilter.py
@@ -30,7 +30,7 @@ class GtkWidgetTypeFilter(EventFilter):
text = TStringProperty("")
strokes = TArrayProperty([])
- def __init__(self, next_state, object_id, text=None, strokes=None):
+ def __init__(self, object_id, text=None, strokes=None):
"""Constructor
@param next_state default EventFilter param, passed on to EventFilter
@param object_id object tree-ish identifier
@@ -39,7 +39,7 @@ class GtkWidgetTypeFilter(EventFilter):
At least one of text or strokes must be supplied
"""
- super(GtkWidgetTypeFilter, self).__init__(next_state)
+ super(GtkWidgetTypeFilter, self).__init__()
self.object_id = object_id
self.text = text
self._captext = ""
diff --git a/addons/triggereventfilter.py b/addons/triggereventfilter.py
index 06c0995..acc0d0d 100644
--- a/addons/triggereventfilter.py
+++ b/addons/triggereventfilter.py
@@ -23,8 +23,8 @@ class TriggerEventFilter(EventFilter):
Used to fake events and see the effect on the FSM.
"""
- def __init__(self, next_state):
- EventFilter.__init__(self, next_state)
+ def __init__(self):
+ EventFilter.__init__(self)
self.toggle_on_callback = False
def install_handlers(self, callback, **kwargs):
diff --git a/tests/bundlertests.py b/tests/bundlertests.py
deleted file mode 100644
index ad8d1bb..0000000
--- a/tests/bundlertests.py
+++ /dev/null
@@ -1,134 +0,0 @@
-# Copyright (C) 2009, Tutorius.org
-# Copyright (C) 2009, Charles-Etienne Carriere <iso.swiffer@gmail.com>
-#
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
-"""
-Bundler tests
-
-This module contains all the tests for the storage mecanisms for tutorials
-This mean testing savins and loading tutorial, .ini file management and
-adding ressources to tutorial
-"""
-
-import unittest
-import os
-import uuid
-
-from sugar.tutorius import bundler
-
-##class VaultTests(unittest.TestCase):
-## def setUp(self):
-## pass
-##
-## def tearDown(self):
-## pass
-##
-## def test_basicQuery(self):
-## vault = Vault()
-##
-## list_metadata = vault.query(keyword='tutorial', startIndex=2, numResults=5)
-##
-## assert len(list_metadata) <= 5
-##
-## def test_advancedQuery(self):
-## vault = Vault()
-##
-## list_metadata = vault.query(keyword='', category='Math', startIndex=10, numResults=10)
-##
-## assert len(list_metadata) <= 10
-##
-## pass
-##
-## def test_installTutorial(self):
-## # Create a new tutorial
-##
-##
-## xml_serializer = XmlSerializer()
-##
-##
-## xml_serializer.save_fsm()
-##
-## def test_deleteTutorial(self):
-## pass
-##
-## def test_saveTutorial(self):
-## pass
-##
-## def test_readTutorial(self):
-## pass
-##
-## def _generateSampleTutorial(self):
-## """
-## Creates a new tutorial and bundles it.
-##
-## @return The UUID for the new tutorial.
-## """
-## self._fsm = FiniteStateMachine("Sample testing FSM")
-## # Add a few states
-## act1 = addon.create('BubbleMessage', message="Hi", pos=[300, 450])
-## ev1 = addon.create('GtkWidgetEventFilter', "0.12.31.2.2", "clicked", "FINAL")
-## act2 = addon.create('BubbleMessage', message="Second message", pos=[250, 150], tailpos=[1,2])
-##
-## st1 = State("INIT")
-## st1.add_action(act1)
-## st1.add_event_filter(ev1)
-##
-## st2 = State("FINAL")
-## st2.add_action(act2)
-##
-## self._fsm.add_state(st1)
-## self._fsm.add_state(st2)
-##
-## xml_ser = XmlSerializer()
-##
-## os.makedirs(os.path.join(sugar.tutorius.bundler._get_store_root(), str(self.uuid)))
-##
-## # xml_ser.save_fsm(self._fsm, TUTORIAL_FILENAME,
-
-class TutorialBundlerTests(unittest.TestCase):
-
- def setUp(self):
-
- #generate a test GUID
- self.test_guid = uuid.uuid1()
- self.guid_path = os.path.join(bundler._get_store_root(),str(self.test_guid))
- os.mkdir(self.guid_path)
-
- self.ini_file = os.path.join(self.guid_path, "meta.ini")
-
- f = open(self.ini_file,'w')
- f.write("[GENERAL_METADATA]")
- f.write(os.linesep)
- f.write("GUID:")
- f.write(str(self.test_guid))
- f.close()
-
- def tearDown(self):
- os.remove(self.ini_file)
- os.rmdir(self.guid_path)
-
- def test_add_ressource(self):
- bund = bundler.TutorialBundler(unicode(self.test_guid))
-
- temp_file = open("test.txt",'w')
- temp_file.write('test')
- temp_file.close()
-
- bund.add_resources("text", "test.txt")
-
- assert os.path.exists(os.path.join(self.guid_path,"test.txt")), "add_ressource did not create the file"
-
-if __name__ == "__main__":
- unittest.main()
diff --git a/tests/coretests.py b/tests/coretests.py
index 4f564c8..b2f68e5 100644
--- a/tests/coretests.py
+++ b/tests/coretests.py
@@ -130,9 +130,9 @@ class StateTest(unittest.TestCase):
Tests the fact that the event filters are correctly installed on setup
and uninstalled on teardown.
"""
- event_filter = addon.create('TriggerEventFilter', "second_state")
+ event_filter = addon.create('TriggerEventFilter')
- state = State("event_test", event_filter_list=[event_filter])
+ state = State("event_test", event_filter_list=[(event_filter, "next_state")])
state.set_tutorial(SimpleTutorial())
assert event_filter.toggle_on_callback == False, "Wrong init of event_filter"
@@ -198,17 +198,20 @@ class StateTest(unittest.TestCase):
def test_add_event_filter(self):
state = State("INIT")
- event1 = addon.create('TriggerEventFilter', "s")
- event2 = addon.create('TriggerEventFilter', "t")
- event3 = addon.create('TriggerEventFilter', "r")
+ event1 = addon.create('TriggerEventFilter')
+ # MJM : 2009-10-21 : Commenting the below as per new FSM standard, a state cannot
+ # have more than one event filter with the same properties (no identical
+ # properties containers)
+ #event2 = addon.create('TriggerEventFilter')
+ #event3 = addon.create('TriggerEventFilter')
# Insert the event filters
- assert state.add_event_filter(event1), "Could not add event filter 1"
- assert state.add_event_filter(event2), "Could not add event filter 2"
- assert state.add_event_filter(event3), "Could not add event filter 3"
+ assert state.add_event_filter(event1, 's'), "Could not add event filter 1"
+ #assert state.add_event_filter(event2, 't'), "Could not add event filter 2"
+ #assert state.add_event_filter(event3, 'r'), "Could not add event filter 3"
# Make sure we cannot insert an event twice
- assert state.add_event_filter(event1) == False, "Could add twice the event filter"
+ assert state.add_event_filter(event1, 's') == False, "Could add twice the event filter"
# Get the list of event filters
event_filters = state.get_event_filter_list()
@@ -243,19 +246,19 @@ class StateTest(unittest.TestCase):
act1 = addon.create("BubbleMessage", message="Hi", position=[132,450])
act2 = addon.create("BubbleMessage", message="Hi", position=[132,450])
- event1 = addon.create("GtkWidgetEventFilter", "nextState", "0.0.0.1.1.2.3.1", "clicked")
+ event1 = addon.create("GtkWidgetEventFilter", "0.0.0.1.1.2.3.1", "clicked")
act3 = addon.create("DialogMessage", message="Hello again.", position=[200, 400])
# Build the first state
st1.add_action(act1)
st1.add_action(act3)
- st1.add_event_filter(event1)
+ st1.add_event_filter(event1, 'nextState')
# Build the second state
st2.add_action(act2)
st2.add_action(act3)
- st2.add_event_filter(event1)
+ st2.add_event_filter(event1, 'nextState')
# Make sure that they are identical for now
assert st1 == st2, "States should be considered as identical"
@@ -282,14 +285,14 @@ class StateTest(unittest.TestCase):
assert not (st1 == st3), "States having a different number of actions should be different"
st4 = copy.deepcopy(st1)
- st4.add_event_filter(addon.create("GtkWidgetEventFilter", "next_state", "0.0.1.1.2.2.3", "clicked"))
+ st4.add_event_filter(addon.create("GtkWidgetEventFilter", "0.0.1.1.2.2.3", "clicked"), "next_state")
assert not (st1 == st4), "States having a different number of events should be different"
st5 = copy.deepcopy(st1)
- st5._event_filters = []
+ st5.clear_event_filters()
- st5.add_event_filter(addon.create("GtkWidgetEventFilter", "other_state", "0.1.2.3.4.1.2", "pressed"))
+ st5.add_event_filter(addon.create("GtkWidgetEventFilter", "0.1.2.3.4.1.2", "pressed"), "other_state")
#import rpdb2; rpdb2.start_embedded_debugger('pass')
assert not (st1 == st5), "States having the same number of event filters" \
@@ -305,10 +308,10 @@ class FSMTest(unittest.TestCase):
act_init = TrueWhileActiveAction()
act_second = TrueWhileActiveAction()
- event_init = FakeEventFilter("SECOND")
+ event_init = FakeEventFilter()
content = {
- "INIT": State("INIT", action_list=[act_init],event_filter_list=[event_init]),
+ "INIT": State("INIT", action_list=[act_init],event_filter_list=[(event_init, "SECOND")]),
"SECOND": State("SECOND", action_list=[act_second])
}
@@ -399,9 +402,9 @@ class FSMTest(unittest.TestCase):
This test removes a state from the FSM. It also verifies that the links
from other states going into the removed state are gone.
"""
- st1 = State("INIT", event_filter_list=[addon.create('TriggerEventFilter', "second")])
- st2 = State("second", event_filter_list=[addon.create('TriggerEventFilter', "third")])
- st3 = State("third", event_filter_list=[addon.create('TriggerEventFilter', "second")])
+ st1 = State("INIT", event_filter_list=[(addon.create('TriggerEventFilter'), "second")])
+ st2 = State("second", event_filter_list=[(addon.create('TriggerEventFilter'), "third")])
+ st3 = State("third", event_filter_list=[(addon.create('TriggerEventFilter'), "second")])
fsm = FiniteStateMachine("StateRemovalTest")
@@ -582,13 +585,13 @@ class FSMExplorationTests(unittest.TestCase):
"""
st1 = State("INIT")
st1.add_action(CountAction())
- st1.add_event_filter(addon.create('TriggerEventFilter', "Second"))
- st1.add_event_filter(addon.create('TriggerEventFilter', "Third"))
+ st1.add_event_filter(addon.create('TriggerEventFilter'), "Second")
+ st1.add_event_filter(addon.create('TriggerEventFilter'), "Third")
st2 = State("Second")
st2.add_action(TrueWhileActiveAction())
- st2.add_event_filter(addon.create('TriggerEventFilter', "Third"))
- st2.add_event_filter(addon.create('TriggerEventFilter', "Fourth"))
+ st2.add_event_filter(addon.create('TriggerEventFilter'), "Third")
+ st2.add_event_filter(addon.create('TriggerEventFilter'), "Fourth")
st3 = State("Third")
st3.add_action(CountAction())
diff --git a/tests/filterstests.py b/tests/filterstests.py
index c45f924..ee6033b 100644
--- a/tests/filterstests.py
+++ b/tests/filterstests.py
@@ -32,20 +32,10 @@ from gtkutilstests import SignalCatcher
class BaseEventFilterTests(unittest.TestCase):
"""Test the behavior of the Base EventFilter class"""
- def test_properties(self):
- """Test EventFilter properties"""
- e = EventFilter("NEXTSTATE")
-
- assert e.next_state == "NEXTSTATE", "next_state should have value used in constructor"
-
- e.next_state = "NEWSTATE"
-
- assert e.next_state == "NEWSTATE", "next_state should have been changed by setter"
-
def test_callback(self):
"""Test the callback mechanism"""
- e = EventFilter("Next")
+ e = EventFilter()
s = SignalCatcher()
#Trigger the do_callback, shouldn't do anything
@@ -79,7 +69,7 @@ class TestTimerEvent(unittest.TestCase):
ctx = gobject.MainContext()
main = gobject.MainLoop(ctx)
- e = addon.create('TimerEvent', "Next", 2) # 2 seconds should be enough :s
+ e = addon.create('TimerEvent', 2) # 2 seconds should be enough :s
s = SignalCatcher()
e.install_handlers(s.callback)
@@ -122,7 +112,7 @@ class TestTimerEvent(unittest.TestCase):
ctx = gobject.MainContext()
main = gobject.MainLoop(ctx)
- e = addon.create('TimerEvent', "Next", 2) # 2 seconds should be enough :s
+ e = addon.create('TimerEvent', 2) # 2 seconds should be enough :s
s = SignalCatcher()
e.install_handlers(s.callback)
@@ -169,7 +159,7 @@ class TestGtkWidgetEventFilter(unittest.TestCase):
self.top.add(self.btn1)
def test_install(self):
- h = addon.create('GtkWidgetEventFilter', "Next","0","whatever")
+ h = addon.create('GtkWidgetEventFilter', "0","whatever")
try:
h.install_handlers(None)
@@ -178,7 +168,7 @@ class TestGtkWidgetEventFilter(unittest.TestCase):
assert True, "Install should have failed"
def test_button_clicks(self):
- h = addon.create('GtkWidgetEventFilter', "Next","0.0","clicked")
+ h = addon.create('GtkWidgetEventFilter', "0.0","clicked")
s = SignalCatcher()
h.install_handlers(s.callback, activity=self.top)
diff --git a/tests/linear_creatortests.py b/tests/linear_creatortests.py
index 999f4d5..8b13656 100644
--- a/tests/linear_creatortests.py
+++ b/tests/linear_creatortests.py
@@ -35,11 +35,11 @@ class CreatorTests(unittest.TestCase):
creator.action(CountAction())
creator.action(CountAction())
- creator.event(TriggerEventFilter("Not important"))
+ creator.event(TriggerEventFilter())
creator.action(CountAction())
- creator.event(TriggerEventFilter("Not good either..."))
+ creator.event(TriggerEventFilter())
fsm = creator.generate_fsm()
@@ -50,13 +50,13 @@ class CreatorTests(unittest.TestCase):
assert len(init_state.get_action_list()) == 2, "Creator did not insert all the actions"
- assert init_state.get_event_filter_list()[0].get_next_state() == "State 1" , "expected next state to be 'State 1' but got %s" % init_state.get_event_filter_list()[0].get_next_state()
+ assert init_state.get_event_filter_list()[0][1] == "State 1" , "expected next state to be 'State 1' but got %s" % init_state.get_event_filter_list()[0].get_next_state()
state1 = fsm.get_state_by_name("State 1")
assert len(state1.get_action_list()) == 1, "Creator did not insert all the actions"
- assert state1.get_event_filter_list()[0].get_next_state() == "State 2"
+ assert state1.get_event_filter_list()[0][1] == "State 2"
# Make sure we have the final state and that it's empty
state2 = fsm.get_state_by_name("State2")
diff --git a/tests/propertiestests.py b/tests/propertiestests.py
index 0b8251a..2494ea6 100644
--- a/tests/propertiestests.py
+++ b/tests/propertiestests.py
@@ -375,7 +375,7 @@ class TArrayPropertyTest(unittest.TestCase):
prop = TArrayProperty([1, 2, 3, 4])
obj = klass()
- assert obj.prop == [1,2,3,4], "Unable to set initial value via constructor"
+ assert obj.prop == (1,2,3,4), "Unable to set initial value via constructor"
assert klass.prop.type == "array", "Wrong type for array : %s"%klass.prop.type
diff --git a/tests/serializertests.py b/tests/serializertests.py
deleted file mode 100644
index 2f2e287..0000000
--- a/tests/serializertests.py
+++ /dev/null
@@ -1,196 +0,0 @@
-# Copyright (C) 2009, Tutorius.org
-# Copyright (C) 2009, Jean-Christophe Savard <savard.jean.christophe@gmail.com>
-#
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
-"""
-Serialization Tests
-
-This module contains all the tests that pertain to the usage of the Tutorius
-Serializer object. This means testing saving a tutorial dictionary to a .tml
-file, loading the list of tutorials for this activity and building chosen
-tutorial.
-"""
-
-import unittest
-
-import os
-import shutil
-
-from sugar.tutorius import bundler, addon
-from sugar.tutorius.core import State, FiniteStateMachine
-from sugar.tutorius.actions import *
-from sugar.tutorius.filters import *
-from sugar.tutorius.bundler import XMLSerializer, Serializer
-import sugar
-from uuid import uuid1
-
-class SerializerInterfaceTest(unittest.TestCase):
- """
- For completeness' sake.
- """
- def test_save(self):
- ser = Serializer()
-
- try:
- ser.save_fsm(None)
- assert False, "save_fsm() should throw an unimplemented error"
- except:
- pass
-
- def test_load(self):
- ser = Serializer()
-
- try:
- ser.load_fsm(str(uuid.uuid1()))
- assert False, "load_fsm() should throw an unimplemented error"
- except:
- pass
-
-class XMLSerializerTest(unittest.TestCase):
- """
- Tests the transformation of XML to FSM, then back.
- """
- def setUp(self):
- # Make the serializer believe the test is in a activity path
- self.testpath = "/tmp/testdata/"
- os.environ["SUGAR_BUNDLE_PATH"] = self.testpath
- os.environ["SUGAR_PREFIX"] = self.testpath
- os.environ["SUGAR_PROFILE"] = 'test'
-## os.mkdir(sugar.tutorius.bundler._get_store_root())
-
- # Create the sample FSM
- self.fsm = FiniteStateMachine("testingMachine")
-
- # Add a few states
- act1 = addon.create('BubbleMessage', message="Hi", position=[300, 450])
- ev1 = addon.create('GtkWidgetEventFilter', "0.12.31.2.2", "clicked", "Second")
- act2 = addon.create('BubbleMessage', message="Second message", position=[250, 150], tail_pos=[1,2])
-
- st1 = State("INIT")
- st1.add_action(act1)
- st1.add_event_filter(ev1)
-
- st2 = State("Second")
-
- st2.add_action(act2)
-
- self.fsm.add_state(st1)
- self.fsm.add_state(st2)
-
- self.uuid = uuid1()
-
- # Flag to set to True if the output can be deleted after execution of
- # the test
- self.remove = True
-
- def tearDown(self):
- """
- Removes the created files, if need be.
- """
- if self.remove == True:
- shutil.rmtree(os.path.join(os.getenv("HOME"),".sugar",os.getenv("SUGAR_PROFILE")))
- if os.path.isdir(self.testpath):
- shutil.rmtree(self.testpath)
-
- def test_save(self):
- """
- Writes an FSM to disk, then compares the file to the expected results.
- "Remove" boolean member specifies if the test data must be removed or not
- """
- xml_ser = XMLSerializer()
- os.makedirs(os.path.join(sugar.tutorius.bundler._get_store_root(), str(self.uuid)))
- xml_ser.save_fsm(self.fsm, bundler.TUTORIAL_FILENAME, os.path.join(sugar.tutorius.bundler._get_store_root(), str(self.uuid)))
-
- def test_save_and_load(self):
- """
- Load up the written FSM and compare it with the object representation.
- """
- self.test_save()
- testpath = "/tmp/testdata/"
- #rpdb2.start_embedded_debugger('flakyPass')
- xml_ser = XMLSerializer()
-
- # This interface needs to be redone... It's not clean because there is
- # a responsibility mixup between the XML reader and the bundler.
- loaded_fsm = xml_ser.load_fsm(str(self.uuid))
-
- # Compare the two FSMs
- assert loaded_fsm._states.get("INIT").name == self.fsm._states.get("INIT").name, \
- 'FSM underlying dictionary differ from original to pickled/reformed one'
- assert loaded_fsm._states.get("Second").name == self.fsm._states.get("Second").name, \
- 'FSM underlying dictionary differ from original to pickled/reformed one'
- assert loaded_fsm._states.get("INIT").get_action_list()[0].message == \
- self.fsm._states.get("INIT").get_action_list()[0].message, \
- 'FSM underlying State underlying Action differ from original to reformed one'
- assert len(loaded_fsm.get_action_list()) == 0, "FSM should not have any actions on itself"
-
- def test_all_actions(self):
- """
- Inserts all the known action types in a FSM, then attempt to load it.
- """
- st = State("INIT")
-
- act1 = addon.create('BubbleMessage', "Hi!", position=[10,120], tail_pos=[-12,30])
- act2 = addon.create('DialogMessage', "Hello again.", position=[120,10])
- act3 = addon.create('WidgetIdentifyAction')
- act4 = addon.create('DisableWidgetAction', "0.0.0.1.0.0.0")
- act5 = addon.create('TypeTextAction', "0.0.0.1.1.1.0.0", "New text")
- act6 = addon.create('ClickAction', "0.0.1.0.1.1")
- act7 = addon.create('OnceWrapper', action=act1)
- act8 = addon.create('ChainAction', actions=[act1, act2, act3, act4])
- actions = [act1, act2, act3, act4, act5, act6, act7, act8]
-
- for action in actions:
- st.add_action(action)
-
- self.fsm.remove_state("Second")
- self.fsm.remove_state("INIT")
- self.fsm.add_state(st)
-
- xml_ser = XMLSerializer()
-
- self.test_save()
-
- reloaded_fsm = xml_ser.load_fsm(str(self.uuid))
- assert self.fsm == reloaded_fsm, "Expected equivalence before saving vs after loading."
-
- def test_all_filters(self):
- """
- Inserts all the known action types in a FSM, then attempt to load it.
- """
- st = State("INIT")
-
- ev1 = addon.create('TimerEvent', "Second", 1000)
- ev2 = addon.create('GtkWidgetEventFilter', next_state="Second", object_id="0.0.1.1.0.0.1", event_name="clicked")
- ev3 = addon.create('GtkWidgetTypeFilter', "Second", "0.0.1.1.1.2.3", text="Typed stuff")
- ev4 = addon.create('GtkWidgetTypeFilter', "Second", "0.0.1.1.1.2.3", strokes="acbd")
- filters = [ev1, ev2, ev3, ev4]
-
- for filter in filters:
- st.add_event_filter(filter)
-
- self.fsm.remove_state("INIT")
- self.fsm.add_state(st)
-
- xml_ser = XMLSerializer()
-
- self.test_save()
-
- reloaded_fsm = xml_ser.load_fsm(str(self.uuid))
-
- assert self.fsm == reloaded_fsm, "Expected equivalence before saving vs after loading."
-
-if __name__ == "__main__":
- unittest.main()
diff --git a/tests/vaulttests.py b/tests/vaulttests.py
new file mode 100644
index 0000000..02c34e8
--- /dev/null
+++ b/tests/vaulttests.py
@@ -0,0 +1,516 @@
+# Copyright (C) 2009, Tutorius.org
+# Copyright (C) 2009, Jean-Christophe Savard <savard.jean.christophe@gmail.com>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+"""
+Vault Tests
+
+This module contains all the tests that pertain to the usage of the Tutorius
+Vault object. The Vault manage all the interactions with the various Tutorius
+modules dans the local file system. This include saving a tutorial to a .xml
+file, generating the metadata file, finding existing tutorials in the file
+system and building chosen tutorials.
+"""
+
+import unittest
+
+import os
+import shutil
+import zipfile
+
+from sugar.tutorius import addon
+from sugar.tutorius.core import State, FiniteStateMachine, Tutorial
+from sugar.tutorius.actions import *
+from sugar.tutorius.filters import *
+from sugar.tutorius.vault import Vault, XMLSerializer, Serializer, TutorialBundler
+
+import sugar
+
+from uuid import uuid1
+
+class VaultInterfaceTest(unittest.TestCase):
+ """
+ Test the high-level interfaces functions of the Vault
+ """
+
+ def create_test_metadata_file(self, ini_file_path, guid):
+ ini_file = open(ini_file_path, 'wt')
+ ini_file.write("[GENERAL_METADATA]\n")
+ ini_file.write('guid=' + str(guid) + '\n')
+ ini_file.write('name=TestTutorial1\n')
+ ini_file.write('version=1\n')
+ ini_file.write('description=This is a test tutorial 1\n')
+ ini_file.write('rating=3.5\n')
+ ini_file.write('category=Test\n')
+ ini_file.write('publish_state=false\n')
+ ini_file.write('[RELATED_ACTIVITIES]\n')
+ ini_file.write('org.laptop.TutoriusActivity = 1\n')
+ ini_file.write('org.laptop.Writus = 1\n')
+ ini_file.close()
+
+
+ def setUp(self):
+ os.environ["SUGAR_BUNDLE_PATH"] = os.path.join(sugar.tutorius.vault._get_store_root(), 'test_bundle_path')
+ path = os.path.join(sugar.tutorius.vault._get_store_root(), 'test_bundle_path', 'data', 'tutorius', 'data')
+ if os.path.isdir(path) != True:
+ os.makedirs(path)
+
+ # Generate a first test GUID
+ self.test_guid = uuid1()
+ self.guid_path = os.path.join(sugar.tutorius.vault._get_store_root(),str(self.test_guid))
+ os.mkdir(self.guid_path)
+
+ # Create a first dummy .ini file
+ self.ini_file_path = os.path.join(self.guid_path, "meta.ini")
+ self.create_test_metadata_file(self.ini_file_path, self.test_guid)
+
+ # Generate a second test GUID
+ self.test_guid2 = uuid1()
+ self.guid_path2 = os.path.join(sugar.tutorius.vault._get_store_root(),str(self.test_guid2))
+ os.mkdir(self.guid_path2)
+
+ # Create a second dummy .ini file
+ self.ini_file_path2 = os.path.join(self.guid_path2, "meta.ini")
+
+ ini_file2 = open(self.ini_file_path2, 'wt')
+ ini_file2.write("[GENERAL_METADATA]\n")
+ ini_file2.write('guid=' + str(self.test_guid2) + '\n')
+ ini_file2.write('name=TestTutorial2\n')
+ ini_file2.write('version=2\n')
+ ini_file2.write('description=This is a test tutorial 2\n')
+ ini_file2.write('rating=4\n')
+ ini_file2.write('category=Test2\n')
+ ini_file2.write('publish_state=false\n')
+ ini_file2.write('[RELATED_ACTIVITIES]\n')
+ ini_file2.write('org.laptop.TutoriusActivity = 2\n')
+ ini_file2.write('org.laptop.Writus = 1\n')
+ ini_file2.write('org.laptop.Testus = 1\n')
+ ini_file2.close()
+
+ # Create a dummy fsm
+ self.fsm = FiniteStateMachine("testingMachine")
+ # Add a few states
+ act1 = addon.create('BubbleMessage', message="Hi", position=[300, 450])
+ ev1 = addon.create('GtkWidgetEventFilter', "0.12.31.2.2", "clicked")
+ act2 = addon.create('BubbleMessage', message="Second message", position=[250, 150], tail_pos=[1,2])
+ st1 = State("INIT")
+ st1.add_action(act1)
+ st1.add_event_filter(ev1, 'Second')
+ st2 = State("Second")
+ st2.add_action(act2)
+ self.fsm.add_state(st1)
+ self.fsm.add_state(st2)
+ self.tuto_guid = uuid1()
+
+ # Create a dummy metadata dictionnary
+ self.test_metadata_dict = {}
+ self.save_test_guid = uuid1()
+ self.test_metadata_dict['name'] = 'TestTutorial1'
+ self.test_metadata_dict['guid'] = str(self.save_test_guid)
+ self.test_metadata_dict['version'] = '1'
+ self.test_metadata_dict['description'] = 'This is a test tutorial 1'
+ self.test_metadata_dict['rating'] = '3.5'
+ self.test_metadata_dict['category'] = 'Test'
+ self.test_metadata_dict['publish_state'] = 'false'
+ activities_dict = {}
+ activities_dict['org.laptop.tutoriusactivity'] = '1'
+ activities_dict['org.laptop,writus'] = '1'
+ self.test_metadata_dict['activities'] = activities_dict
+
+
+ def test_installTutorials(self):
+
+ # TODO : Test for erronous file too (not .xml, not .ini, not .zip, etc.)
+
+ # create dummy tutorial
+
+ # create a test folder in the file
+ # system outside the Vault
+ test_path = os.path.join(os.getenv("HOME"),".sugar", 'default', 'tutorius', 'tmp')
+ if os.path.isdir(test_path) == True:
+ shutil.rmtree(os.path.join(os.getenv("HOME"),".sugar", 'default', 'tutorius', 'tmp'))
+ os.makedirs(test_path)
+
+ # Creat a dummy tutorial .xml file
+ serializer = XMLSerializer()
+
+ serializer.save_fsm(self.fsm, 'tutorial.xml', test_path)
+
+ # Create a dummy tutorial metadata file
+ self.create_test_metadata_file(os.path.join(test_path, 'meta.ini'), self.tuto_guid)
+
+ #Zip these tutorials files in the pkzip file format
+ archive_list = [os.path.join(test_path, 'meta.ini'), os.path.join(test_path, 'tutorial.xml')]
+
+ zfilename = "TestTutorial.zip"
+
+ zout = zipfile.ZipFile(os.path.join(test_path, zfilename), "w")
+ for fname in archive_list:
+ fname_splitted = fname.rsplit('/')
+ file_only_name = fname_splitted[fname_splitted.__len__() - 1]
+ zout.write(fname, file_only_name)
+ zout.close()
+
+ # test if the file is a valid pkzip file
+ assert zipfile.is_zipfile(os.path.join(test_path, zfilename)) == True, "The zipping of the tutorial files failed."
+
+ # test installTutorials function
+ vault = Vault()
+
+ install_return = vault.installTutorials(test_path, 'TestTutorial.zip', False)
+ assert install_return != 2, "Tutorial install has returned an error"
+
+ # check if the tutorial is now in the vault
+ try :
+ bundler = TutorialBundler(self.tuto_guid)
+ bundler.get_tutorial_path(self.tuto_guid)
+ except IOError:
+ print("Cannot find the specified tutorial's GUID in the vault")
+
+
+ def test_query(self):
+ """
+ Test the query function that return a list of tutorials (dictionnaries) that
+ correspond to the specified parameters.
+ """
+
+ # Note : Temporary only test query that return ALL tutorials in the vault.
+ # TODO : Test with varying parameters
+
+ vault = Vault()
+
+ tutorial_list = vault.query()
+
+ if tutorial_list.__len__() < 2:
+ assert False, 'Error, list doesnt have enough tutorial in it : ' + str(tutorial_list.__len__()) + ' element'
+
+ for tuto_dictionnary in tutorial_list:
+ if tuto_dictionnary['name'] == 'TestTutorial1':
+ related = tuto_dictionnary['activities']
+ assert tuto_dictionnary['version'] == '1'
+ assert tuto_dictionnary['description'] == 'This is a test tutorial 1'
+ assert tuto_dictionnary['rating'] == '3.5'
+ assert tuto_dictionnary['category'] == 'Test'
+ assert tuto_dictionnary['publish_state'] == 'false'
+ assert related.has_key('org.laptop.tutoriusactivity')
+ assert related.has_key('org.laptop.writus')
+
+ elif tuto_dictionnary['name'] == 'TestTutorial2':
+ related = tuto_dictionnary['activities']
+ assert tuto_dictionnary['version'] == '2'
+ assert tuto_dictionnary['description'] == 'This is a test tutorial 2'
+ assert tuto_dictionnary['rating'] == '4'
+ assert tuto_dictionnary['category'] == 'Test2'
+ assert tuto_dictionnary['publish_state'] == 'false'
+ assert related.has_key('org.laptop.tutoriusactivity')
+ assert related.has_key('org.laptop.writus')
+ assert related.has_key('org.laptop.testus')
+
+ else:
+ assert False, 'list is empty or name property is wrong'
+
+
+ def test_loadTutorial(self):
+ """
+ Test the opening of a tutorial from the vault by passing it is guid and
+ returning the Tutorial object representation. This test verify that the
+ initial underlying FSM and the new loaded one are equivalent.
+ """
+
+ # call test_installTutorials to be sure that the tuto is now in the Vault
+ self.test_installTutorials()
+ bundler = TutorialBundler(self.tuto_guid)
+ test = bundler.get_tutorial_path(self.tuto_guid)
+ # load tutorial created in the test_installTutorial function
+ vault = Vault()
+ reloaded_tuto = vault.loadTutorial(self.tuto_guid)
+
+ # Compare the two FSMs
+ reloaded_fsm = reloaded_tuto.state_machine
+
+ assert reloaded_fsm._states.get("INIT").name == self.fsm._states.get("INIT").name, \
+ 'FSM underlying dictionary differ from original to reformed one'
+ assert reloaded_fsm._states.get("Second").name == self.fsm._states.get("Second").name, \
+ 'FSM underlying dictionary differ from original to reformed one'
+ assert reloaded_fsm._states.get("INIT").get_action_list()[0].message == \
+ self.fsm._states.get("INIT").get_action_list()[0].message, \
+ 'FSM underlying State underlying Action differ from original to reformed one'
+ assert len(reloaded_fsm.get_action_list()) == 0, "FSM should not have any actions on itself"
+
+ def test_saveTutorial(self):
+ """
+ This test verify the vault function that save a new tutorial (Tutorial object +metadata).
+ """
+
+ # Save the tutorial in the vault
+ vault = Vault()
+ tutorial = Tutorial('test', self.fsm)
+ vault.saveTutorial(tutorial, self.test_metadata_dict)
+
+ # Get the tutorial back
+ reloaded_tuto = vault.loadTutorial(self.save_test_guid)
+
+ # Compare the two FSMs
+ reloaded_fsm = reloaded_tuto.state_machine
+
+ assert reloaded_fsm._states.get("INIT").name == self.fsm._states.get("INIT").name, \
+ 'FSM underlying dictionary differ from original to reformed one'
+ assert reloaded_fsm._states.get("Second").name == self.fsm._states.get("Second").name, \
+ 'FSM underlying dictionary differ from original to reformed one'
+ assert reloaded_fsm._states.get("INIT").get_action_list()[0].message == \
+ self.fsm._states.get("INIT").get_action_list()[0].message, \
+ 'FSM underlying State underlying Action differ from original to reformed one'
+ assert len(reloaded_fsm.get_action_list()) == 0, "FSM should not have any actions on itself"
+
+ # TODO : Compare the initial and reloaded metadata when vault.Query() will accept specifc argument
+ # (so we can specifiy that we want only the metadata for this particular tutorial
+
+
+
+ def tearDown(self):
+ folder = os.path.join(os.getenv("HOME"),".sugar", 'default', 'tutorius', 'data');
+ for file in os.listdir(folder):
+ file_path = os.path.join(folder, file)
+ shutil.rmtree(file_path)
+
+ if (os.path.isdir(os.path.join(os.getenv("HOME"),".sugar", 'default', 'tutorius', 'tmp'))):
+ shutil.rmtree(os.path.join(os.getenv("HOME"),".sugar", 'default', 'tutorius', 'tmp'))
+
+
+class SerializerInterfaceTest(unittest.TestCase):
+ """
+ For completeness' sake.
+ """
+
+ def test_save(self):
+ ser = Serializer()
+
+ try:
+ ser.save_fsm(None)
+ assert False, "save_fsm() should throw an unimplemented error"
+ except:
+ pass
+
+ def test_load(self):
+ ser = Serializer()
+
+ try:
+ ser.load_fsm(str(uuid.uuid1()))
+ assert False, "load_fsm() should throw an unimplemented error"
+ except:
+ pass
+
+class XMLSerializerTest(unittest.TestCase):
+ """
+ Tests the transformation of XML to FSM, then back.
+ """
+
+ def setUp(self):
+
+ os.environ["SUGAR_BUNDLE_PATH"] = os.path.join(sugar.tutorius.vault._get_store_root(), 'test_bundle_path')
+ path = os.path.join(sugar.tutorius.vault._get_store_root(), 'test_bundle_path')
+ if os.path.isdir(path) != True:
+ os.makedirs(path)
+
+ # Create the sample FSM
+ self.fsm = FiniteStateMachine("testingMachine")
+
+ # Add a few states
+ act1 = addon.create('BubbleMessage', message="Hi", position=[300, 450])
+ ev1 = addon.create('GtkWidgetEventFilter', "0.12.31.2.2", "clicked")
+ act2 = addon.create('BubbleMessage', message="Second message", position=[250, 150], tail_pos=[1,2])
+
+ st1 = State("INIT")
+ st1.add_action(act1)
+ st1.add_event_filter(ev1, 'Second')
+
+ st2 = State("Second")
+
+ st2.add_action(act2)
+
+ self.fsm.add_state(st1)
+ self.fsm.add_state(st2)
+
+ self.uuid = uuid1()
+
+ # Flag to set to True if the output can be deleted after execution of
+ # the test
+ self.remove = True
+
+ def tearDown(self):
+ """
+ Removes the created files, if need be.
+ """
+ if self.remove == True:
+ shutil.rmtree(os.path.join(sugar.tutorius.vault._get_store_root(), 'test_bundle_path'))
+
+ folder = os.path.join(os.getenv("HOME"),".sugar", 'default', 'tutorius', 'data');
+ for file in os.listdir(folder):
+ file_path = os.path.join(folder, file)
+ shutil.rmtree(file_path)
+
+ def create_test_metadata(self, ini_file_path, guid):
+ ini_file = open(ini_file_path, 'wt')
+ ini_file.write("[GENERAL_METADATA]\n")
+ ini_file.write('guid=' + str(guid) + '\n')
+ ini_file.write('name=TestTutorial1\n')
+ ini_file.write('version=1\n')
+ ini_file.write('description=This is a test tutorial 1\n')
+ ini_file.write('rating=3.5\n')
+ ini_file.write('category=Test\n')
+ ini_file.write('publish_state=false\n')
+ ini_file.write('[RELATED_ACTIVITIES]\n')
+ ini_file.write('org.laptop.TutoriusActivity = 1\n')
+ ini_file.write('org.laptop.Writus = 1\n')
+ ini_file.close()
+
+ def test_save(self):
+ """
+ Writes an FSM to disk, then compares the file to the expected results.
+ "Remove" boolean argument specify if the test data must be removed or not
+ """
+ xml_ser = XMLSerializer()
+ os.makedirs(os.path.join(sugar.tutorius.vault._get_store_root(), str(self.uuid)))
+ xml_ser.save_fsm(self.fsm, sugar.tutorius.vault.TUTORIAL_FILENAME, os.path.join(sugar.tutorius.vault._get_store_root(), str(self.uuid)))
+ self.create_test_metadata(os.path.join(sugar.tutorius.vault._get_store_root(), str(self.uuid), 'meta.ini'), self.uuid)
+
+
+ def test_save_and_load(self):
+ """
+ Load up the written FSM and compare it with the object representation.
+ """
+ self.test_save()
+ xml_ser = XMLSerializer()
+
+ loaded_fsm = xml_ser.load_fsm(str(self.uuid))
+
+ # Compare the two FSMs
+ assert loaded_fsm._states.get("INIT").name == self.fsm._states.get("INIT").name, \
+ 'FSM underlying dictionary differ from original to reformed one'
+ assert loaded_fsm._states.get("Second").name == self.fsm._states.get("Second").name, \
+ 'FSM underlying dictionary differ from original to reformed one'
+ assert loaded_fsm._states.get("INIT").get_action_list()[0].message == \
+ self.fsm._states.get("INIT").get_action_list()[0].message, \
+ 'FSM underlying State underlying Action differ from original to reformed one'
+ assert len(loaded_fsm.get_action_list()) == 0, "FSM should not have any actions on itself"
+
+ def test_all_actions(self):
+ """
+ Inserts all the known action types in a FSM, then attempt to load it.
+ """
+ st = State("INIT")
+
+ act1 = addon.create('BubbleMessage', "Hi!", position=[10,120], tail_pos=[-12,30])
+ act2 = addon.create('DialogMessage', "Hello again.", position=[120,10])
+ act3 = addon.create('WidgetIdentifyAction')
+ act4 = addon.create('DisableWidgetAction', "0.0.0.1.0.0.0")
+ act5 = addon.create('TypeTextAction', "0.0.0.1.1.1.0.0", "New text")
+ act6 = addon.create('ClickAction', "0.0.1.0.1.1")
+ act7 = addon.create('OnceWrapper', action=act1)
+ act8 = addon.create('ChainAction', actions=[act1, act2, act3, act4])
+ actions = [act1, act2, act3, act4, act5, act6, act7, act8]
+
+ for action in actions:
+ st.add_action(action)
+
+ self.fsm.remove_state("Second")
+ self.fsm.remove_state("INIT")
+ self.fsm.add_state(st)
+
+ xml_ser = XMLSerializer()
+
+ self.test_save()
+
+ reloaded_fsm = xml_ser.load_fsm(str(self.uuid))
+
+ # TODO : Cannot do object equivalence, must check equality of all underlying object
+ # assert self.fsm == reloaded_fsm, "Expected equivalence before saving vs after loading."
+
+ def test_all_filters(self):
+ """
+ Inserts all the known action filters in a FSM, then attempt to load it.
+ """
+ st = State("INIT")
+
+ ev1 = addon.create('TimerEvent', 1000)
+ ev2 = addon.create('GtkWidgetEventFilter', object_id="0.0.1.1.0.0.1", event_name="clicked")
+ ev3 = addon.create('GtkWidgetTypeFilter', "0.0.1.1.1.2.3", text="Typed stuff")
+ ev4 = addon.create('GtkWidgetTypeFilter', "0.0.1.1.1.2.3", strokes="acbd")
+ filters = [ev1, ev2, ev3, ev4]
+
+ for filter in filters:
+ st.add_event_filter(filter, 'Second')
+
+ self.fsm.remove_state("INIT")
+ self.fsm.add_state(st)
+
+ xml_ser = XMLSerializer()
+
+ self.test_save()
+
+ reloaded_fsm = xml_ser.load_fsm(str(self.uuid))
+
+ # TODO : Cannot do object equivalence, must check equality of all underlying object
+ # assert self.fsm == reloaded_fsm, "Expected equivalence before saving vs after loading."
+
+
+class TutorialBundlerTests(unittest.TestCase):
+ """
+ TutorialBundler tests
+
+ This module contains all the tests for the storage mecanisms for tutorials
+ This mean testing saving and loading tutorial, .ini file management and
+ adding ressources to tutorial
+ """
+
+ def setUp(self):
+ os.environ["SUGAR_BUNDLE_PATH"] = os.path.join(sugar.tutorius.vault._get_store_root(), 'test_bundle_path')
+ path = os.path.join(sugar.tutorius.vault._get_store_root(), 'test_bundle_path')
+ if os.path.isdir(path) != True:
+ os.makedirs(path)
+
+ #generate a test GUID
+ self.test_guid = uuid1()
+ self.guid_path = os.path.join(sugar.tutorius.vault._get_store_root(),str(self.test_guid))
+ os.mkdir(self.guid_path)
+
+ self.ini_file = os.path.join(self.guid_path, "meta.ini")
+
+ ini_file = open(self.ini_file, 'wt')
+ ini_file.write('[GENERAL_METADATA]\n')
+ ini_file.write('guid=' + str(self.test_guid) + '\n')
+ ini_file.write('name=TestTutorial\n')
+ ini_file.write('version=1\n')
+ ini_file.write('description=This is a test tutorial\n')
+ ini_file.write('rating=3.5\n')
+ ini_file.write('category=Test\n')
+ ini_file.write('publish_state=false\n')
+ ini_file.write('[RELATED_ACTIVITES]\n')
+ ini_file.write('org.laptop.TutoriusActivity = 1\n')
+ ini_file.write('org.laptop.Writus = 1\n')
+ ini_file.close()
+
+ def tearDown(self):
+ os.remove(self.ini_file)
+ os.rmdir(self.guid_path)
+
+ folder = os.path.join(os.getenv("HOME"),".sugar", 'default', 'tutorius', 'data');
+ for file in os.listdir(folder):
+ file_path = os.path.join(folder, file)
+ shutil.rmtree(file_path)
+
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/tutorius/core.py b/tutorius/core.py
index 0f33052..d08c136 100644
--- a/tutorius/core.py
+++ b/tutorius/core.py
@@ -271,7 +271,7 @@ class State(object):
if len(self._actions) != len(otherState._actions):
return False
- if len(self._event_filters) != len(otherState._event_filters):
+ if len(self._transitions) != len(otherState._transitions):
return False
for act in self._actions:
@@ -287,12 +287,14 @@ class State(object):
return False
# Do they have the same event filters?
- for event in self._event_filters:
+ for event in self._transitions:
+ state_name = self._transitions[event]
found = False
# For every event filter in the other state, try to match it with
# the current filter. We just need to find one with the right
# properties and values.
- for otherEvent in otherState._event_filters:
+ for otherEvent in otherState._transitions:
+ other_state_name = otherState._transitions[otherEvent]
if event == otherEvent:
found = True
break
@@ -515,8 +517,9 @@ class FiniteStateMachine(State):
#TODO : Move this code inside the State itself - we're breaking
# encap :P
- for event in st._transitions:
- if st._transitions[event] == state_name:
+ for event in st._transitions.keys():
+ state = st._transitions[event]
+ if state == state_name:
del st._transitions[event]
# Remove the state from the dictionary
@@ -535,8 +538,9 @@ class FiniteStateMachine(State):
next_states = set()
- for event, state in state._transitions:
- next_states.add(state)
+ for event in state._transitions.keys():
+ state_name_in_dict = state._transitions[event]
+ next_states.add(state_name_in_dict)
return tuple(next_states)
@@ -558,7 +562,7 @@ class FiniteStateMachine(State):
states = []
# Walk through the list of states
for st in self._states.itervalues():
- for event, state in st._transitions:
+ for event, state in st._transitions.items():
if state == state_name:
states.append(state)
continue
diff --git a/tutorius/bundler.py b/tutorius/vault.py
index c9558b1..9215e8d 100644
--- a/tutorius/bundler.py
+++ b/tutorius/vault.py
@@ -22,32 +22,40 @@ This module contains all the data handling class of Tutorius
import logging
import os
+import shutil
+import tempfile
import uuid
import xml.dom.minidom
from xml.dom import NotFoundErr
+import zipfile
from sugar.tutorius import addon
from sugar.tutorius.core import Tutorial, State, FiniteStateMachine
-from sugar.tutorius.filters import *
-from sugar.tutorius.actions import *
from ConfigParser import SafeConfigParser
+logger = logging.getLogger("tutorius")
+
# this is where user installed/generated tutorials will go
def _get_store_root():
profile_name = os.getenv("SUGAR_PROFILE") or "default"
return os.path.join(os.getenv("HOME"),
".sugar",profile_name,"tutorius","data")
# this is where activity bundled tutorials should be, under the activity bundle
-def _get_bundle_root(base_path=None):
- base_path = base_path or os.getenv("SUGAR_BUNDLE_PATH")
- if base_path:
+def _get_bundle_root():
+ """
+ Return the path of the bundled activity, or None if not applicable.
+ """
+ if os.getenv("SUGAR_BUNDLE_PATH") != None:
return os.path.join(os.getenv("SUGAR_BUNDLE_PATH"),"data","tutorius","data")
+ else:
+ return None
INI_ACTIVITY_SECTION = "RELATED_ACTIVITIES"
INI_METADATA_SECTION = "GENERAL_METADATA"
-INI_GUID_PROPERTY = "GUID"
-INI_NAME_PROPERTY = "NAME"
-INI_XML_FSM_PROPERTY = "FSM_FILENAME"
+INI_GUID_PROPERTY = "guid"
+INI_NAME_PROPERTY = "name"
+INI_XML_FSM_PROPERTY = "fsm_filename"
+INI_VERSION_PROPERTY = 'version'
INI_FILENAME = "meta.ini"
TUTORIAL_FILENAME = "tutorial.xml"
NODE_COMPONENT = "Component"
@@ -55,65 +63,263 @@ NODE_SUBCOMPONENT = "property"
NODE_SUBCOMPONENTLIST = "listproperty"
NEXT_STATE_ATTR = "next_state"
-class TutorialStore(object):
-
- def list_available_tutorials(self, activity_name, activity_vers):
+class Vault(object):
+
+ ## Vault internal functions :
+ @staticmethod
+ def list_available_tutorials(activity_name = None, activity_vers = 0):
"""
Generate the list of all tutorials present on disk for a
given activity.
+ @param activity_name the name of the activity associated with this tutorial. None means ALL activities
+ @param activity_vers the version number of the activity to find tutorail for. 0 means find for ANY version. Ifactivity_ame is None, version number is not used
@returns a map of tutorial {names : GUID}.
"""
# check both under the activity data and user installed folders
- paths = [p for p in [_get_store_root(), _get_bundle_root()] if p ]
+ if _get_bundle_root() != None:
+ paths = [_get_store_root(), _get_bundle_root()]
+ else:
+ paths = [_get_store_root()]
tutoGuidName = {}
for repository in paths:
# (our) convention dictates that tutorial folders are named
- # with their GUID (for unicity) but this is not enforced.
+ # with their GUID (for unicity)
try:
for tuto in os.listdir(repository):
parser = SafeConfigParser()
- parser.read(os.path.join(repository, tuto, INI_FILENAME))
- guid = parser.get(INI_METADATA_SECTION, INI_GUID_PROPERTY)
- name = parser.get(INI_METADATA_SECTION, INI_NAME_PROPERTY)
- activities = parser.options(INI_ACTIVITY_SECTION)
- # enforce matching activity name AND version, as UI changes
- # break tutorials. We may lower this requirement when the
- # UAM gets less dependent on the widget order.
- # Also note property names are always stored lowercase.
- if activity_name.lower() in activities:
- version = parser.get(INI_ACTIVITY_SECTION, activity_name)
- if activity_vers == version:
+ file = parser.read(os.path.join(repository, tuto, INI_FILENAME))
+ if file != []:
+ # If parser can read at least section
+ guid = parser.get(INI_METADATA_SECTION, INI_GUID_PROPERTY)
+ name = parser.get(INI_METADATA_SECTION, INI_NAME_PROPERTY)
+ activities = parser.options(INI_ACTIVITY_SECTION)
+ # enforce matching activity name AND version, as UI changes
+ # break tutorials. We may lower this requirement when the
+ # UAM gets less dependent on the widget order.
+ # Also note property names are always stored lowercase.
+ if (activity_name != None) and (activity_name.lower() in activities):
+ version = parser.get(INI_ACTIVITY_SECTION, activity_name)
+ if (activity_vers == version) or (activity_vers == 0):
+ tutoGuidName[guid] = name
+ elif (activity_name == None):
tutoGuidName[guid] = name
except OSError:
# the repository may not exist. Continue scanning
pass
return tutoGuidName
-
- def load_tutorial(self, Guid, bundle_path=None):
+
+ ## Vault interface functions :
+ @staticmethod
+ def installTutorials(path, zip_file_name, forceinstall=False):
+ """
+ Extract the tutorial files in the ZIPPED tutorial archive at the
+ specified path and add them inside the vault. This should remove any previous
+ version of this tutorial, if there's any. On the opposite, if we are
+ trying to install an earlier version, the function will return 1 if
+ forceInstall is not set to true.
+
+ @params path The path where the zipped tutorial archive is present
+ @params forceinstall A flag that indicate if we need to force overwrite
+ of a tutorial even if is version number is lower than the existing one.
+
+ @returns 0 if it worked, 1 if the user needs to confirm the installation
+ and 2 to mean an error happened
+ """
+ # TODO : Check with architecture team for exception vs error returns
+
+ # test if the file is a valid pkzip file
+ if zipfile.is_zipfile(os.path.join(path, zip_file_name)) != True:
+ assert False, "Error : The given file is not a valid PKZip file"
+
+ # unpack the zip archive
+ zfile = zipfile.ZipFile(os.path.join(path, zip_file_name), "r" )
+
+ temp_path = tempfile.mkdtemp(dir=_get_store_root())
+ zfile.extractall(temp_path)
+
+ # get the tutorial file
+ ini_file_path = os.path.join(temp_path, INI_FILENAME)
+ ini_file = SafeConfigParser()
+ ini_file.read(ini_file_path)
+
+ # get the tutorial guid
+ guid = ini_file.get(INI_METADATA_SECTION, INI_GUID_PROPERTY)
+
+ # Check if tutorial already exist
+ tutorial_path = os.path.join(_get_store_root(), guid)
+ if os.path.isdir(tutorial_path) == False:
+ # Copy the tutorial in the Vault
+ shutil.copytree(temp_path, tutorial_path)
+
+ else:
+ # Check the version of the existing tutorial
+ existing_version = ini_file.get(INI_METADATA_SECTION, INI_VERSION_PROPERTY)
+ # Check the version of the new tutorial
+ new_ini_file = SafeConfigParser()
+ new_ini_file.read(os.path.join(tutorial_path, INI_FILENAME))
+ new_version = new_ini_file.get(INI_METADATA_SECTION, INI_VERSION_PROPERTY)
+
+ if new_version < existing_version and forceinstall == False:
+ # Version of new tutorial is older and forceinstall is false, return exception
+ return 1
+ else :
+ # New tutorial is newer or forceinstall flag is set, can overwrite the existing tutorial
+ shutil.rmtree(tutorial_path)
+ shutil.copytree(temp_path, tutorial_path)
+
+ # Remove temp data
+ shutil.rmtree(temp_path)
+
+ return 0
+
+ @staticmethod
+ def query(keyword=[], relatedActivityNames=[], category=[]):
+ """
+ Returns the list of tutorials that corresponds to the specified parameters.
+
+ @returns a list of Tutorial meta-data (TutorialID, Description,
+ Rating, Category, PublishState, etc...)
+ TODO : Search for tuto caracterised by the entry : OR between [], and between each
+
+ The returned dictionnary is of this format : key = property name, value = property value
+ The dictionnary also contain one dictionnary element whose key is the string 'activities'
+ and whose value is another dictionnary of this form : key = related activity name,
+ value = related activity version number
"""
- Rebuilds a tutorial object from it's serialized state.
- Common storing paths will be scanned.
- @param Guid the generic identifier of the tutorial
- @returns a Tutorial object containing an FSM
+ # Temp solution for returning all tutorials metadata
+
+ tutorial_list = []
+ tuto_guid_list = []
+ ini_file = SafeConfigParser()
+ if keyword == [] and relatedActivityNames == [] and category == []:
+ # get all tutorials tuples (name:guid) for all activities and version
+ tuto_dict = Vault.list_available_tutorials()
+ for id in tuto_dict.keys():
+ tuto_guid_list.append(id)
+
+ # Find .ini metadata files with the guid list
+
+ # Get the guid from the tuto tuples
+ for guid in tuto_guid_list:
+ # Create a dictionnary containing the metadata and also
+ # another dictionnary containing the tutorial Related Acttivities,
+ # and add it to a list
+
+ # Create a TutorialBundler object from the guid
+ bundler = TutorialBundler(guid)
+ # Find the .ini file path for this guid
+ ini_file_path = bundler.get_tutorial_path(guid)
+ # Read the .ini file
+ ini_file.read(os.path.join(ini_file_path, 'meta.ini'))
+
+ metadata_dictionnary = {}
+ related_act_dictionnary = {}
+ metadata_list = ini_file.options(INI_METADATA_SECTION)
+ for metadata_name in metadata_list:
+ # Create a dictionnary of tutorial metadata
+ metadata_dictionnary[metadata_name] = ini_file.get(INI_METADATA_SECTION, metadata_name)
+ # Get Related Activities data from.ini files
+ related_act_list = ini_file.options(INI_ACTIVITY_SECTION)
+ for related_act in related_act_list:
+ # For related activites, the format is : key = activity name, value = activity version
+ related_act_dictionnary[related_act] = ini_file.get(INI_ACTIVITY_SECTION, related_act)
+
+ # Add Related Activities dictionnary to metadata dictionnary
+ metadata_dictionnary['activities'] = related_act_dictionnary
+
+ # Add this dictionnary to tutorial list
+ tutorial_list.append(metadata_dictionnary)
+
+ # Return tutorial list
+ return tutorial_list
+
+ @staticmethod
+ def loadTutorial(Guid):
+ """
+ Creates an executable version of a tutorial from its saved representation.
+ @returns an executable representation of a tutorial
"""
- bundler = TutorialBundler(Guid, bundle_path=bundle_path)
- bundler_path = bundler.get_tutorial_path()
+
+ bundle = TutorialBundler(Guid)
+ bundle_path = bundle.get_tutorial_path(Guid)
config = SafeConfigParser()
- config.read(os.path.join(bundler_path, INI_FILENAME))
+ config.read(os.path.join(bundle_path, INI_FILENAME))
serializer = XMLSerializer()
name = config.get(INI_METADATA_SECTION, INI_NAME_PROPERTY)
- fsm = serializer.load_fsm(Guid, bundler.Path)
+ fsm = serializer.load_fsm(Guid, bundle_path)
tuto = Tutorial(name, fsm)
return tuto
+
+ @staticmethod
+ def saveTutorial(tutorial, metadata_dict):
+ """
+ Creates a persistent version of a tutorial in the Vault.
+ @returns true if the tutorial was saved correctly
+ """
+
+ # Get the tutorial guid from metadata dictionnary
+ guid = metadata_dict[INI_GUID_PROPERTY]
+
+ # Check if tutorial already exist
+ tutorial_path = os.path.join(_get_store_root(), guid)
+ if os.path.isdir(tutorial_path) == False:
+
+ # Serialize the tutorial and write it to disk
+ xml_ser = XMLSerializer()
+ os.makedirs(tutorial_path)
+ xml_ser.save_fsm(tutorial.state_machine, TUTORIAL_FILENAME, tutorial_path)
+
+ # Create the metadata file
+ ini_file_path = os.path.join(tutorial_path, "meta.ini")
+ parser = SafeConfigParser()
+ parser.add_section(INI_METADATA_SECTION)
+ for key, value in metadata_dict.items():
+ if key != 'activities':
+ parser.set(INI_METADATA_SECTION, key, value)
+ else:
+ related_activities_dict = value
+ parser.add_section(INI_ACTIVITY_SECTION)
+ for related_key, related_value in related_activities_dict.items():
+ parser.set(INI_ACTIVITY_SECTION, related_key, related_value)
+ # Write the file to disk
+ with open(ini_file_path, 'wb') as configfile:
+ parser.write(configfile)
+
+ else:
+ # Error, tutorial already exist
+ return False
+
+ # TODO : wait for Ben input on how to unpublish tuto before coding this function
+ # For now, no unpublishing will occur.
+
+
+ @staticmethod
+ def deleteTutorial(Tutorial):
+ """
+ Removes the tutorial from the Vault. It will unpublish the tutorial if need be,
+ and it will also wipe it from the persistent storage.
+ @returns true is the tutorial was deleted from the Vault
+ """
+ bundle = TutorialBundler(Guid)
+ bundle_path = bundle.get_tutorial_path(Guid)
+
+ # TODO : Need also to unpublish tutorial, need to interact with webservice module
+
+ shutil.rmtree(bundle_path)
+ if os.path.isdir(bundle_path) == False:
+ return True
+ else:
+ return False
+
class Serializer(object):
"""
@@ -129,13 +335,13 @@ class Serializer(object):
exception occur. If no GUID is provided, FSM is written in a new file
in the store root.
"""
- return NotImplementedError()
+ raise NotImplementedError()
def load_fsm(self):
"""
Load fsm from disk.
"""
- return NotImplementedError()
+ raise NotImplementedError()
class XMLSerializer(Serializer):
"""
@@ -271,7 +477,7 @@ class XMLSerializer(Serializer):
def _create_event_filters_node(self, event_filters, doc):
"""
- Create and return a xml Node from a event filters.
+ Create and return a xml Node from an event filters.
"""
eventFiltersList = doc.createElement("EventFiltersList")
for event, state in event_filters:
@@ -300,60 +506,7 @@ class XMLSerializer(Serializer):
file_object = open(os.path.join(path, xml_filename), "w")
file_object.write(doc.toprettyxml())
file_object.close()
-
-
- def _find_tutorial_dir_with_guid(self, guid):
- """
- Finds the tutorial with the associated GUID. If it is found, return
- the path to the tutorial's directory. If it doesn't exist, raise an
- IOError.
-
- A note : if there are two tutorials with this GUID in the folders,
- they will both be inspected and the one with the highest version
- number will be returned. If they have the same version number, the one
- from the global store will be returned.
-
- @param guid The GUID of the tutorial that is to be loaded.
- """
- # Attempt to find the tutorial's directory in the global directory
- global_dir = os.path.join(_get_store_root(), guid)
- # Then in the activty's bundle path
- activity_dir = os.path.join(_get_bundle_root(), guid)
-
- # If they both exist
- if os.path.isdir(global_dir) and os.path.isdir(activity_dir):
- # Inspect both metadata files
- global_meta = os.path.join(global_dir, "meta.ini")
- activity_meta = os.path.join(activity_dir, "meta.ini")
-
- # Open both config files
- global_parser = SafeConfigParser()
- global_parser.read(global_meta)
-
- activity_parser = SafeConfigParser()
- activity_parser.read(activity_meta)
-
- # Get the version number for each tutorial
- global_version = global_parser.get(INI_METADATA_SECTION, "version")
- activity_version = activity_parser.get(INI_METADATA_SECTION, "version")
-
- # If the global version is higher or equal, we'll take it
- if global_version >= activity_version:
- return global_dir
- else:
- return activity_dir
-
- # Do we just have the global directory?
- if os.path.isdir(global_dir):
- return global_dir
-
- # Or just the activity's bundle directory?
- if os.path.isdir(activity_dir):
- return activity_dir
-
- # Error : none of these directories contain the tutorial
- raise IOError(2, "Neither the global nor the bundle directory contained the tutorial with GUID %s"%guid)
-
+
def _get_direct_descendants_by_tag_name(self, node, name):
"""
Searches in the list of direct descendants of a node to find all the node
@@ -375,14 +528,15 @@ class XMLSerializer(Serializer):
return_list.append(childNode)
return return_list
- def _load_xml_properties(self, properties_elem):
- """
- Changes a list of properties into fully instanciated properties.
-
- @param properties_elem An XML element reprensenting a list of
- properties
- """
- return []
+
+## def _load_xml_properties(self, properties_elem):
+## """
+## Changes a list of properties into fully instanciated properties.
+##
+## @param properties_elem An XML element reprensenting a list of
+## properties
+## """
+## return []
def _load_xml_event_filters(self, filters_elem):
"""
@@ -510,7 +664,7 @@ class XMLSerializer(Serializer):
return reformed_state_list
- def _load_xml_fsm(self, fsm_elem):
+ def load_xml_fsm(self, fsm_elem):
"""
Takes in an XML element representing an FSM and returns the fully
crafted FSM.
@@ -551,7 +705,8 @@ class XMLSerializer(Serializer):
Load fsm from xml file whose .ini file guid match argument guid.
"""
# Fetch the directory (if any)
- tutorial_dir = path or self._find_tutorial_dir_with_guid(guid)
+ bundler = TutorialBundler(guid)
+ tutorial_dir = bundler.get_tutorial_path(guid)
# Open the XML file
tutorial_file = os.path.join(tutorial_dir, TUTORIAL_FILENAME)
@@ -560,7 +715,7 @@ class XMLSerializer(Serializer):
fsm_elem = xml_dom.getElementsByTagName("FSM")[0]
- return self._load_xml_fsm(fsm_elem)
+ return self.load_xml_fsm(fsm_elem)
class TutorialBundler(object):
@@ -582,20 +737,18 @@ class TutorialBundler(object):
#Look for the file in the path if a uid is supplied
if generated_guid:
#General store
- store_path = os.path.join(_get_store_root(), generated_guid, INI_FILENAME)
+ store_path = os.path.join(_get_store_root(), str(generated_guid), INI_FILENAME)
if os.path.isfile(store_path):
self.Path = os.path.dirname(store_path)
- else:
+ elif _get_bundle_root() != None:
#Bundle store
- base_bundle_path = _get_bundle_root(bundle_path)
- if base_bundle_path:
- bundle_path = os.path.join(base_bundle_path, generated_guid, INI_FILENAME)
- if os.path.isfile(bundle_path):
- self.Path = os.path.dirname(bundle_path)
- else:
- raise IOError(2,"Unable to locate metadata file for guid '%s'" % generated_guid)
+ bundle_path = os.path.join(_get_bundle_root(), str(generated_guid), INI_FILENAME)
+ if os.path.isfile(bundle_path):
+ self.Path = os.path.dirname(bundle_path)
else:
raise IOError(2,"Unable to locate metadata file for guid '%s'" % generated_guid)
+ else:
+ raise IOError(2,"Unable to locate metadata file for guid '%s'" % generated_guid)
else:
#Create the folder, any failure will go through to the caller for now
@@ -615,67 +768,75 @@ class TutorialBundler(object):
cfg.set(INI_METADATA_SECTION, INI_NAME_PROPERTY, tutorial.name)
cfg.set(INI_METADATA_SECTION, INI_XML_FSM_PROPERTY, TUTORIAL_FILENAME)
cfg.add_section(INI_ACTIVITY_SECTION)
- cfg.set(INI_ACTIVITY_SECTION, os.environ['SUGAR_BUNDLE_NAME'],
+ if os.environ['SUGAR_BUNDLE_NAME'] != None and os.environ['SUGAR_BUNDLE_VERSION'] != None:
+ cfg.set(INI_ACTIVITY_SECTION, os.environ['SUGAR_BUNDLE_NAME'],
os.environ['SUGAR_BUNDLE_VERSION'])
+ else:
+ cfg.set(INI_ACTIVITY_SECTION, 'not_an_activity', '0')
#Write the ini file
cfg.write( file( os.path.join(self.Path, INI_FILENAME), 'w' ) )
- def get_tutorial_path(self):
- """
- Return the path of the .ini file associated with the guiven guid set in
- the Guid property of the Tutorial_Bundler. If the guid is present in
- more than one path, the store_root is given priority.
+
+ @staticmethod
+ def get_tutorial_path(guid):
"""
+ Finds the tutorial with the associated GUID. If it is found, return
+ the path to the tutorial's directory. If it doesn't exist, raise an
+ IOError.
- store_root = _get_store_root()
- bundle_root = _get_bundle_root()
+ A note : if there are two tutorials with this GUID in the folders,
+ they will both be inspected and the one with the highest version
+ number will be returned. If they have the same version number, the one
+ from the global store will be returned.
- config = SafeConfigParser()
- path = None
+ @param guid The GUID of the tutorial that is to be loaded.
+ """
+ # Attempt to find the tutorial's directory in the global directory
+ global_dir = os.path.join(_get_store_root(),str(guid))
+ # Then in the activty's bundle path
+ if _get_bundle_root() != None:
+ activity_dir = os.path.join(_get_bundle_root(), str(guid))
+ else:
+ activity_dir = ''
- logging.debug("************ Path of store_root folder of activity : " \
- + store_root)
-
- # iterate in each GUID subfolder
- for dir in os.listdir(store_root):
+ # If they both exist
+ if os.path.isdir(global_dir) and os.path.isdir(activity_dir):
+ # Inspect both metadata files
+ global_meta = os.path.join(global_dir, "meta.ini")
+ activity_meta = os.path.join(activity_dir, "meta.ini")
- # iterate for each .ini file in the store_root folder
-
- for file_name in os.listdir(os.path.join(store_root, dir)):
- if file_name.endswith(".ini"):
- logging.debug("******************* Found .ini file : " \
- + file_name)
- config.read(os.path.join(store_root, dir, file_name))
- if config.get(INI_METADATA_SECTION, INI_GUID_PROPERTY) == self.Guid:
- xml_filename = config.get(INI_METADATA_SECTION,
- INI_XML_FSM_PROPERTY)
-
- path = os.path.join(store_root, dir)
- return path
+ # Open both config files
+ global_parser = SafeConfigParser()
+ global_parser.read(global_meta)
- logging.debug("************ Path of bundle_root folder of activity : " \
- + bundle_root)
+ activity_parser = SafeConfigParser()
+ activity_parser.read(activity_meta)
-
- # iterate in each GUID subfolder
- for dir in os.listdir(bundle_root):
+ # Get the version number for each tutorial
+ global_version = global_parser.get(INI_METADATA_SECTION, "version")
+ activity_version = activity_parser.get(INI_METADATA_SECTION, "version")
- # iterate for each .ini file in the bundle_root folder
- for file_name in os.listdir(os.path.join(bundle_root, dir)):
- if file_name.endswith(".ini"):
- logging.debug("******************* Found .ini file : " \
- + file_name)
- config.read(os.path.join(bundle_root, dir, file_name))
- if config.get(INI_METADATA_SECTION, INI_GUID_PROPERTY) == self.Guid:
- path = os.path.join(bundle_root, self.Guid)
- return path
-
- if path is None:
- logging.debug("**************** Error : GUID not found")
- raise KeyError
-
- def write_fsm(self, fsm):
+ # If the global version is higher or equal, we'll take it
+ if global_version >= activity_version:
+ return global_dir
+ else:
+ return activity_dir
+
+ # Do we just have the global directory?
+ if os.path.isdir(global_dir):
+ return global_dir
+
+ # Or just the activity's bundle directory?
+ if os.path.isdir(activity_dir):
+ return activity_dir
+
+ # Error : none of these directories contain the tutorial
+ raise IOError(2, "Neither the global nor the bundle directory contained the tutorial with GUID %s"%guid)
+
+
+ @staticmethod
+ def write_fsm(fsm):
"""
Save fsm to disk. If a GUID parameter is provided, the existing GUID is
@@ -692,8 +853,8 @@ class TutorialBundler(object):
xml_filename = config.get(INI_METADATA_SECTION, INI_XML_FSM_PROPERTY)
serializer.save_fsm(fsm, xml_filename, self.Path)
-
- def add_resources(self, typename, file):
+ @staticmethod
+ def add_resources(typename, file):
"""
Add ressources to metadata.
"""