diff --git a/daemon/gvfsbackenddav.c b/daemon/gvfsbackenddav.c index 25fb24c..1a89a7e 100644 --- a/daemon/gvfsbackenddav.c +++ b/daemon/gvfsbackenddav.c @@ -52,6 +52,7 @@ #include "gvfsjobopenforwrite.h" #include "gvfsjobwrite.h" #include "gvfsjobseekwrite.h" +#include "gvfsjobclosewrite.h" #include "gvfsjobsetdisplayname.h" #include "gvfsjobqueryinfo.h" #include "gvfsjobqueryfsinfo.h" @@ -186,6 +187,23 @@ message_add_redirect_header (SoupMessage *msg, header_redirect); } +static void +message_add_if_match_header (SoupMessage *msg, + const char *value, + gboolean negative) +{ + const char *header; + + if (value == NULL) + return; + + header = negative ? "If-None-Match" : "If-Match"; + + soup_message_headers_append (msg->request_headers, + header, value); + +} + static inline gboolean str_equal (const char *a, const char *b, gboolean insensitive) { @@ -265,7 +283,14 @@ redirect_handler (SoupMessage *msg, gpointer user_data) guint status; gboolean redirect; + g_print ("+ redirect_handler\n"); + status = msg->status_code; + + if (! SOUP_STATUS_IS_REDIRECTION (status)) { + return; + } + new_loc = soup_message_headers_get (msg->response_headers, "Location"); /* If we don't have a location to redirect to, just fail */ @@ -382,6 +407,36 @@ g_vfs_backend_dav_send_message (GVfsBackend *backend, SoupMessage *message) return http_backend_send_message (backend, message); } +static SoupSession * +g_vfs_backend_dav_get_session (GVfsBackend *backend) +{ + GVfsBackendHttp *http_backend; + + http_backend = G_VFS_BACKEND_HTTP (backend); + return http_backend->session; +} + +static void +g_vfs_backend_dav_queue_message (GVfsBackend *backend, + SoupMessage *message, + SoupSessionCallback callback, + gpointer user_data) +{ + GVfsBackendHttp *http_backend; + SoupSession *session; + + http_backend = G_VFS_BACKEND_HTTP (backend); + session = http_backend->session; + + /* We have our own custom redirect handler */ + soup_message_set_flags (message, SOUP_MESSAGE_NO_REDIRECT); + + soup_message_add_header_handler (message, "got_body", "Location", + G_CALLBACK (redirect_handler), session); + + soup_session_queue_message (session, message, callback, user_data); +} + /* ************************************************************************* */ /* generic xml parsing functions */ @@ -1723,6 +1778,7 @@ do_enumerate (GVfsBackend *backend, static gboolean dav_make_backup (GVfsBackend *backend, SoupURI *uri, + const char *etag, GError **error) { SoupURI *backup_uri; @@ -1735,6 +1791,7 @@ dav_make_backup (GVfsBackend *backend, message_add_destination_header (msg, backup_uri); message_add_overwrite_header (msg, TRUE); + message_add_if_match_header (msg, etag, FALSE); status = g_vfs_backend_dav_send_message (backend, msg); res = TRUE; @@ -1753,256 +1810,494 @@ dav_make_backup (GVfsBackend *backend, return res; } -/* *** create () *** */ +typedef enum { + + PF_CANCLLED = -1, + + PF_OPEN = 0, + PF_NEED_DATA, + PF_HAVE_DATA, + PF_DATA_WRITTEN, + PF_CLOSE, + PF_CLOSE_WAIT, + PF_CLOSED + +} PutFileState; + +typedef struct { + + PutFileState state; + + GCancellable *cancellable; + GError *error; + + SoupSession *session; + + GVfsJob *job; + + GMutex *lock; + GCond *cond; + + char *data; + gsize data_len; + + char *etag; + +} PutFileHandle; + +static PutFileHandle * +put_file_handle_new (GVfsJob *job) +{ + PutFileHandle *handle; + + handle = g_slice_new0 (PutFileHandle); + + handle->lock = g_mutex_new (); + handle->cond = g_cond_new (); + + handle->state = PF_OPEN; + + handle->job = job; + + return handle; +} + +static void +put_file_handle_free (PutFileHandle *handle) +{ + g_mutex_free (handle->lock); + g_cond_free (handle->cond); + + if (handle->cancellable) + g_object_unref (handle->cancellable); + + g_slice_free (PutFileHandle, handle); +} + +#if 0 static void -try_create_tested_existence (SoupSession *session, SoupMessage *msg, - gpointer user_data) +put_file_handle_job_cancelled (GVfsJob *job, + PutFileHandle *handle) { - GVfsJob *job = G_VFS_JOB (user_data); - GVfsBackendHttp *op_backend = job->backend_data; - GOutputStream *stream; - SoupMessage *put_msg; - SoupURI *uri; + g_mutex_lock (handle->lock); + - if (SOUP_STATUS_IS_SUCCESSFUL (msg->status_code)) + g_mutex_unlock (handle->lock); +} + +static guint +put_file_handle_job_glue_cancellable (PutFileHandle *handle, + GVfsJob *job) +{ + guint id; + + id = g_signal_connect (job, "cancelled", + (GCallback) put_file_handle_job_cancelled, + PutFileHandle); + return id; +} +#endif + +static gboolean +put_file_handle_wirte (PutFileHandle *handle, + char *buffer, + gsize buffer_size, + GCancellable *cancellable, + GError **error) +{ + gboolean res; + gboolean have_written; + + have_written = FALSE; + res = TRUE; + + g_print ("+ put_file_handle_wirte\n"); + g_mutex_lock (handle->lock); + g_print (" put_file_handle_wirte: have lock %d\n", handle->state); + +restart: + switch (handle->state) { - g_vfs_job_failed (job, - G_IO_ERROR, - G_IO_ERROR_EXISTS, - _("Target file already exists")); - return; + case PF_HAVE_DATA: + /* Need to wait until data is written */ + g_cond_wait (handle->cond, handle->lock); + goto restart; + + case PF_NEED_DATA: + handle->data = buffer; + handle->data_len = buffer_size; + handle->state = PF_HAVE_DATA; + have_written = TRUE; + g_cond_signal (handle->cond); + g_print (" put_file_handle_wirte: does that work?!\n"); + g_cond_wait (handle->cond, handle->lock); + goto restart; + break; + + case PF_DATA_WRITTEN: + + if (have_written == FALSE) + goto restart; + + handle->state = PF_NEED_DATA; + + if ((res = handle->error == NULL) != TRUE) + g_propagate_error (error, handle->error); + break; + + default: + g_assert_not_reached (); } - /* TODO: other errors */ - uri = soup_message_get_uri (msg); - put_msg = soup_message_new_from_uri (SOUP_METHOD_PUT, uri); + g_assert (res == FALSE || handle->state == PF_NEED_DATA); + g_mutex_unlock (handle->lock); + g_print ("- put_file_handle_wirte\n"); + return res; +} - /* - * Doesn't work with apache > 2.2.9 - * soup_message_headers_append (put_msg->request_headers, "If-None-Match", "*"); - */ - stream = soup_output_stream_new (op_backend->session, put_msg, -1); - g_object_unref (put_msg); +static gboolean +put_file_handle_close (PutFileHandle *handle, + GCancellable *cancellable, + GError **error) +{ + gboolean res; - g_vfs_job_open_for_write_set_handle (G_VFS_JOB_OPEN_FOR_WRITE (job), stream); - g_vfs_job_succeeded (job); -} + g_print ("+ put_file_handle_close\n"); + g_mutex_lock (handle->lock); + g_print (" put_file_handle_close: have_lock\n"); -static gboolean -try_create (GVfsBackend *backend, - GVfsJobOpenForWrite *job, - const char *filename, - GFileCreateFlags flags) +restart: + switch (handle->state) + { + case PF_CLOSED: + if ((res = handle->error == NULL) == FALSE) + g_propagate_error (error, handle->error); + break; + + case PF_NEED_DATA: + handle->state = PF_CLOSE; + /* fallthrough */ + g_cond_signal (handle->cond); + + default: + g_cond_wait (handle->cond, handle->lock); + goto restart; + } + + g_mutex_unlock (handle->lock); + g_print ("- put_file_handle_close\n"); + return res; +} + +static void +put_done (SoupSession *session, + SoupMessage *msg, + gpointer user_data) { - SoupMessage *msg; - SoupURI *uri; + PutFileHandle *handle; + handle = (PutFileHandle *) user_data; - /* TODO: if SoupOutputStream supported chunked requests, we could - * use a PUT with "If-None-Match: *" and "Expect: 100-continue" - */ - uri = http_backend_uri_for_filename (backend, filename, FALSE); - msg = soup_message_new_from_uri (SOUP_METHOD_HEAD, uri); - soup_uri_free (uri); + g_print ("+ put_done\n"); - g_vfs_job_set_backend_data (G_VFS_JOB (job), backend, NULL); + g_mutex_lock (handle->lock); + g_print (" put_done: have lock\n"); + + if (! SOUP_STATUS_IS_SUCCESSFUL (msg->status_code)) + g_set_error (&handle->error, G_IO_ERROR, + http_error_code_from_status (msg->status_code), + "%s", msg->reason_phrase); + + handle->state = PF_CLOSED; + + /* XXX Set the etag */ + + g_cond_signal (handle->cond); + g_mutex_unlock (handle->lock); + + if (handle->job) + { + /* This probably means we didn't even get far at all + */ + g_vfs_job_failed (G_VFS_JOB (handle->job), + G_IO_ERROR, + http_error_code_from_status (msg->status_code), + "%s", msg->reason_phrase); + put_file_handle_free (handle); + } + + g_print ("- put_done\n"); - http_backend_queue_message (backend, msg, try_create_tested_existence, job); - return TRUE; } -/* *** replace () *** */ static void -open_for_replace_succeeded (GVfsBackendHttp *op_backend, GVfsJob *job, - SoupURI *uri, const char *etag) +put_wrote_chunk (SoupMessage *msg, + gpointer user_data) { - SoupMessage *put_msg; - GOutputStream *stream; + PutFileHandle *handle = (PutFileHandle *) user_data; + + g_print ("+ wrote chunk\n"); + g_mutex_lock (handle->lock); + g_print (" worte_chunk: have_lock %d\n", handle->state); - put_msg = soup_message_new_from_uri (SOUP_METHOD_PUT, uri); +restart: + switch (handle->state) + { - if (etag) - soup_message_headers_append (put_msg->request_headers, "If-Match", etag); + case PF_NEED_DATA: + g_cond_wait (handle->cond, handle->lock); + goto restart; + + case PF_HAVE_DATA: + soup_message_body_append (msg->request_body, + SOUP_MEMORY_STATIC, + handle->data, + handle->data_len); + handle->state = PF_DATA_WRITTEN; + break; + + case PF_DATA_WRITTEN: + g_cond_signal (handle->cond); + /* Does that work ? */ + g_print (" worte_chunk: Does taht work?\n"); + g_cond_wait (handle->cond, handle->lock); + goto restart; + + case PF_CLOSE: + soup_message_body_complete (msg->request_body); + handle->state = PF_CLOSE_WAIT; + /* Will signal the cond from put_done */ + break; + + default: + g_assert_not_reached (); + } - stream = soup_output_stream_new (op_backend->session, put_msg, -1); - g_object_unref (put_msg); + g_assert (handle->state == PF_DATA_WRITTEN || + handle->state == PF_CLOSE_WAIT); - g_vfs_job_open_for_write_set_handle (G_VFS_JOB_OPEN_FOR_WRITE (job), stream); - g_vfs_job_succeeded (job); + g_mutex_unlock (handle->lock); + g_print ("- wrote chunk\n"); } + static void -try_replace_checked_etag (SoupSession *session, SoupMessage *msg, - gpointer user_data) +put_got_informational (SoupMessage *msg, + gpointer user_data) { - GVfsJob *job = G_VFS_JOB (user_data); - GVfsBackendHttp *op_backend = job->backend_data; + PutFileHandle *handle = (PutFileHandle *) user_data; + + g_print ("got informational %u\n", msg->status_code); - if (msg->status_code == SOUP_STATUS_PRECONDITION_FAILED) + if (SOUP_STATUS_IS_INFORMATIONAL (msg->status_code)) { - g_vfs_job_failed (G_VFS_JOB (job), - G_IO_ERROR, - G_IO_ERROR_WRONG_ETAG, - _("The file was externally modified")); + g_print ("Is informational \n"); + if (handle->job) + { + g_vfs_job_succeeded (handle->job); + handle->job = NULL; + } + } + else + { + /* Makr the body as done just to be sure that we don't end + * up in some strange situations. In other words make sure + * put_done () gets called soon + * NB: job_done will find the handle->job and complete it + * there + */ + + handle->state = PF_CLOSE_WAIT; + soup_message_body_complete (msg->request_body); return; } - /* TODO: other errors */ - open_for_replace_succeeded (op_backend, job, soup_message_get_uri (msg), - soup_message_headers_get (msg->request_headers, "If-Match")); -} + handle->state = PF_NEED_DATA; + /* Initiate the first chunk */ + put_wrote_chunk (msg, user_data); +} -static gboolean -try_replace (GVfsBackend *backend, - GVfsJobOpenForWrite *job, - const char *filename, - const char *etag, - gboolean make_backup, - GFileCreateFlags flags) +static SoupMessage * +put_message_new (SoupURI *uri, + PutFileHandle *handle) { - GVfsBackendHttp *op_backend; - SoupURI *uri; + SoupMessage *msg; - /* TODO: if SoupOutputStream supported chunked requests, we could - * use a PUT with "If-Match: ..." and "Expect: 100-continue" - */ + msg = soup_message_new_from_uri (SOUP_METHOD_PUT, uri); - op_backend = G_VFS_BACKEND_HTTP (backend); - if (make_backup) - { - g_vfs_job_failed (G_VFS_JOB (job), - G_IO_ERROR, - G_IO_ERROR_CANT_CREATE_BACKUP, - _("Backup file creation failed")); - return TRUE; - } + soup_message_headers_set_expectations (msg->request_headers, + SOUP_EXPECTATION_CONTINUE); + + g_signal_connect (msg, "got-informational", + G_CALLBACK (put_got_informational), handle); + + g_signal_connect (msg, "wrote-chunk", + G_CALLBACK (put_wrote_chunk), handle); + soup_message_body_set_accumulate (msg->request_body, FALSE); + soup_message_headers_set_encoding (msg->request_headers, + SOUP_ENCODING_CHUNKED); + + return msg; +} +/* *** create () *** */ +static void +do_create (GVfsBackend *backend, + GVfsJobOpenForWrite *job, + const char *filename, + GFileCreateFlags flags) +{ + PutFileHandle *handle; + SoupMessage *msg; + SoupURI *uri; + gboolean can_conditional; uri = http_backend_uri_for_filename (backend, filename, FALSE); - if (etag) - { - SoupMessage *msg; + can_conditional = FALSE; /* XXX: need a way to detect server caps */ + if (can_conditional == FALSE) + { + guint status; msg = soup_message_new_from_uri (SOUP_METHOD_HEAD, uri); - soup_uri_free (uri); - soup_message_headers_append (msg->request_headers, "If-Match", etag); + status = g_vfs_backend_dav_send_message (backend, msg); + + g_object_unref (msg); - g_vfs_job_set_backend_data (G_VFS_JOB (job), op_backend, NULL); - soup_session_queue_message (op_backend->session, msg, - try_replace_checked_etag, job); - return TRUE; + if (SOUP_STATUS_IS_SUCCESSFUL (status)) + { + g_vfs_job_failed (G_VFS_JOB (job), G_IO_ERROR, + G_IO_ERROR_EXISTS, + _("Target file already exists")); + soup_uri_free (uri); + return; + } } + + handle = put_file_handle_new (G_VFS_JOB (job)); + handle->session = g_vfs_backend_dav_get_session (backend); + g_vfs_job_open_for_write_set_handle (job, handle); - open_for_replace_succeeded (op_backend, G_VFS_JOB (job), uri, NULL); - soup_uri_free (uri); - return TRUE; + msg = put_message_new (uri, handle); + + if (can_conditional == TRUE) + message_add_if_match_header (msg, "*", FALSE); + + g_vfs_backend_dav_queue_message (backend, msg, put_done, handle); } + -/* *** write () *** */ +/* *** replace () *** */ static void -write_ready (GObject *source_object, - GAsyncResult *result, - gpointer user_data) +do_replace (GVfsBackend *backend, + GVfsJobOpenForWrite *job, + const char *filename, + const char *etag, + gboolean make_backup, + GFileCreateFlags flags) { - GOutputStream *stream; - GVfsJob *job; + + PutFileHandle *handle; + SoupMessage *msg; + SoupURI *uri; + gboolean res; GError *error; - gssize nwrote; - stream = G_OUTPUT_STREAM (source_object); - error = NULL; - job = G_VFS_JOB (user_data); + error = NULL; + uri = http_backend_uri_for_filename (backend, filename, FALSE); - nwrote = g_output_stream_write_finish (stream, result, &error); + if (make_backup) + { + res = dav_make_backup (backend, uri, etag, &error); - if (nwrote < 0) - { - g_vfs_job_failed (G_VFS_JOB (job), - error->domain, - error->code, - error->message); + if (res == FALSE) + { + g_vfs_job_failed_from_error (G_VFS_JOB (job), error); + g_error_free (error); + return; + } + } - g_error_free (error); - return; - } - g_vfs_job_write_set_written_size (G_VFS_JOB_WRITE (job), nwrote); - g_vfs_job_succeeded (job); -} + handle = put_file_handle_new (G_VFS_JOB (job)); + handle->session = g_vfs_backend_dav_get_session (backend); + g_vfs_job_open_for_write_set_handle (job, handle); -static gboolean -try_write (GVfsBackend *backend, - GVfsJobWrite *job, - GVfsBackendHandle handle, - char *buffer, - gsize buffer_size) -{ - GOutputStream *stream; + msg = put_message_new (uri, handle); - stream = G_OUTPUT_STREAM (handle); + message_add_if_match_header (msg, etag, FALSE); - g_output_stream_write_async (stream, - buffer, - buffer_size, - G_PRIORITY_DEFAULT, - G_VFS_JOB (job)->cancellable, - write_ready, - job); - return TRUE; + g_vfs_backend_dav_queue_message (backend, msg, put_done, handle); } -/* *** close_write () *** */ +/* *** write () *** */ static void -close_write_ready (GObject *source_object, - GAsyncResult *result, - gpointer user_data) +do_write (GVfsBackend *backend, + GVfsJobWrite *job, + GVfsBackendHandle bhandle, + char *buffer, + gsize count) { - GOutputStream *stream; - GVfsJob *job; - GError *error; + PutFileHandle *handle; gboolean res; - + GError *error; + + handle = (PutFileHandle *) bhandle; error = NULL; - job = G_VFS_JOB (user_data); - stream = G_OUTPUT_STREAM (source_object); - res = g_output_stream_close_finish (stream, - result, - &error); + + res = put_file_handle_wirte (handle, + buffer, + count, + G_VFS_JOB (job)->cancellable, + &error); + if (res == FALSE) { - g_vfs_job_failed (G_VFS_JOB (job), - error->domain, - error->code, - error->message); - + g_vfs_job_failed_from_error (G_VFS_JOB (job), error); g_error_free (error); } else - g_vfs_job_succeeded (job); + { + g_vfs_job_write_set_written_size (G_VFS_JOB_WRITE (job), count); + g_vfs_job_succeeded (G_VFS_JOB (job)); + } - g_object_unref (stream); + return; } -static gboolean -try_close_write (GVfsBackend *backend, - GVfsJobCloseWrite *job, - GVfsBackendHandle handle) + +static void +do_close_write (GVfsBackend *backend, + GVfsJobCloseWrite *job, + GVfsBackendHandle _handle) { - GOutputStream *stream; + PutFileHandle *handle; + gboolean res; + GError *error; - stream = G_OUTPUT_STREAM (handle); + handle = (PutFileHandle *) _handle; - g_output_stream_close_async (stream, - G_PRIORITY_DEFAULT, + res = put_file_handle_close (handle, G_VFS_JOB (job)->cancellable, - close_write_ready, - job); + &error); - return TRUE; + if (res == FALSE) + { + g_vfs_job_failed_from_error (G_VFS_JOB (job), error); + g_error_free (error); + } + else + { + if (handle->etag) + g_vfs_job_close_write_set_etag (job, handle->etag); + g_vfs_job_succeeded (G_VFS_JOB (job)); + } + + put_file_handle_free (handle); } +/* *** make_directory () *** */ static void do_make_directory (GVfsBackend *backend, GVfsJobMakeDirectory *job, @@ -2033,6 +2328,7 @@ do_make_directory (GVfsBackend *backend, g_object_unref (msg); } +/* *** delete () **** */ static void do_delete (GVfsBackend *backend, GVfsJobDelete *job, @@ -2171,10 +2467,10 @@ g_vfs_backend_dav_class_init (GVfsBackendDavClass *klass) backend_class->try_query_info = NULL; backend_class->query_info = do_query_info; backend_class->enumerate = do_enumerate; - backend_class->try_create = try_create; - backend_class->try_replace = try_replace; - backend_class->try_write = try_write; - backend_class->try_close_write = try_close_write; + backend_class->create = do_create; + backend_class->replace = do_replace; + backend_class->write = do_write; + backend_class->close_write = do_close_write; backend_class->make_directory = do_make_directory; backend_class->delete = do_delete; backend_class->set_display_name = do_set_display_name;