diff --git a/README.md b/README.md index 6b2b8b5487c1f67a96eb638ea365c6b8c2787337..992531ed7848389b293c99ecb5eab00c63fa891b 100644 --- a/README.md +++ b/README.md @@ -15,8 +15,10 @@ Fedora and Debian packages are planned. You can also install using flatpak, which should work for every Linux distribution ```sh +flatpak install org.gnome.Sdk//3.30 org.gnome.Platform//3.30 flatpak-builder --install --user --force-clean _flatbuild ch.gnugen.Moody.json ``` +For some reason, you may need to install elfutils `sudo apt install elfutils` Flatpak will build libhandy and myhtml from their repositry, and use libsoup and libsecret from the Gnome runtime. diff --git a/epfl.h b/epfl.h index 542c6ca3cb6757e986d69d0700e352ee3b6ef9ec..924d8af8e55be9870150c8f2db2f52e1ccb1dca7 100644 --- a/epfl.h +++ b/epfl.h @@ -28,8 +28,10 @@ #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=" + diff --git a/src/data-struct.h b/src/data-struct.h index f81ee6a5e29578ee27e13991aa77904b360f28c1..983e436508c0ef6816017f5bbd21bc25bbd868ae 100644 --- a/src/data-struct.h +++ b/src/data-struct.h @@ -23,6 +23,7 @@ enum resource_type { RESOURCE_ASSIGNMENT, RESOURCE_FILE, + RESOURCE_FOLDERFILE, // a file inside a folder: in such case, the id is the folder id RESOURCE_FOLDER, RESOURCE_FORUM, RESOURCE_PAGE, @@ -53,9 +54,15 @@ struct Week { struct Resource* resources; }; +struct FolderContent { + guint resource_count; + struct Resource* resources; +}; + struct Resource { enum resource_type type; guint id; gchar* textname; gchar* name; + gchar* filename; // present only when it's a RESOURCE_FOLDERFILE }; diff --git a/src/gui-course.c b/src/gui-course.c index 8c0e6f79becabab85359681be5b2dc2490a9655c..2613fc5e313ad03d3b8cb5ad014a1257e280b1ff 100644 --- a/src/gui-course.c +++ b/src/gui-course.c @@ -85,11 +85,12 @@ update_course_content (GtkWidget *self_widget, gpointer unused) } static void -course_content_obtained_cb (gpointer ptr, struct CourseContent content) +course_content_obtained_cb (gpointer ptr, gpointer data) { GuiCourse *self = ptr; + struct CourseContent *content = data; self->content_obtained = TRUE; - self->content = content; + self->content = *content; update_course_content (GTK_WIDGET (self), NULL); } diff --git a/src/gui-pass-dialog.c b/src/gui-pass-dialog.c index 4f172e885f3db341ef0b5af12812fab29a086991..eb58a452b44389e1160635fcdbe4c7e2f0d346b3 100644 --- a/src/gui-pass-dialog.c +++ b/src/gui-pass-dialog.c @@ -44,7 +44,9 @@ gui_pass_dialog_new (GtkWindow *window, { pass_changed_cb = cb; pass_changed_cb_data = user_data; - GuiPassDialog *self = g_object_new (HDY_TYPE_GUI_PASS_DIALOG, NULL); + GuiPassDialog *self = g_object_new (HDY_TYPE_GUI_PASS_DIALOG, + "use-header-bar", 1, + NULL); // prefill the username gtk_entry_set_text (self->user_entry, current_username); diff --git a/src/gui-resource.c b/src/gui-resource.c index 18d2ae7d9f9ad5aecab7f1cf456ddc696e46575d..0110e901088765b27030ed11e0a74820744116a1 100644 --- a/src/gui-resource.c +++ b/src/gui-resource.c @@ -32,11 +32,13 @@ struct _GuiResource //GtkBox *sub_item_list; gint type; guint id; + const gchar *filename; gboolean downloaded; }; enum { - PROP_0, PROP_RESOURCE_TYPE, PROP_RESOURCE_NAME, PROP_RESOURCE_TEXTNAME, PROP_RESOURCE_ID, N_RESOURCE_PROPERTIES + PROP_0, PROP_RESOURCE_TYPE, PROP_RESOURCE_NAME, PROP_RESOURCE_TEXTNAME, + PROP_RESOURCE_ID, PROP_RESOURCE_FILENAME, N_RESOURCE_PROPERTIES }; GParamSpec *gui_resource_properties[N_RESOURCE_PROPERTIES] = { NULL, }; @@ -47,25 +49,39 @@ G_DEFINE_TYPE (GuiResource, gui_resource, HDY_TYPE_ACTION_ROW) GuiResource * gui_resource_new (struct Resource file_info) { + // if both or none are true, there is a problem + g_assert ((file_info.filename==NULL) ^ (file_info.type==RESOURCE_FOLDERFILE)); return g_object_new (HDY_TYPE_GUI_RESOURCE, "resource_type", file_info.type, "resource_name", file_info.name, "resource_textname", file_info.textname, "resource_id", file_info.id, + "resource_filename", file_info.filename, NULL); } static void -open_file_cb (gpointer ptr, guint id) +open_file_cb (gpointer ptr, gpointer filename_ptr) { GuiResource *self = ptr; - gchar *filename = get_resource_filename (RESOURCE_FILE, id); - if (filename) { - if (!g_app_info_launch_default_for_uri (filename, NULL, NULL)) { - g_warning ("Couldn't start a program to open '%s'\n", filename); - self->downloaded = FALSE; // force download again - } - g_free (filename); + gchar *filename = filename_ptr; + if (!g_app_info_launch_default_for_uri (filename, NULL, NULL)) { + g_warning ("Couldn't start a program to open '%s'\n", filename); + self->downloaded = FALSE; // force download again + } +} + +static void +open_folder_cb (gpointer ptr, gpointer data) +{ + GuiResource *self = ptr; + struct FolderContent *content = data; + GtkListBox *parent = GTK_LIST_BOX (gtk_widget_get_parent (GTK_WIDGET (self))); + guint start_index = gtk_list_box_row_get_index (GTK_LIST_BOX_ROW (self)); + gtk_container_remove (GTK_CONTAINER (parent), GTK_WIDGET (self)); + for (guint i=0; i<content->resource_count; i++) { + GuiResource *resource = gui_resource_new (content->resources[i]); + gtk_list_box_insert (parent, GTK_WIDGET (resource), start_index+i); } } @@ -80,15 +96,17 @@ activated_cb (GtkListBox* unused, GuiResource *self) g_error ("could not open url %s\n", url); } break; case RESOURCE_FILE: - /*if (self->downloaded) - open_file_cb (self); - else*/ - moodle_provider_download_file_async (self->id, open_file_cb, self); + moodle_provider_download_file_async (self->id, open_file_cb, self); + break; + case RESOURCE_FOLDERFILE: + if (self->filename && self->id) + moodle_provider_download_file_by_filename_async (self->id, self->filename, open_file_cb, self); + else + g_warning ("That's odd: missing filename and/or id :-/\n"); break; case RESOURCE_FOLDER: - { g_autofree gchar *url = g_strdup_printf (EPFL_FOLDER_LINK"%u", self->id); - if (!g_app_info_launch_default_for_uri (url, NULL, NULL)) - g_error ("could not open url %s\n", url); } + moodle_provider_get_folder_content_async (open_folder_cb, self->id, self); + //folder_opened_cb (self, 0, NULL); break; case RESOURCE_FORUM: // just open it in the web browser @@ -136,6 +154,7 @@ gui_resource_set_property (GObject *object, hdy_action_row_set_icon_name (HDY_ACTION_ROW (self), "task-due"); break; case RESOURCE_FILE: + case RESOURCE_FOLDERFILE: hdy_action_row_set_icon_name (HDY_ACTION_ROW (self), "x-office-document"); break; case RESOURCE_FOLDER: @@ -167,13 +186,11 @@ gui_resource_set_property (GObject *object, hdy_action_row_set_title (HDY_ACTION_ROW (self), g_value_get_string (value)); break; case PROP_RESOURCE_ID: - //switch (self->type) self->id = g_value_get_uint (value); - gchar *filename = get_resource_filename (RESOURCE_FILE, self->id); - if (filename) { - self->downloaded = g_file_test (filename, G_FILE_TEST_EXISTS); - g_free (filename); - } + break; + case PROP_RESOURCE_FILENAME: + self->filename = g_strdup (g_value_get_string (value)); + // ask whether we have already downloaded the file break; default: // We don't have any other property... @@ -214,6 +231,13 @@ gui_resource_class_init (GuiResourceClass *class) 0, G_PARAM_WRITABLE|G_PARAM_CONSTRUCT_ONLY); + gui_resource_properties[PROP_RESOURCE_FILENAME] = + g_param_spec_string ("resource_filename", "filename", + "The resource filename, like 'serie3.pdf';" + " if this property is non-null then it is used instead of `id`", + NULL, + G_PARAM_WRITABLE|G_PARAM_CONSTRUCT_ONLY); + object_class->set_property = gui_resource_set_property; g_object_class_install_properties (object_class, N_RESOURCE_PROPERTIES, diff --git a/src/gui-week.c b/src/gui-week.c index b6dd1813b82dde92807dca36ff66baabcba1f221..1eda77224e91c0a7bb2f4dd3561d0a638f4014cf 100644 --- a/src/gui-week.c +++ b/src/gui-week.c @@ -25,7 +25,8 @@ struct _GuiWeek { GtkBox parent_instance; - HdyActionRow *text; + //HdyActionRow *text; + HdyActionRow *placeholder; GtkLabel *week_label; const gchar *week_name; GtkListBox *week_content; @@ -58,7 +59,7 @@ update_files (GuiWeek* self) { for (guint i=0; i<self->resource_count; i++) { GuiResource* file = gui_resource_new (self->resources[i]); - gtk_list_box_insert (self->week_content, GTK_WIDGET (file), i+1); + gtk_container_add (GTK_CONTAINER (self->week_content), GTK_WIDGET (file)); } } @@ -121,6 +122,7 @@ gui_week_class_init (GuiWeekClass *class) gtk_widget_class_set_template_from_resource (widget_class, "/ch/gnugen/Moody/ui/gui-week.ui"); gtk_widget_class_bind_template_child (widget_class, GuiWeek, week_label); gtk_widget_class_bind_template_child (widget_class, GuiWeek, week_content); + gtk_widget_class_bind_template_child (widget_class, GuiWeek, placeholder); gtk_widget_class_bind_template_callback_full (widget_class, "file_selected_cb", G_CALLBACK(activated_cb)); } @@ -129,4 +131,13 @@ gui_week_init (GuiWeek *self) { gtk_widget_init_template (GTK_WIDGET (self)); gtk_list_box_set_header_func (self->week_content, hdy_list_box_separator_header, NULL, NULL); + + // don't take too much space + GtkWidget *box = gtk_container_get_children (GTK_CONTAINER (self->placeholder))->data; + g_object_set (G_OBJECT (box), + "height-request", -1, + "margin", 0, + "margin-left", 12, + "margin-right", 12, + NULL); } diff --git a/src/gui-window.c b/src/gui-window.c index 08dcaafa87d0a5e0078288356f448c651a592818..1bbafb44c432a2ecf8c9a0df382da760150bd21d 100644 --- a/src/gui-window.c +++ b/src/gui-window.c @@ -200,6 +200,17 @@ update_courses (GuiWindow *self) self->courses[i].name, self->courses[i].name); } + + // ellipsize the labels + GtkWidget *scrolled_win = gtk_bin_get_child (GTK_BIN (self->sidebar)); + GtkWidget *viewport = gtk_bin_get_child (GTK_BIN (scrolled_win)); + GtkWidget *label_list = gtk_bin_get_child (GTK_BIN (viewport)); + + void ellipsize_label (GtkWidget *list_item, gpointer unused) { + GtkLabel *label = GTK_LABEL (gtk_bin_get_child (GTK_BIN (list_item))); + gtk_label_set_ellipsize (label, PANGO_ELLIPSIZE_END); + } + gtk_container_foreach (GTK_CONTAINER (label_list), ellipsize_label, NULL); } static void diff --git a/src/moodle-parser.c b/src/moodle-parser.c index c76b2186facd36543146f1abdacfb902c4dc7024..07c3c650f0579286c3269508ec1e26c846172285 100644 --- a/src/moodle-parser.c +++ b/src/moodle-parser.c @@ -26,8 +26,33 @@ static myhtml_t *myhtml; + +static enum resource_type +read_link (const char *href, guint *id, gchar **filename) +{ + // yay C! + if (sscanf (href, EPFL_ASSIGNMENT_LINK"%u", id)) + return RESOURCE_ASSIGNMENT; + if (sscanf (href, EPFL_FILE_LINK"%u", id)) + return RESOURCE_FILE; + if (sscanf (href, EPFL_FOLDERFILE_LINK"%m[^?]", id, filename)) + return RESOURCE_FOLDERFILE; + if (sscanf (href, EPFL_FOLDER_LINK"%u", id)) + return RESOURCE_FOLDER; + if (sscanf (href, EPFL_FORUM_LINK"%u", id)) + return RESOURCE_FORUM; + if (sscanf (href, EPFL_PAGE_LINK"%u", id)) + return RESOURCE_PAGE; + if (sscanf (href, EPFL_QUIZ_LINK"%u", id)) + return RESOURCE_QUIZ; + if (sscanf (href, EPFL_URL_LINK"%u", id)) + return RESOURCE_URL; + g_warning ("unknown kind of link, %s", href); + return -1; +} + char * -moodle_parse_form_error (const char *html, size_t html_size) +moodle_parse_form_error (const gchar *html, gsize html_size) { /*myhtml_collection_t *collection = myhtml_get_nodes_by_tag_id (tree, NULL, MyHTML_TAG_SCRIPT, NULL); @@ -51,8 +76,79 @@ moodle_parse_form_error (const char *html, size_t html_size) } +struct FolderContent +moodle_parse_folder_content (const gchar *html, gsize html_size, const gchar *moodle_filename) +{ + myhtml_tree_t *tree; + myhtml_tree_node_t *root_node; // the node we will look for urls into + myhtml_collection_t *collection; + myhtml_collection_t *hrefs; + struct Resource *resources; + + if (!html_size || !html) + g_error ("empty html given"); + + tree = myhtml_tree_create (); + myhtml_tree_init (tree, myhtml); + myhtml_parse (tree, MyENCODING_UTF_8, html, html_size); + + collection = myhtml_get_nodes_by_attribute_value (tree, NULL, NULL, FALSE, + "role", strlen("role"), + "main", strlen("main"), + NULL); + + if(collection && collection->list && collection->length==1) + root_node = collection->list[0]; + else + g_error ("weird result getting the folder container\n"); + myhtml_collection_destroy (collection); + + hrefs = myhtml_get_nodes_by_tag_id_in_scope (tree, NULL, + root_node, + MyHTML_TAG_A, + NULL); + + if (!hrefs || !hrefs->list || !hrefs->length) + g_error ("weird result getting folder's links\n"); + + resources = malloc (hrefs->length*sizeof(struct Resource)); // at most this size will be used. + size_t count=0; + enum resource_type type; + gchar *name=NULL; + gchar *href=NULL; + gchar *filename=NULL; + guint id; + for (size_t i=0; i<hrefs->length; i++) { + myhtml_tree_node_t *node = hrefs->list[i]; + myhtml_tree_attr_t *attr = myhtml_node_attribute_first (node); + while (attr) { + // find the href attribute + if (strcmp (myhtml_attribute_key (attr, NULL), "href") == 0) { + href = g_strdup (myhtml_attribute_value (attr, NULL)); // found! + break; + } + attr = myhtml_attribute_next (attr); + } + if (href) { + type = read_link (href, &id, &filename); + if (node->child && node->child->next && node->child->next->child) { + name = g_strdup (myhtml_node_text(node->child->next->child, NULL)); + } + resources[count] = (struct Resource) {type, id, name, href, filename}; + count++; + } + } + + myhtml_collection_destroy (hrefs); + myhtml_tree_destroy (tree); + + struct FolderContent content = {count, resources}; + return content; +} + + struct Courses -moodle_parse_course_list (const char *html, size_t html_size, const gchar *filename) +moodle_parse_course_list (const gchar *html, gsize html_size, const gchar *filename) { myhtml_tree_t *tree; myhtml_tree_node_t *courses_node; // the node we will look for urls into @@ -206,7 +302,7 @@ moodle_extract_week (gchar *week_name, myhtml_tree_t *tree, myhtml_tree_node_t * while (li) { gchar *name; gchar *textname; - enum resource_type type = -1; + enum resource_type type; guint id; myhtml_collection_t *hrefs = myhtml_get_nodes_by_tag_id_in_scope (tree, NULL, li, @@ -230,23 +326,7 @@ moodle_extract_week (gchar *week_name, myhtml_tree_t *tree, myhtml_tree_node_t * if (strcmp (myhtml_attribute_key (attr, NULL), "href") == 0) { href = myhtml_attribute_value (attr, &size); - // yay C! - if (sscanf (href, EPFL_ASSIGNMENT_LINK"%u", &id)) - type = RESOURCE_ASSIGNMENT; - else if (sscanf (href, EPFL_FILE_LINK"%u", &id)) - type = RESOURCE_FILE; - else if (sscanf (href, EPFL_FOLDER_LINK"%u", &id)) - type = RESOURCE_FOLDER; - else if (sscanf (href, EPFL_FORUM_LINK"%u", &id)) - type = RESOURCE_FORUM; - else if (sscanf (href, EPFL_PAGE_LINK"%u", &id)) - type = RESOURCE_PAGE; - else if (sscanf (href, EPFL_QUIZ_LINK"%u", &id)) - type = RESOURCE_QUIZ; - else if (sscanf (href, EPFL_URL_LINK"%u", &id)) - type = RESOURCE_URL; - else - g_warning ("unknown kind of link, %s", href); + type = read_link (href, &id, NULL); string = /*g_path_get_basename (*/href/*)*/; size = strlen (string); @@ -265,8 +345,7 @@ moodle_extract_week (gchar *week_name, myhtml_tree_t *tree, myhtml_tree_node_t * if (id && textname && name) { assert(resource_count<1000); // there should be no more than 1000 files a week, right ? right ?? - struct Resource resource = {type, id, textname, name}; - resources[resource_count] = resource; + resources[resource_count] = (struct Resource) {type, id, textname, name, NULL}; resource_count++; } else { g_print ("a li doesn't have any link\n"); @@ -286,7 +365,7 @@ moodle_extract_week (gchar *week_name, myhtml_tree_t *tree, myhtml_tree_node_t * struct CourseContent -moodle_parse_course_content (const char *html, size_t html_size, const char *filename) +moodle_parse_course_content (const gchar *html, gsize html_size, const gchar *filename) { myhtml_tree_t *tree; myhtml_collection_t *root_col; diff --git a/src/moodle-parser.h b/src/moodle-parser.h index b8353960441179084e591ef7da8cf6ace645d69a..381ad38bd8391aace6e85331f4936355761edfc3 100644 --- a/src/moodle-parser.h +++ b/src/moodle-parser.h @@ -21,6 +21,7 @@ #include "data-struct.h" +struct FolderContent moodle_parse_folder_content (const gchar *html, gsize html_size, const gchar *filename); struct Courses moodle_parse_course_list (const gchar *html, gsize html_size, const gchar *filename); struct CourseContent moodle_parse_course_content (const gchar *html, gsize html_size, const gchar *filename); diff --git a/src/moodle-provider.c b/src/moodle-provider.c index 0760ddddb51bad4ecc9a57d362988dbdfa7f14e1..3419be731ed7696f4e13f9a069270b5c77d448f1 100644 --- a/src/moodle-provider.c +++ b/src/moodle-provider.c @@ -24,19 +24,7 @@ #include "utils.h" #include "config.h" -struct course_content_cb_data { - course_content_cb cb; - gpointer user_data; - gchar *filename; -}; -struct file_cb_data { - file_cb cb; - gpointer user_data; - SoupMessage *msg; - guint file_id; -}; - -static struct MoodleProviderState { +typedef struct MoodleProviderState { SoupSession *session; SoupCookieJar *jar; SoupLogger *logger; @@ -44,64 +32,148 @@ static struct MoodleProviderState { const gchar *password; MoodleOnlineMode online_mode; gboolean connected; -} self; - +} MoodleProviderState; +static MoodleProviderState self; + + +typedef void (*handle_stream_cb) (GInputStream *is, + gboolean should_save_to_disk, + const gchar *filename, + SoupMessageHeaders *headers, + provider_cb cb, + gpointer user_data); + +struct cb_data { + handle_stream_cb stream_cb; // the function that should handle the response stream + provider_cb final_cb; // the callback the user of the moodle-provider gave us + SoupMessage *msg; // the message we sent: it is usefull as it contains the response headers + gpointer user_data; // the data the callback needs + gchar *filename; // the file we are looking for: when caching, the file should be accessible from here. +}; static void -moodle_course_content_cb (SoupSession *session, - SoupMessage *msg, - gpointer course_cb_ptr) +request_url_cb (GObject *unused, + GAsyncResult *res, + gpointer info_ptr) { - struct course_content_cb_data *course_cb = course_cb_ptr; + struct cb_data *info = info_ptr; + gboolean save_to_disk = TRUE; - if (msg->status_code == 1) + if (info->msg->status_code == 1) return; // we are aborting - if (msg->status_code != 200) - g_warning ("connection error, http %u\n", msg->status_code); + if (info->msg->status_code != 200) { + g_warning ("connection error, http %u\n", info->msg->status_code); + save_to_disk = FALSE; + } - SoupBuffer *result = soup_message_body_flatten (msg->response_body); - write_file (course_cb->filename, result->data, result->length); + GInputStream *is = soup_session_send_finish (self.session, + res, + NULL); - struct CourseContent content = moodle_parse_course_content (result->data, result->length, course_cb->filename); + SoupMessageHeaders *headers; + g_object_get (G_OBJECT (info->msg), "response-headers", &headers, NULL); + info->stream_cb (is, save_to_disk, info->filename, headers, info->final_cb, info->user_data); - course_cb->cb(course_cb->user_data, content); - free (course_cb->filename); - free (course_cb); - soup_buffer_free (result); + g_free (info->filename); + free (info); +} + +// this function is the core of this file. It basically means «get me that», +// whether I'm online or not and whether it's in cache or not. +// (btw, here's the full prototype, expanding the typedefs:) +// static void request_url (const gchar *url, const gchar *filename, SoupMessagePriority priority, void (*stream_cb) (const gchar *html, gsize html_size, const gchar *filename, void (*cb) (void *user_data, void *retrieved_content), void *user_data), void (*final_cb) (void *user_data, void *retrieved_content), void *user_data); +static void +request_url (const gchar *url, + const gchar *filename, + SoupMessagePriority priority, + handle_stream_cb stream_cb, + provider_cb final_cb, + gpointer user_data) +{ + if (self.online_mode == MOODLE_MODE_ONLINE) { + + SoupMessage *msg; + msg = soup_message_new ("GET", url); + soup_message_set_priority (msg, priority); + struct cb_data *cb_info = malloc (sizeof (struct cb_data)); + *cb_info = (struct cb_data) + { .stream_cb = stream_cb, .final_cb = final_cb, .msg = msg, + .user_data = user_data, .filename = g_strdup (filename) }; + soup_session_send_async (self.session, msg, NULL, request_url_cb, (gpointer)cb_info); + + } else { // offline_mode + + // this is not quite async, but works «fast enough» + g_autoptr (GFile) file = g_file_new_for_uri (filename); + GInputStream *is; + is = G_INPUT_STREAM (g_file_read (file, NULL, NULL)); + if (G_IS_INPUT_STREAM (is)) + stream_cb (is, FALSE, filename, NULL, final_cb, user_data); + else + g_warning ("Could not read file %s from cache\n", filename); + + } } static void -moodle_downloaded_file_cb (GObject *unused, - GAsyncResult *res, - gpointer ptr) +moodle_downloaded_file_cb (GInputStream *is, + gboolean should_save_to_disk, + const gchar *filename_looked_for, + SoupMessageHeaders *headers, + provider_cb final_cb, + gpointer user_data) { - struct file_cb_data *cb_info = ptr; - GInputStream *is = soup_session_send_finish (self.session, - res, - NULL); + if (should_save_to_disk) { + // we first try to read the real name (serie3.pdf) + // and if it exists, we make file-123456 a symlink to it - //const char *filename = g_path_get_basename (soup_uri_to_string (soup_message_get_uri (cb_info->msg), FALSE)); - g_autofree char *filename = get_resource_filename (RESOURCE_FILE, cb_info->file_id); - write_g_input_stream_to_file (is, filename); - cb_info->cb (cb_info->user_data, cb_info->file_id); - g_slice_free (struct file_cb_data, cb_info); + const char *header = soup_message_headers_get_one (headers, "Content-Disposition"); + g_autofree char *real_filename = NULL, *name = NULL; + + if (sscanf (header, "inline;" " filename=\"%m[^\"]\"", &name) == 1 + || sscanf (header, "attachment;"" filename=\"%m[^\"]\"", &name) == 1) { + + real_filename = get_resource_filename (RESOURCE_FILE, 0, name); + g_autofree gchar *path1 = g_filename_from_uri (real_filename, NULL, NULL); + g_autofree gchar *path2 = g_filename_from_uri (filename_looked_for, NULL, NULL); + + if (g_strcmp0 (path1, path2)) { // paths are different: make a symlink! + g_autoptr (GFile) link_file = g_file_new_for_uri (filename_looked_for); + g_file_make_symbolic_link (link_file, name, NULL, NULL); + // name is the name without the path: we want to make a relative link, so that's perfect + } + + } else { + g_warning ("could not read header '%s'\n", header); + } + + write_g_input_stream_to_file (is, real_filename ?: filename_looked_for); + } + + final_cb (user_data, g_strdup (filename_looked_for)); } void -moodle_provider_download_file_async (guint id, file_cb cb, gpointer cb_arg) +moodle_provider_download_file_by_filename_async (guint folder_id, + const gchar *moodle_filename, + provider_cb cb, + gpointer cb_arg) +{ + g_autofree char *url = g_strdup_printf (EPFL_FOLDERFILE_LINK"%s?forcedownload=1", folder_id, moodle_filename); + g_autofree char *filename = get_resource_filename (RESOURCE_FOLDERFILE, folder_id, moodle_filename); + request_url (url, filename, SOUP_MESSAGE_PRIORITY_HIGH, moodle_downloaded_file_cb, cb, cb_arg); +} + +void +moodle_provider_download_file_async (guint id, + provider_cb cb, + gpointer cb_arg) { g_autofree char *url = g_strdup_printf (EPFL_FILE_LINK"%u&redirect=1", id); - SoupMessage *msg = soup_message_new ("GET", url); - soup_message_set_priority (msg, SOUP_MESSAGE_PRIORITY_HIGH); - struct file_cb_data *cb_info = g_slice_new (struct file_cb_data); - *cb_info = (struct file_cb_data) { cb, cb_arg, msg, id }; - soup_session_send_async (self.session, - msg, - NULL, - moodle_downloaded_file_cb, - (gpointer) cb_info); + g_autofree char *filename = get_resource_filename (RESOURCE_FILE, id, NULL); + request_url (url, filename, SOUP_MESSAGE_PRIORITY_HIGH, moodle_downloaded_file_cb, cb, cb_arg); } @@ -171,39 +243,48 @@ moodle_provider_connect (GInputStream **response) } -enum moodle_error -moodle_provider_get_course_content_async (course_content_cb cb, guint id, gpointer cb_arg) +static void +got_course_content_cb (GInputStream *is, + gboolean should_save_to_disk, + const gchar *filename, + SoupMessageHeaders *headers, + provider_cb final_cb, + gpointer user_data) { - g_autofree char *url = g_strdup_printf (EPFL_COURSE_LINK"%u", id); - g_autofree char *filename = get_course_filename (id); - - if (self.online_mode == MOODLE_MODE_ONLINE) { - - SoupMessage *msg; - msg = soup_message_new ("GET", url); - soup_message_set_priority (msg, SOUP_MESSAGE_PRIORITY_LOW); - struct course_content_cb_data *course_cb = malloc (sizeof (struct course_content_cb_data)); - course_cb->cb = cb; - course_cb->user_data = cb_arg; - course_cb->filename = malloc (strlen (filename)+1); - strcpy (course_cb->filename, filename); - soup_session_queue_message (self.session, msg, moodle_course_content_cb, (gpointer)course_cb); - - } else { // offline_mode - - char *html; - size_t html_size; - read_file (filename, &html, &html_size); - struct CourseContent content = moodle_parse_course_content (html, html_size, filename); - free (html); - cb(cb_arg, content); + gchar *html; + gsize html_size; + read_g_input_stream (is, &html, &html_size); + if (should_save_to_disk) + write_file (filename, html, html_size); + struct CourseContent content = moodle_parse_course_content (html, html_size, filename); + final_cb (user_data, &content); +} - } - return MOODLE_ERROR_OK; +static void +got_folder_content_cb (GInputStream *is, + gboolean should_save_to_disk, + const gchar *filename, + SoupMessageHeaders *headers, + provider_cb final_cb, + gpointer user_data) +{ + gchar *html; + gsize html_size; + read_g_input_stream (is, &html, &html_size); + if (should_save_to_disk) + write_file (filename, html, html_size); + struct FolderContent content = moodle_parse_folder_content (html, html_size, filename); + final_cb (user_data, &content); } - +void +moodle_provider_get_course_content_async (provider_cb cb, guint id, gpointer cb_arg) +{ + g_autofree char *url = g_strdup_printf (EPFL_COURSE_LINK"%u", id); + g_autofree char *filename = get_course_filename (id); + request_url (url, filename, SOUP_MESSAGE_PRIORITY_LOW, got_course_content_cb, cb, cb_arg); +} enum moodle_error moodle_provider_get_courses_sync (courses_cb cb, @@ -238,8 +319,22 @@ moodle_provider_get_courses_sync (courses_cb cb, return MOODLE_ERROR_OK; } + +void +moodle_provider_get_folder_content_async (provider_cb cb, + guint id, + gpointer cb_arg) +{ + g_autofree char *url = g_strdup_printf (EPFL_FOLDER_LINK"%u", id); + g_autofree char *filename = get_resource_filename (RESOURCE_FOLDER, id, NULL); + + request_url (url, filename, SOUP_MESSAGE_PRIORITY_HIGH, got_folder_content_cb, cb, cb_arg); +} + void -moodle_provider_init (MoodleOnlineMode mode, const gchar *username, const gchar *password) +moodle_provider_init (MoodleOnlineMode mode, + const gchar *username, + const gchar *password) { self.online_mode = mode; self.connected = FALSE; diff --git a/src/moodle-provider.h b/src/moodle-provider.h index 3852c3eba774fe1acfc9855e05c08b26b6c33bcc..e0e5cd9306da5610ba7355682e6299ce5d4a9569 100644 --- a/src/moodle-provider.h +++ b/src/moodle-provider.h @@ -21,8 +21,10 @@ #include "data-struct.h" typedef void (*courses_cb) (gpointer user_data, struct Courses); -typedef void (*course_content_cb)(gpointer user_data, struct CourseContent); -typedef void (*file_cb) (gpointer user_data, guint filename); + +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. enum moodle_error { MOODLE_ERROR_OK, @@ -42,11 +44,21 @@ void moodle_provider_init (MoodleOnlineMode offline_mode, const gchar *username, // the provider must be _init'd again if we want to reuse it. void moodle_provider_disconnect (void); -void moodle_provider_download_file_async (guint id, file_cb cb, gpointer cb_arg); +void moodle_provider_download_file_by_filename_async (guint folder_id, + const gchar *filename, + provider_cb cb, + gpointer cb_arg); + +void moodle_provider_download_file_async (guint id, provider_cb cb, gpointer cb_arg); enum moodle_error moodle_provider_get_courses_sync (courses_cb cb, gpointer cb_arg); -enum moodle_error moodle_provider_get_course_content_async (course_content_cb cb, - guint id, - gpointer cb_arg); + +void moodle_provider_get_course_content_async (provider_cb cb, + guint id, + gpointer cb_arg); + +void moodle_provider_get_folder_content_async (provider_cb cb, + guint id, + gpointer cb_arg); diff --git a/src/utils.c b/src/utils.c index 8f5dd62c8cf9c82115f8fade75bcaa218f232c14..b8f3036ad29a8607517bfe26dfd9027e39ff22a1 100644 --- a/src/utils.c +++ b/src/utils.c @@ -37,14 +37,20 @@ get_cache_dir (void) return cache_dir; } +// get the canonical "file:///" url where a file should be accessible gchar * -get_resource_filename (gint res_type, guint id/*, gchar *filename*/) +get_resource_filename (gint res_type, guint id, const gchar *filename) { gchar *filepath; switch (res_type) { case RESOURCE_FILE: - //g_warn_if_fail (filename!=NULL); - filepath = g_strdup_printf ("file://%s/file-%u", get_cache_dir (), id); + if (filename) // used when we have a file by id and we want to save it to his real name + filepath = g_strdup_printf ("file://%s/%s", get_cache_dir (), filename); + else + filepath = g_strdup_printf ("file://%s/file-%u", get_cache_dir (), id); + break; + case RESOURCE_FOLDERFILE: + filepath = g_strdup_printf ("file://%s/%s", get_cache_dir (), filename); break; case RESOURCE_FOLDER: filepath = g_strdup_printf ("file://%s/folder-%u.html", get_cache_dir (), id); @@ -103,7 +109,7 @@ write_file (const char *name, const char *content, size_t size) // if we don't make a copy, it will get free'd by the caller // before we write content to the file. The content should // be "small" (<1MiB): a Moodle web page - char *copy = g_strdup (content); + char *copy = g_strndup (content, size); g_file_replace_contents_async (file, copy, size, @@ -148,7 +154,7 @@ read_g_input_stream (GInputStream *is, char **content, size_t *size) void write_g_input_stream_to_file (GInputStream *is, const char *filename) { - GFile *file = g_file_new_for_uri (filename); + g_autoptr (GFile) file = g_file_new_for_uri (filename); g_autoptr (GError) error = NULL; g_autoptr (GFileOutputStream) os = g_file_replace (file, NULL, FALSE, G_FILE_CREATE_PRIVATE, NULL, &error); diff --git a/src/utils.h b/src/utils.h index 0c290d1e027efc9167040a741a198fa3b91bec0b..6af0e77f4eb4fefc034dc5b5cbf2033f7527d9dc 100644 --- a/src/utils.h +++ b/src/utils.h @@ -19,7 +19,7 @@ #pragma once #include <gio/gio.h> -gchar *get_resource_filename (gint res_type, guint id/*, gchar *filename*/); +gchar *get_resource_filename (gint res_type, guint id, const gchar *filename); // takes either a filename or an id. gchar *get_course_filename (guint id); gchar *get_index_filename (void); diff --git a/ui/gui-course.ui b/ui/gui-course.ui index 5011d5c315fc69f634e94dffcacbbf564268d73b..f03a62d60ee46a62faaa2e1ae08a98671c26e7fe 100644 --- a/ui/gui-course.ui +++ b/ui/gui-course.ui @@ -10,8 +10,8 @@ <property name="margin_end">12</property> <property name="margin_top">32</property> <property name="margin_bottom">32</property> - <property name="maximum_width">550</property> - <property name="linear_growth_width">400</property> + <property name="maximum_width">600</property> + <property name="linear_growth_width">450</property> <child> <object class="GtkBox"> <property name="visible">True</property> @@ -133,3 +133,4 @@ </template> </interface> + diff --git a/ui/gui-pass-dialog.ui b/ui/gui-pass-dialog.ui index f100c7883add148b698d7540caad50b80048ba01..5a0401cf7c00566bbf84093ec2e68e3d1e55980e 100644 --- a/ui/gui-pass-dialog.ui +++ b/ui/gui-pass-dialog.ui @@ -5,7 +5,7 @@ <requires lib="libhandy" version="0.0"/> <template class="GuiPassDialog" parent="HdyDialog"> <property name="can_focus">False</property> - <property name="title" translatable="yes">Set<!-- Username and Password--></property> + <property name="title" translatable="yes">Set Username and Password</property> <property name="icon_name">dialog-password</property> <property name="type_hint">dialog</property> <child type="action"> @@ -149,3 +149,4 @@ </template> </interface> + diff --git a/ui/gui-week.ui b/ui/gui-week.ui index 72ce12289f45a100d8ce81a7a2863aaba770991b..c25c7f336e1e84d9c6482aa1895949fec351eeaa 100644 --- a/ui/gui-week.ui +++ b/ui/gui-week.ui @@ -15,6 +15,7 @@ <property name="halign">start</property> <property name="margin_bottom">3</property> <property name="margin_top">12</property> + <property name="ellipsize">end</property> <property name="label" translatable="yes">Week n</property> <attributes> <attribute name="weight" value="bold"/> @@ -31,15 +32,16 @@ <property name="visible">True</property> <property name="can_focus">False</property> <property name="selection_mode">none</property> - <signal name="row-activated" handler="file_selected_cb"/><!-- - <child> - <object class="HdyActionRow"> + <signal name="row-activated" handler="file_selected_cb"/> + <child type="placeholder"> + <object class="HdyActionRow" id="placeholder"> <property name="visible">True</property> + <property name="opacity">0.7</property> <property name="can_focus">True</property> <property name="activatable">False</property> - <property name="title" translatable="yes">Message this week from teacher</property> + <property name="title" translatable="yes">No new content this week</property> </object> - </child> + </child><!-- <child> <object class="HdyActionRow"> <property name="visible">True</property> diff --git a/ui/gui-window.ui b/ui/gui-window.ui index e8d7d37760648887681038478cb3c8d985459a86..f5230317b8decfa7b400625e07c561595506a628 100644 --- a/ui/gui-window.ui +++ b/ui/gui-window.ui @@ -172,6 +172,7 @@ <property name="visible">True</property> <child> <object class="GtkStack" id="stack"> + <property name="width_request">300</property> <property name="visible">True</property> <property name="can_focus">False</property> <property name="vhomogeneous">False</property> @@ -330,20 +331,7 @@ <headerbar name="sub_header_bar"/> </headerbars> </object> - <object class="GtkAdjustment" id="column_width_adjustment"> - <property name="lower">0</property> - <property name="upper">10000</property> - <property name="value">600</property> - <property name="page-increment">100</property> - <property name="step-increment">10</property> - </object> - <object class="GtkAdjustment" id="column_linear_width_adjustment"> - <property name="lower">0</property> - <property name="upper">10000</property> - <property name="value">500</property> - <property name="page-increment">100</property> - <property name="step-increment">10</property> - </object> </interface> +