...
 
......@@ -3,10 +3,12 @@
// enables non-standard api connection (the only one supported at the moment)
#define EPFL
#define USERNAME "afontain"
#mesondefine CACHE_ROOT
// how long we consider the cache «up to date»: for index files,
// the default is one hour; but for regular file, it is 10 years.
#define CACHE_TIME_HTML 3600
#define CACHE_TIME_FILES (10*365*24*3600)
// how verbose the http requests are:
// 0 – none
......@@ -21,3 +23,5 @@
#include "epfl.h"
#endif // EPFL
......@@ -18,20 +18,23 @@
#pragma once
#define EPFL_MAIN_PAGE "https://moodle.epfl.ch/my/"
#define EPFL_TEQUILA_LOGIN "https://moodle.epfl.ch/auth/tequila/index.php"
//#define EPFL_MOODLE_ROOT "https://moodlearchive.epfl.ch/2018-2019/" // doesn't quite work: cannot get course list
#define EPFL_MOODLE_ROOT "https://moodle.epfl.ch"
#define EPFL_MAIN_PAGE EPFL_MOODLE_ROOT"/my/"
#define EPFL_TEQUILA_LOGIN EPFL_MOODLE_ROOT"/auth/tequila/index.php"
#define EPFL_TEQUILA_LOGIN_ACTION "https://tequila.epfl.ch/cgi-bin/tequila/login"
#define EPFL_ENROL_LINK "https://moodle.epfl.ch/enrol/"
#define EPFL_ENROL_LINK EPFL_MOODLE_ROOT"/enrol/"
#define EPFL_COURSE_LINK "https://moodle.epfl.ch/course/view.php?id="
#define EPFL_COURSE_LINK EPFL_MOODLE_ROOT"/course/view.php?id="
#define EPFL_ASSIGNMENT_LINK "https://moodle.epfl.ch/mod/assign/view.php?id="
#define EPFL_FILE_LINK "https://moodle.epfl.ch/mod/resource/view.php?id="
#define EPFL_FOLDERFILE_LINK "https://moodle.epfl.ch/pluginfile.php/%u/mod_folder/content/0/"
#define EPFL_FOLDER_LINK "https://moodle.epfl.ch/mod/folder/view.php?id="
#define EPFL_FORUM_LINK "https://moodle.epfl.ch/mod/forum/view.php?id="
#define EPFL_PAGE_LINK "https://moodle.epfl.ch/mod/page/view.php?id="
#define EPFL_QUIZ_LINK "https://moodle.epfl.ch/mod/quiz/view.php?id="
#define EPFL_URL_LINK "https://moodle.epfl.ch/mod/url/view.php?id="
#define EPFL_ASSIGNMENT_LINK EPFL_MOODLE_ROOT"/mod/assign/view.php?id="
#define EPFL_FILE_LINK EPFL_MOODLE_ROOT"/mod/resource/view.php?id="
#define EPFL_FOLDERFILE_LINK EPFL_MOODLE_ROOT"/pluginfile.php/%u/mod_folder/content/0/"
#define EPFL_FOLDER_LINK EPFL_MOODLE_ROOT"/mod/folder/view.php?id="
#define EPFL_FORUM_LINK EPFL_MOODLE_ROOT"/mod/forum/view.php?id="
#define EPFL_PAGE_LINK EPFL_MOODLE_ROOT"/mod/page/view.php?id="
#define EPFL_QUIZ_LINK EPFL_MOODLE_ROOT"/mod/quiz/view.php?id="
#define EPFL_URL_LINK EPFL_MOODLE_ROOT"/mod/url/view.php?id="
......@@ -84,6 +84,15 @@ update_course_content (GtkWidget *self_widget, gpointer unused)
}
}
void
download_course_content (GtkWidget *self_widget, gpointer bool_download_selected_elements)
{
GuiCourse *self = HDY_GUI_COURSE (self_widget);
gtk_container_foreach (GTK_CONTAINER (self->box_week),
&download_week_content,
bool_download_selected_elements);
}
static void
course_content_obtained_cb (gpointer ptr, gpointer data)
{
......
......@@ -34,5 +34,8 @@ GuiCourse* gui_course_new (struct Course course_info);
void update_course_content (GtkWidget *self_widget, gpointer unused);
void
download_course_content (GtkWidget *self_widget,
gpointer bool_download_selected_elements);
G_END_DECLS
......@@ -25,6 +25,17 @@
#define HANDY_USE_UNSTABLE_API
#include <handy.h>
struct FolderFileInfo {
guint folder_id;
gchar *filename;
};
union ResourceInfo {
struct FolderFileInfo folderfileinfo;
const gchar *url;
guint id;
};
struct _GuiResource
{
HdyActionRow parent_instance;
......@@ -150,6 +161,39 @@ activated_cb (GtkListBox* unused, GuiResource *self)
gtk_list_box_row_set_activatable (GTK_LIST_BOX_ROW (self), TRUE);
}
static void
download_folder_content (gpointer self_ptr, gpointer folder_content_ptr) //REWRITEME
{
struct FolderContent *content = folder_content_ptr;
for (guint i=0; i<content->resource_count; i++) {
GuiResource *resource = gui_resource_new (content->resources[i]);
resource_download_content (GTK_WIDGET (resource), NULL);
}
open_folder_cb (self_ptr, folder_content_ptr); // since we know the folder content, display it
}
void
resource_download_content (GtkWidget *self_widget, gpointer unused)
{
GuiResource *self = HDY_GUI_RESOURCE (self_widget);
switch (self->type) {
case RESOURCE_FILE:
moodle_provider_download_file_async (self->id, NULL, self);
break;
case RESOURCE_FOLDERFILE:
if (self->filename && self->id)
moodle_provider_download_file_by_filename_async (self->id, self->filename, NULL, self);
else
g_warning ("That's odd: missing filename and/or id :-/\n");
break;
case RESOURCE_FOLDER:
moodle_provider_get_folder_content_async (download_folder_content, self->id, self);
break;
default:
break;
}
}
static void
gui_resource_set_property (GObject *object,
guint property_id,
......
......@@ -34,4 +34,6 @@ GuiResource *gui_resource_new (struct Resource file_info);
void activated_cb (GtkListBox* unused, GuiResource *self);
void resource_download_content (GtkWidget *self_widget, gpointer unused);
G_END_DECLS
......@@ -63,6 +63,22 @@ update_files (GuiWeek* self)
}
}
void
download_week_content (GtkWidget *self_widget, gpointer bool_download_selected_elements)
{
GuiWeek *self = HDY_GUI_WEEK (self_widget);
if (GPOINTER_TO_INT (bool_download_selected_elements)) {
GList *resources = gtk_list_box_get_selected_rows (self->week_content);
g_list_foreach (resources,
(GFunc)&resource_download_content,
NULL);
} else {
gtk_container_foreach (GTK_CONTAINER (self->week_content),
&resource_download_content,
NULL);
}
}
static void
gui_week_set_property (GObject *object,
guint property_id,
......
......@@ -31,4 +31,8 @@ G_DECLARE_FINAL_TYPE (GuiWeek, gui_week, HDY, GUI_WEEK, GtkBox)
GuiWeek* gui_week_new (struct Week week_info);
void
download_week_content (GtkWidget *self_widget,
gpointer bool_download_selected_elements);
G_END_DECLS
......@@ -37,7 +37,8 @@ struct _GuiWindow
HdyLeaflet *header_box;
HdyLeaflet *content_box;
GtkButton *back;
GtkMenuButton *menu;
GtkMenuButton *primary_menu;
GtkMenuButton *secondary_menu;
//GtkToggleButton *search_button;
GtkStackSidebar *sidebar;
GtkStack *stack;
......@@ -91,6 +92,17 @@ activate_ask_pass (GSimpleAction *simple,
gtk_widget_show (GTK_WIDGET (self->dialog));
}
static void
activate_download_current_course (GSimpleAction *simple,
GVariant *parameter,
gpointer ptr)
{
// get current course
GuiWindow *self = ptr;
download_course_content (gtk_stack_get_visible_child (self->stack),
GINT_TO_POINTER (FALSE));
}
/*
static void
activate_print_string (GSimpleAction *simple,
......@@ -228,6 +240,9 @@ update_courses (GuiWindow *self)
self->courses[i].name);
}
// make the secondary menu visible
gtk_widget_show (GTK_WIDGET (self->secondary_menu));
// ellipsize the labels
GtkWidget *scrolled_win = gtk_bin_get_child (GTK_BIN (self->sidebar));
GtkWidget *viewport = gtk_bin_get_child (GTK_BIN (scrolled_win));
......@@ -309,7 +324,8 @@ gui_window_class_init (GuiWindowClass *class)
gtk_widget_class_bind_template_child (widget_class, GuiWindow, header_box);
gtk_widget_class_bind_template_child (widget_class, GuiWindow, content_box);
gtk_widget_class_bind_template_child (widget_class, GuiWindow, back);
gtk_widget_class_bind_template_child (widget_class, GuiWindow, menu);
gtk_widget_class_bind_template_child (widget_class, GuiWindow, primary_menu);
gtk_widget_class_bind_template_child (widget_class, GuiWindow, secondary_menu);
//gtk_widget_class_bind_template_child (widget_class, GuiWindow, search_button);
gtk_widget_class_bind_template_child (widget_class, GuiWindow, sidebar);
gtk_widget_class_bind_template_child (widget_class, GuiWindow, stack);
......@@ -355,7 +371,8 @@ gui_window_init (GuiWindow *self)
initialize_moodle_provider (self);
const GActionEntry entries[] = {
{ "ask-pass", activate_ask_pass },
{ "ask-pass", activate_ask_pass },
{ "download-current-course", activate_download_current_course },
//{ "print-string", activate_print_string, "s" }
};
g_action_map_add_action_entries (G_ACTION_MAP (self),
......
......@@ -118,6 +118,27 @@ get_index_filename (void)
}
static gboolean
cache_is_usable_for (const gchar *filename)
{
g_autoptr (GFile) file = g_file_new_for_uri (filename);
g_autoptr (GFileInfo) fileinfo = g_file_query_info (file,
G_FILE_ATTRIBUTE_TIME_MODIFIED,
G_FILE_QUERY_INFO_NONE,
NULL,
NULL);
if (fileinfo) {
guint64 mtime = g_file_info_get_attribute_uint64 (fileinfo, G_FILE_ATTRIBUTE_TIME_MODIFIED);
guint64 now = g_get_real_time () / 1000000; // that one is in µs
if (g_str_has_suffix (filename, ".html")) {
return CACHE_TIME_HTML > (now - mtime); // if we updated recently enough, that's good
}
return CACHE_TIME_FILES > (now - mtime); // non-html files are less likely to be updated
}
return FALSE; // no file info: this file doesn't exists; we better not rely on cache
}
static void
request_url_cb (GObject *unused,
GAsyncResult *res,
......@@ -158,7 +179,8 @@ request_url (const gchar *url,
provider_cb final_cb,
gpointer user_data)
{
if (self.online_mode == MOODLE_MODE_ONLINE) {
if (!cache_is_usable_for (filename) && self.online_mode == MOODLE_MODE_ONLINE) {
SoupMessage *msg;
msg = soup_message_new ("GET", url);
......@@ -170,7 +192,7 @@ request_url (const gchar *url,
soup_session_send_async (self.session, msg, NULL, request_url_cb, (gpointer)cb_info);
} else { // offline_mode
g_print ("using cache for %s\n", filename);
// this is not quite async, but works «fast enough»
g_autoptr (GFile) file = g_file_new_for_uri (filename);
GInputStream *is;
......@@ -219,7 +241,8 @@ moodle_downloaded_file_cb (GInputStream *is,
write_g_input_stream_to_file (is, real_filename ?: filename_looked_for);
}
final_cb (user_data, g_strdup (filename_looked_for));
if (final_cb)
final_cb (user_data, g_strdup (filename_looked_for));
}
void
......@@ -322,7 +345,8 @@ got_course_content_cb (GInputStream *is,
struct CourseContent content = moodle_parse_course_content (html, html_size, filename);
if (should_save_to_disk)
write_file_async (filename, g_steal_pointer (&html), html_size);
final_cb (user_data, &content);
if (final_cb)
final_cb (user_data, &content);
}
......@@ -340,7 +364,8 @@ got_folder_content_cb (GInputStream *is,
struct FolderContent content = moodle_parse_folder_content (html, html_size, filename);
if (should_save_to_disk)
write_file_async (filename, g_steal_pointer (&html), html_size);
final_cb (user_data, &content);
if (final_cb)
final_cb (user_data, &content);
}
void
......
......@@ -25,6 +25,7 @@ typedef void (*courses_cb) (gpointer user_data, struct Courses);
typedef void (*provider_cb) (gpointer user_data, gpointer retrieved_content);
// retrieved_content will be a struct Courses *, struct FolderContent *
// according to the called function name.
// when giving to a function a provider_cb, you can pass NULL instead.
enum moodle_error {
MOODLE_ERROR_OK,
......
......@@ -3,7 +3,7 @@
<interface>
<requires lib="gtk+" version="3.16"/>
<requires lib="libhandy" version="0.0"/>
<object class="GtkPopoverMenu" id="menu_popover">
<object class="GtkPopoverMenu" id="primary_menu_popover">
<child>
<object class="GtkBox">
<property name="visible">True</property>
......@@ -44,9 +44,32 @@
</object>
</child>
</object>
<object class="GtkPopoverMenu" id="secondary_menu_popover">
<child>
<object class="GtkBox">
<property name="visible">True</property>
<property name="margin">10</property>
<property name="orientation">vertical</property>
<child>
<object class="GtkModelButton" id="download">
<property name="text" translatable="yes">Download course files</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="action-name">win.download-current-course</property>
</object>
<packing>
<property name="position">0</property>
</packing>
</child>
</object>
</child>
</object>
<template class="GuiWindow" parent="GtkApplicationWindow">
<property name="can_focus">False</property>
<property name="title">Handy Moodle</property>
<property name="title">Moody</property>
<property name="default_width">360</property>
<property name="default_height">576</property>
<signal name="key-press-event" handler="key_pressed_cb" after="yes" swapped="no"/>
......@@ -65,8 +88,27 @@
<object class="GtkHeaderBar" id="header_bar">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="title">Handy Moodle</property>
<property name="title">Moody</property>
<property name="show_close_button">True</property>
<child>
<object class="GtkMenuButton" id="primary_menu">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="popover">primary_menu_popover</property>
<child>
<object class="GtkImage" id="open_primary_menu">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="icon_name">open-menu-symbolic</property>
<property name="icon_size">1</property>
</object>
</child>
</object>
<packing>
<property name="pack_type">end</property>
</packing>
</child>
</object>
<packing>
<property name="name">sidebar</property>
......@@ -112,16 +154,16 @@
</packing>
</child>
<child>
<object class="GtkMenuButton" id="menu">
<property name="visible">True</property>
<object class="GtkMenuButton" id="secondary_menu">
<property name="visible">False</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="popover">menu_popover</property>
<property name="popover">secondary_menu_popover</property>
<child>
<object class="GtkImage" id="open_menu">
<object class="GtkImage" id="open_secondary_menu">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="icon_name">open-menu-symbolic</property>
<property name="icon_name">view-more-symbolic</property>
<property name="icon_size">1</property>
</object>
</child>
......@@ -217,7 +259,7 @@
<property name="opacity">0.7</property>
<property name="halign">center</property>
<property name="margin_bottom">12</property>
<property name="label" translatable="yes">Welcome to Handy Moodle</property>
<property name="label" translatable="yes">Welcome!</property>
<property name="justify">center</property>
<property name="wrap">True</property>
<attributes>
......@@ -236,7 +278,7 @@
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="opacity">0.7</property>
<property name="label" translatable="yes">This is a very αλφα version.</property>
<property name="label" translatable="yes">You should set you username to use Moody :-)</property>
<property name="justify">center</property>
<property name="wrap">True</property>
<property name="use_markup">True</property>
......@@ -335,3 +377,5 @@