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>
 
 
+