Updated Spanish translation
[anjuta-git-plugin.git] / plugins / sourceview / anjuta-document-saver.c
blob8e5f3281da8a319e2c4bcd248dbafd89ac112f91
1 /*
2 * anjuta-document-saver.c
3 * This file is part of anjuta
5 * Copyright (C) 2005 - Paolo Borelli and Paolo Maggi
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 2 of the License, or
10 * (at your option) any later version.
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin Street, Fifth Floor,
20 * Boston, MA 02110-1301, USA.
24 * Modified by the anjuta Team, 2005. See the AUTHORS file for a
25 * list of people on the anjuta Team.
26 * See the ChangeLog files for a list of changes.
28 * $Id$
31 #ifdef HAVE_CONFIG_H
32 #include <config.h>
33 #endif
35 #include <sys/mman.h>
36 #include <sys/types.h>
37 #include <sys/stat.h>
38 #include <fcntl.h>
39 #include <string.h>
40 #include <errno.h>
42 #include <glib/gi18n.h>
43 #include <glib/gfileutils.h>
44 #include <libgnomevfs/gnome-vfs.h>
46 #include <libanjuta/anjuta-encodings.h>
47 #include <libanjuta/anjuta-convert.h>
49 #include "anjuta-document-saver.h"
50 #include "anjuta-marshal.h"
52 #define ANJUTA_DOCUMENT_SAVER_GET_PRIVATE(object)(G_TYPE_INSTANCE_GET_PRIVATE ((object), ANJUTA_TYPE_DOCUMENT_SAVER, AnjutaDocumentSaverPrivate))
54 struct _AnjutaDocumentSaverPrivate
56 AnjutaDocument *document;
58 gchar *uri;
59 const AnjutaEncoding *encoding;
61 AnjutaDocumentSaveFlags flags;
63 gboolean keep_backup;
64 gchar *backup_ext;
65 gboolean backups_in_curr_dir;
67 time_t doc_mtime;
68 gchar *mime_type; //CHECK use FileInfo instead?
70 GnomeVFSFileSize size;
71 GnomeVFSFileSize bytes_written;
73 /* temp data for local files */
74 gint fd;
75 gchar *local_path;
77 /* temp data for remote files */
78 GnomeVFSURI *vfs_uri;
79 GnomeVFSAsyncHandle *handle;
80 GnomeVFSAsyncHandle *info_handle;
81 gint tmpfd;
82 gchar *tmp_fname;
83 GnomeVFSFileInfo *orig_info; /* used to restore permissions */
85 GError *error;
88 G_DEFINE_TYPE(AnjutaDocumentSaver, anjuta_document_saver, G_TYPE_OBJECT)
90 /* Signals */
92 enum {
93 SAVING,
94 LAST_SIGNAL
97 static guint signals[LAST_SIGNAL] = { 0 };
99 static void
100 anjuta_document_saver_finalize (GObject *object)
102 AnjutaDocumentSaverPrivate *priv = ANJUTA_DOCUMENT_SAVER (object)->priv;
104 g_free (priv->uri);
106 if (priv->vfs_uri)
107 gnome_vfs_uri_unref (priv->vfs_uri);
109 g_free (priv->backup_ext);
111 g_free (priv->local_path);
112 g_free (priv->mime_type);
113 g_free (priv->tmp_fname);
115 if (priv->orig_info)
116 gnome_vfs_file_info_unref (priv->orig_info);
118 if (priv->error)
119 g_error_free (priv->error);
121 G_OBJECT_CLASS (anjuta_document_saver_parent_class)->finalize (object);
124 static void
125 anjuta_document_saver_class_init (AnjutaDocumentSaverClass *klass)
127 GObjectClass *object_class = G_OBJECT_CLASS (klass);
129 object_class->finalize = anjuta_document_saver_finalize;
131 signals[SAVING] =
132 g_signal_new ("saving",
133 G_OBJECT_CLASS_TYPE (object_class),
134 G_SIGNAL_RUN_LAST,
135 G_STRUCT_OFFSET (AnjutaDocumentSaverClass, saving),
136 NULL, NULL,
137 anjuta_marshal_VOID__BOOLEAN_POINTER,
138 G_TYPE_NONE,
140 G_TYPE_BOOLEAN,
141 G_TYPE_POINTER);
143 g_type_class_add_private (object_class, sizeof(AnjutaDocumentSaverPrivate));
146 static void
147 anjuta_document_saver_init (AnjutaDocumentSaver *saver)
149 saver->priv = ANJUTA_DOCUMENT_SAVER_GET_PRIVATE (saver);
151 saver->priv->fd = -1;
153 saver->priv->tmpfd = -1;
155 saver->priv->error = NULL;
158 AnjutaDocumentSaver *
159 anjuta_document_saver_new (AnjutaDocument *doc)
161 AnjutaDocumentSaver *saver;
163 g_return_val_if_fail (ANJUTA_IS_DOCUMENT (doc), NULL);
165 saver = ANJUTA_DOCUMENT_SAVER (g_object_new (ANJUTA_TYPE_DOCUMENT_SAVER, NULL));
167 saver->priv->document = doc;
169 return saver;
173 * Write the document contents in fd.
175 static gboolean
176 write_document_contents (gint fd,
177 GtkTextBuffer *doc,
178 const AnjutaEncoding *encoding,
179 GError **error)
181 GtkTextIter start_iter;
182 GtkTextIter end_iter;
183 gchar *contents;
184 gsize len;
185 gboolean add_cr;
186 ssize_t written;
187 gboolean res;
189 gtk_text_buffer_get_bounds (doc, &start_iter, &end_iter);
190 contents = gtk_text_buffer_get_slice (doc, &start_iter, &end_iter, TRUE);
192 len = strlen (contents);
194 if (len >= 1)
195 add_cr = (*(contents + len - 1) != '\n');
196 else
197 add_cr = FALSE;
199 if (encoding != anjuta_encoding_get_utf8 ())
201 gchar *converted_contents;
202 gsize new_len;
204 converted_contents = anjuta_convert_from_utf8 (contents,
205 len,
206 encoding,
207 &new_len,
208 error);
209 g_free (contents);
211 if (*error != NULL)
213 /* Conversion error */
214 return FALSE;
216 else
218 contents = converted_contents;
219 len = new_len;
223 /* make sure we are at the start */
224 res = (lseek (fd, 0, SEEK_SET) != -1);
226 /* Truncate the file to 0, in case it was not empty */
227 if (res)
228 res = (ftruncate (fd, 0) == 0);
230 /* Save the file content */
231 if (res)
233 written = write (fd, contents, len);
234 res = ((written != -1) && ((gsize) written == len));
237 /* Add \n at the end if needed */
238 if (res && add_cr)
240 if (encoding != anjuta_encoding_get_utf8 ())
242 gchar *converted_n = NULL;
243 gsize n_len;
245 converted_n = anjuta_convert_from_utf8 ("\n",
246 -1,
247 encoding,
248 &n_len,
249 NULL);
251 if (converted_n == NULL)
253 /* we do not error out for this */
254 g_warning ("Cannot add '\\n' at the end of the file.");
256 else
258 written = write (fd, converted_n, n_len);
259 res = ((written != -1) && ((gsize) written == n_len));
260 g_free (converted_n);
263 else
265 res = (write (fd, "\n", 1) == 1);
269 g_free (contents);
271 if (!res)
273 GnomeVFSResult result = gnome_vfs_result_from_errno ();
275 g_set_error (error,
276 ANJUTA_DOCUMENT_ERROR,
277 result,
278 "%s", gnome_vfs_result_to_string (result));
281 return res;
284 static void
285 save_completed_or_failed (AnjutaDocumentSaver *saver)
287 /* the object will be unrefed in the callback of the saving
288 * signal, so we need to prevent finalization.
290 g_object_ref (saver);
292 g_signal_emit (saver,
293 signals[SAVING],
295 TRUE, /* completed */
296 saver->priv->error);
298 g_object_unref (saver);
301 static gchar *
302 get_backup_filename (AnjutaDocumentSaver *saver)
304 gchar *fname;
305 gchar *bak_ext = NULL;
307 if ((saver->priv->backup_ext != NULL) &&
308 (strlen (saver->priv->backup_ext) > 0))
309 bak_ext = saver->priv->backup_ext;
310 else
311 bak_ext = g_strdup("~");
313 fname = g_strconcat (saver->priv->local_path, bak_ext, NULL);
315 /* If we are not going to keep the backup file and fname
316 * already exists, try to use another name.
317 * Change one character, just before the extension.
319 if (!saver->priv->keep_backup &&
320 g_file_test (fname, G_FILE_TEST_EXISTS))
322 gchar *wp;
324 wp = fname + strlen (fname) - 1 - strlen (bak_ext);
325 g_return_val_if_fail (wp > fname, NULL);
327 *wp = 'z';
328 while ((*wp > 'a') && g_file_test (fname, G_FILE_TEST_EXISTS))
329 --*wp;
331 /* They all exist??? Must be something wrong. */
332 if (*wp == 'a')
334 g_free (fname);
335 fname = NULL;
339 return fname;
342 /* like unlink, but doesn't fail if the file wasn't there at all */
343 static gboolean
344 remove_file (const gchar *name)
346 gint res;
348 res = unlink (name);
350 return (res == 0) || ((res == -1) && (errno == ENOENT));
353 #define BUFSIZE 8192 /* size of normal write buffer */
355 static gboolean
356 copy_file_data (gint sfd,
357 gint dfd,
358 GError **error)
360 gboolean ret = TRUE;
361 GError *err = NULL;
362 gpointer buffer;
363 const gchar *write_buffer;
364 ssize_t bytes_read;
365 ssize_t bytes_to_write;
366 ssize_t bytes_written;
368 buffer = g_malloc (BUFSIZE);
372 bytes_read = read (sfd, buffer, BUFSIZE);
373 if (bytes_read == -1)
375 GnomeVFSResult result = gnome_vfs_result_from_errno ();
377 g_set_error (&err,
378 ANJUTA_DOCUMENT_ERROR,
379 result,
380 "%s", gnome_vfs_result_to_string (result));
382 ret = FALSE;
384 break;
387 bytes_to_write = bytes_read;
388 write_buffer = buffer;
392 bytes_written = write (dfd, write_buffer, bytes_to_write);
393 if (bytes_written == -1)
395 GnomeVFSResult result = gnome_vfs_result_from_errno ();
397 g_set_error (&err,
398 ANJUTA_DOCUMENT_ERROR,
399 result,
400 "%s", gnome_vfs_result_to_string (result));
402 ret = FALSE;
404 break;
407 bytes_to_write -= bytes_written;
408 write_buffer += bytes_written;
410 while (bytes_to_write > 0);
412 } while ((bytes_read != 0) && (ret == TRUE));
414 if (error)
415 *error = err;
417 return ret;
420 /* FIXME: this is ugly for multple reasons: it refetches all the info,
421 * it doesn't use fd etc... we need something better, possibly in gnome-vfs
422 * public api.
424 static gchar *
425 get_slow_mime_type (const char *text_uri)
427 GnomeVFSFileInfo *info;
428 char *mime_type;
429 GnomeVFSResult result;
431 info = gnome_vfs_file_info_new ();
432 result = gnome_vfs_get_file_info (text_uri, info,
433 GNOME_VFS_FILE_INFO_GET_MIME_TYPE |
434 GNOME_VFS_FILE_INFO_FORCE_SLOW_MIME_TYPE |
435 GNOME_VFS_FILE_INFO_FOLLOW_LINKS);
436 if (info->mime_type == NULL || result != GNOME_VFS_OK) {
437 mime_type = NULL;
438 } else {
439 mime_type = g_strdup (info->mime_type);
441 gnome_vfs_file_info_unref (info);
443 return mime_type;
446 /* ----------- local files ----------- */
448 static gboolean
449 save_existing_local_file (AnjutaDocumentSaver *saver)
451 mode_t saved_umask;
452 struct stat statbuf;
453 struct stat new_statbuf;
454 gchar *backup_filename = NULL;
455 gboolean backup_created = FALSE;
457 if (fstat (saver->priv->fd, &statbuf) != 0)
459 GnomeVFSResult result = gnome_vfs_result_from_errno ();
461 g_set_error (&saver->priv->error,
462 ANJUTA_DOCUMENT_ERROR,
463 result,
464 "%s", gnome_vfs_result_to_string (result));
466 goto out;
469 /* not a regular file */
470 if (!S_ISREG (statbuf.st_mode))
472 if (S_ISDIR (statbuf.st_mode))
474 g_set_error (&saver->priv->error,
475 ANJUTA_DOCUMENT_ERROR,
476 GNOME_VFS_ERROR_IS_DIRECTORY,
477 "%s", gnome_vfs_result_to_string (GNOME_VFS_ERROR_IS_DIRECTORY));
479 else
481 g_set_error (&saver->priv->error,
482 ANJUTA_DOCUMENT_ERROR,
483 ANJUTA_DOCUMENT_ERROR_NOT_REGULAR_FILE,
484 "Not a regular file");
487 goto out;
490 /* check if the file is actually writable */
491 if ((statbuf.st_mode & 0222) == 0) //FIXME... check better what else vim does
493 g_set_error (&saver->priv->error,
494 ANJUTA_DOCUMENT_ERROR,
495 GNOME_VFS_ERROR_READ_ONLY,
496 "%s", gnome_vfs_result_to_string (GNOME_VFS_ERROR_READ_ONLY));
498 goto out;
501 /* check if someone else modified the file externally,
502 * except when "saving as", when saving a new doc (mtime = 0)
503 * or when the mtime check is explicitely disabled
505 if (saver->priv->doc_mtime > 0 &&
506 statbuf.st_mtime != saver->priv->doc_mtime &&
507 ((saver->priv->flags & ANJUTA_DOCUMENT_SAVE_IGNORE_MTIME) == 0))
509 g_set_error (&saver->priv->error,
510 ANJUTA_DOCUMENT_ERROR,
511 ANJUTA_DOCUMENT_ERROR_EXTERNALLY_MODIFIED,
512 "Externally modified");
514 goto out;
517 /* prepare the backup name */
518 backup_filename = get_backup_filename (saver);
519 if (backup_filename == NULL)
521 /* bad bad luck... */
522 g_warning (_("Could not obtain backup filename"));
524 g_set_error (&saver->priv->error,
525 ANJUTA_DOCUMENT_ERROR,
526 GNOME_VFS_ERROR_GENERIC,
527 "%s", gnome_vfs_result_to_string (GNOME_VFS_ERROR_GENERIC));
529 goto out;
532 /* We now use two backup strategies.
533 * The first one (which is faster) consist in saving to a
534 * tmp file then rename the original file to the backup and the
535 * tmp file to the original name. This is fast but doesn't work
536 * when the file is a link (hard or symbolic) or when we can't
537 * write to the current dir or can't set the permissions on the
538 * new file. We also do not use it when the backup is not in the
539 * current dir, since if it isn't on the same FS rename wont work.
540 * The second strategy consist simply in copying the old file
541 * to a backup file and rewrite the contents of the file.
544 if (saver->priv->backups_in_curr_dir &&
545 !(statbuf.st_nlink > 1) &&
546 !g_file_test (saver->priv->local_path, G_FILE_TEST_IS_SYMLINK))
548 gchar *dirname;
549 gchar *tmp_filename;
550 gint tmpfd;
552 dirname = g_path_get_dirname (saver->priv->local_path);
553 tmp_filename = g_build_filename (dirname, ".anjuta-save-XXXXXX", NULL);
554 g_free (dirname);
556 /* We set the umask because some (buggy) implementations
557 * of mkstemp() use permissions 0666 and we want 0600.
559 saved_umask = umask (0077);
560 tmpfd = g_mkstemp (tmp_filename);
561 umask (saved_umask);
563 if (tmpfd == -1)
565 g_free (tmp_filename);
566 goto fallback_strategy;
569 /* try to set permissions */
570 if (fchown (tmpfd, statbuf.st_uid, statbuf.st_gid) == -1 ||
571 fchmod (tmpfd, statbuf.st_mode) == -1)
573 close (tmpfd);
574 unlink (tmp_filename);
575 g_free (tmp_filename);
576 goto fallback_strategy;
579 if (!write_document_contents (tmpfd,
580 GTK_TEXT_BUFFER (saver->priv->document),
581 saver->priv->encoding,
582 &saver->priv->error))
584 close (tmpfd);
585 unlink (tmp_filename);
586 g_free (tmp_filename);
587 goto out;
590 /* original -> backup */
591 if (rename (saver->priv->local_path, backup_filename) != 0)
593 GnomeVFSResult result = gnome_vfs_result_from_errno ();
595 g_set_error (&saver->priv->error,
596 ANJUTA_DOCUMENT_ERROR,
597 result,
598 "%s", gnome_vfs_result_to_string (result));
600 close (tmpfd);
601 unlink (tmp_filename);
602 g_free (tmp_filename);
603 goto out;
606 /* tmp -> original */
607 if (rename (tmp_filename, saver->priv->local_path) != 0)
609 GnomeVFSResult result = gnome_vfs_result_from_errno ();
611 g_set_error (&saver->priv->error,
612 ANJUTA_DOCUMENT_ERROR,
613 result,
614 "%s", gnome_vfs_result_to_string (result));
616 /* try to restore... no error checking */
617 rename (backup_filename, saver->priv->local_path);
619 close (tmpfd);
620 unlink (tmp_filename);
621 g_free (tmp_filename);
622 goto out;
625 g_free (tmp_filename);
627 /* restat and get the mime type */
628 if (fstat (tmpfd, &new_statbuf) != 0)
630 GnomeVFSResult result = gnome_vfs_result_from_errno ();
632 g_set_error (&saver->priv->error,
633 ANJUTA_DOCUMENT_ERROR,
634 result,
635 "%s", gnome_vfs_result_to_string (result));
637 close (tmpfd);
638 goto out;
641 saver->priv->doc_mtime = new_statbuf.st_mtime;
643 saver->priv->mime_type = get_slow_mime_type (saver->priv->uri);
645 if (!saver->priv->keep_backup)
646 unlink (backup_filename);
648 close (tmpfd);
650 goto out;
653 fallback_strategy:
655 /* try to copy the old contents in a backup for safety
656 * unless we are explicetely told not to.
658 if ((saver->priv->flags & ANJUTA_DOCUMENT_SAVE_IGNORE_BACKUP) == 0)
660 gint bfd;
662 /* move away old backups */
663 if (!remove_file (backup_filename))
665 /* we don't care about which was the problem, just
666 * that a backup was not possible.
668 g_set_error (&saver->priv->error,
669 ANJUTA_DOCUMENT_ERROR,
670 ANJUTA_DOCUMENT_ERROR_CANT_CREATE_BACKUP,
671 "No backup created");
673 goto out;
676 bfd = open (backup_filename,
677 O_WRONLY | O_CREAT | O_EXCL,
678 statbuf.st_mode & 0777);
680 if (bfd == -1)
682 g_set_error (&saver->priv->error,
683 ANJUTA_DOCUMENT_ERROR,
684 ANJUTA_DOCUMENT_ERROR_CANT_CREATE_BACKUP,
685 "No backup created");
687 goto out;
690 /* Try to set the group of the backup same as the
691 * original file. If this fails, set the protection
692 * bits for the group same as the protection bits for
693 * others. */
694 if (fchown (bfd, (uid_t) -1, statbuf.st_gid) != 0)
696 if (fchmod (bfd,
697 (statbuf.st_mode& 0707) |
698 ((statbuf.st_mode & 07) << 3)) != 0)
700 g_set_error (&saver->priv->error,
701 ANJUTA_DOCUMENT_ERROR,
702 ANJUTA_DOCUMENT_ERROR_CANT_CREATE_BACKUP,
703 "No backup created");
705 unlink (backup_filename);
706 close (bfd);
708 goto out;
712 if (!copy_file_data (saver->priv->fd, bfd, NULL))
714 g_set_error (&saver->priv->error,
715 ANJUTA_DOCUMENT_ERROR,
716 ANJUTA_DOCUMENT_ERROR_CANT_CREATE_BACKUP,
717 "No backup created");
719 unlink (backup_filename);
720 close (bfd);
722 goto out;
725 backup_created = TRUE;
726 close (bfd);
729 /* finally overwrite the original */
730 if (!write_document_contents (saver->priv->fd,
731 GTK_TEXT_BUFFER (saver->priv->document),
732 saver->priv->encoding,
733 &saver->priv->error))
735 goto out;
738 /* remove the backup if we don't want to keep it */
739 if (backup_created && !saver->priv->keep_backup)
741 unlink (backup_filename);
744 /* re stat the file and refetch the mime type */
745 if (fstat (saver->priv->fd, &new_statbuf) != 0)
747 GnomeVFSResult result = gnome_vfs_result_from_errno ();
749 g_set_error (&saver->priv->error,
750 ANJUTA_DOCUMENT_ERROR,
751 result,
752 "%s", gnome_vfs_result_to_string (result));
754 goto out;
757 saver->priv->doc_mtime = new_statbuf.st_mtime;
759 g_free (saver->priv->mime_type);
760 saver->priv->mime_type = get_slow_mime_type (saver->priv->uri);
762 out:
763 if (close (saver->priv->fd))
764 g_warning ("File '%s' has not been correctly closed: %s",
765 saver->priv->uri,
766 strerror (errno));
767 saver->priv->fd = -1;
769 g_free (backup_filename);
771 save_completed_or_failed (saver);
773 /* stop the timeout */
774 return FALSE;
777 static gboolean
778 save_new_local_file (AnjutaDocumentSaver *saver)
780 struct stat statbuf;
782 if (!write_document_contents (saver->priv->fd,
783 GTK_TEXT_BUFFER (saver->priv->document),
784 saver->priv->encoding,
785 &saver->priv->error))
787 goto out;
790 /* stat the file and fetch the mime type */
791 if (fstat (saver->priv->fd, &statbuf) != 0)
793 GnomeVFSResult result = gnome_vfs_result_from_errno ();
795 g_set_error (&saver->priv->error,
796 ANJUTA_DOCUMENT_ERROR,
797 result,
798 "%s", gnome_vfs_result_to_string (result));
800 goto out;
803 saver->priv->doc_mtime = statbuf.st_mtime;
805 g_free (saver->priv->mime_type);
806 saver->priv->mime_type = get_slow_mime_type (saver->priv->uri);
808 out:
809 if (close (saver->priv->fd))
810 g_warning ("File '%s' has not been correctly closed: %s",
811 saver->priv->uri,
812 strerror (errno));
814 saver->priv->fd = -1;
816 save_completed_or_failed (saver);
818 /* stop the timeout */
819 return FALSE;
822 static gboolean
823 open_local_failed (AnjutaDocumentSaver *saver)
825 save_completed_or_failed (saver);
827 /* stop the timeout */
828 return FALSE;
831 static void
832 save_local_file (AnjutaDocumentSaver *saver)
834 GSourceFunc next_phase;
835 GnomeVFSResult result;
837 /* saving start */
838 g_signal_emit (saver,
839 signals[SAVING],
841 FALSE,
842 NULL);
844 /* the file doesn't exist, create it */
845 saver->priv->fd = open (saver->priv->local_path,
846 O_CREAT | O_EXCL | O_WRONLY,
847 0666);
848 if (saver->priv->fd != -1)
850 next_phase = (GSourceFunc) save_new_local_file;
851 goto out;
854 /* the file already exist */
855 else if (errno == EEXIST)
857 saver->priv->fd = open (saver->priv->local_path, O_RDWR);
858 if (saver->priv->fd != -1)
860 next_phase = (GSourceFunc) save_existing_local_file;
861 goto out;
865 /* else error */
866 result = gnome_vfs_result_from_errno (); //may it happen that no errno?
868 g_set_error (&saver->priv->error,
869 ANJUTA_DOCUMENT_ERROR,
870 result,
871 "%s", gnome_vfs_result_to_string (result));
873 next_phase = (GSourceFunc) open_local_failed;
875 out:
876 g_timeout_add_full (G_PRIORITY_HIGH,
878 next_phase,
879 saver,
880 NULL);
883 /* ----------- remote files ----------- */
885 static void
886 remote_save_completed_or_failed (AnjutaDocumentSaver *saver)
888 /* we can now close and unlink the tmp file */
889 close (saver->priv->tmpfd);
890 unlink (saver->priv->tmp_fname);
892 save_completed_or_failed (saver);
895 static void
896 remote_get_info_cb (GnomeVFSAsyncHandle *handle,
897 GList *results,
898 gpointer data)
900 AnjutaDocumentSaver *saver = ANJUTA_DOCUMENT_SAVER (data);
901 GnomeVFSGetFileInfoResult *info_result;
903 /* assert that the list has one and only one item */
904 g_return_if_fail (results != NULL && results->next == NULL);
906 info_result = (GnomeVFSGetFileInfoResult *) results->data;
907 g_return_if_fail (info_result != NULL);
909 if (info_result->result != GNOME_VFS_OK)
911 g_set_error (&saver->priv->error,
912 ANJUTA_DOCUMENT_ERROR,
913 info_result->result,
914 "%s", gnome_vfs_result_to_string (info_result->result));
916 remote_save_completed_or_failed (saver);
918 return;
921 if (info_result->file_info->valid_fields & GNOME_VFS_FILE_INFO_FIELDS_MTIME)
922 saver->priv->doc_mtime = info_result->file_info->mtime;
924 if (info_result->file_info->valid_fields & GNOME_VFS_FILE_INFO_FIELDS_MIME_TYPE)
926 g_free (saver->priv->mime_type);
927 saver->priv->mime_type = g_strdup (info_result->file_info->mime_type);
930 remote_save_completed_or_failed (saver);
933 static gint
934 async_xfer_ok (GnomeVFSXferProgressInfo *progress_info,
935 AnjutaDocumentSaver *saver)
937 switch (progress_info->phase)
939 case GNOME_VFS_XFER_PHASE_INITIAL:
940 break;
941 case GNOME_VFS_XFER_CHECKING_DESTINATION:
943 GnomeVFSFileInfo *orig_info;
944 GnomeVFSResult res;
946 /* we need to retrieve info ourselves too, since xfer
947 * doesn't allow to access it :(
948 * If that was not enough we need to do it sync or we are going
949 * to mess everything up
951 orig_info = gnome_vfs_file_info_new ();
952 res = gnome_vfs_get_file_info_uri (saver->priv->vfs_uri,
953 orig_info,
954 GNOME_VFS_FILE_INFO_DEFAULT |
955 GNOME_VFS_FILE_INFO_FOLLOW_LINKS);
957 if (res == GNOME_VFS_ERROR_NOT_FOUND)
959 /* ok, we are not overwriting, go on with the xfer */
960 break;
963 if (res != GNOME_VFS_OK)
965 // CHECK: do we want to ignore the error and try to go on anyway?
966 g_set_error (&saver->priv->error,
967 ANJUTA_DOCUMENT_ERROR,
968 res,
969 "%s", gnome_vfs_result_to_string (res));
971 /* abort xfer */
972 return 0;
976 /* check if someone else modified the file externally,
977 * except when "saving as", when saving a new doc (mtime = 0)
978 * or when the mtime check is explicitely disabled
980 if (orig_info->valid_fields & GNOME_VFS_FILE_INFO_FIELDS_MTIME)
982 if (saver->priv->doc_mtime > 0 &&
983 orig_info->mtime != saver->priv->doc_mtime &&
984 ((saver->priv->flags & ANJUTA_DOCUMENT_SAVE_IGNORE_MTIME) == 0))
986 g_set_error (&saver->priv->error,
987 ANJUTA_DOCUMENT_ERROR,
988 ANJUTA_DOCUMENT_ERROR_EXTERNALLY_MODIFIED,
989 "Externally modified");
991 /* abort xfer */
992 return 0;
996 /* store the original file info, so that we can restore permissions */
997 // FIXME: what about the case where we are usin "Save as" but overwriting a file... we don't want to restore perms
998 if (orig_info->valid_fields & GNOME_VFS_FILE_INFO_FIELDS_PERMISSIONS)
999 saver->priv->orig_info = orig_info;
1001 break;
1002 case GNOME_VFS_XFER_PHASE_COLLECTING:
1003 case GNOME_VFS_XFER_PHASE_DELETESOURCE: // why do we get this phase??
1004 break;
1005 case GNOME_VFS_XFER_PHASE_READYTOGO:
1006 saver->priv->size = progress_info->bytes_total;
1007 break;
1008 case GNOME_VFS_XFER_PHASE_OPENSOURCE:
1009 case GNOME_VFS_XFER_PHASE_OPENTARGET:
1010 case GNOME_VFS_XFER_PHASE_COPYING:
1011 case GNOME_VFS_XFER_PHASE_WRITETARGET:
1012 case GNOME_VFS_XFER_PHASE_CLOSETARGET:
1013 if (progress_info->bytes_copied > 0)
1014 saver->priv->bytes_written = MIN (progress_info->total_bytes_copied,
1015 progress_info->bytes_total);
1016 break;
1017 case GNOME_VFS_XFER_PHASE_FILECOMPLETED:
1018 case GNOME_VFS_XFER_PHASE_CLEANUP:
1019 break;
1020 case GNOME_VFS_XFER_PHASE_COMPLETED:
1021 /* Transfer done!
1022 * Restore the permissions if needed and then refetch
1023 * info on our newly written file to get the mime etc */
1025 GList *uri_list = NULL;
1027 /* Try is not as paranoid as the local version (GID)... it would take
1028 * yet another stat to do it...
1030 if (saver->priv->orig_info != NULL &&
1031 (saver->priv->orig_info->valid_fields & GNOME_VFS_FILE_INFO_FIELDS_PERMISSIONS))
1033 gnome_vfs_set_file_info_uri (saver->priv->vfs_uri,
1034 saver->priv->orig_info,
1035 GNOME_VFS_SET_FILE_INFO_PERMISSIONS);
1037 // FIXME: for now is a blind try... do we want to error check?
1040 uri_list = g_list_prepend (uri_list, saver->priv->vfs_uri);
1042 gnome_vfs_async_get_file_info (&saver->priv->info_handle,
1043 uri_list,
1044 GNOME_VFS_FILE_INFO_DEFAULT |
1045 GNOME_VFS_FILE_INFO_GET_MIME_TYPE |
1046 GNOME_VFS_FILE_INFO_FORCE_SLOW_MIME_TYPE |
1047 GNOME_VFS_FILE_INFO_FOLLOW_LINKS,
1048 GNOME_VFS_PRIORITY_MAX,
1049 remote_get_info_cb,
1050 saver);
1051 g_list_free (uri_list);
1053 break;
1054 /* Phases we don't expect to see */
1055 case GNOME_VFS_XFER_PHASE_SETATTRIBUTES:
1056 case GNOME_VFS_XFER_PHASE_CLOSESOURCE:
1057 case GNOME_VFS_XFER_PHASE_MOVING:
1058 case GNOME_VFS_XFER_PHASE_READSOURCE:
1059 default:
1060 g_return_val_if_reached (0);
1063 /* signal the progress */
1064 g_signal_emit (saver,
1065 signals[SAVING],
1067 FALSE,
1068 NULL);
1070 return 1;
1073 static gint
1074 async_xfer_error (GnomeVFSXferProgressInfo *progress_info,
1075 AnjutaDocumentSaver *saver)
1077 g_set_error (&saver->priv->error,
1078 ANJUTA_DOCUMENT_ERROR,
1079 progress_info->vfs_status,
1080 "%s", gnome_vfs_result_to_string (progress_info->vfs_status));
1082 remote_save_completed_or_failed (saver);
1084 return GNOME_VFS_XFER_ERROR_ACTION_ABORT;
1087 static gint
1088 async_xfer_progress (GnomeVFSAsyncHandle *handle,
1089 GnomeVFSXferProgressInfo *progress_info,
1090 gpointer data)
1092 AnjutaDocumentSaver *saver = ANJUTA_DOCUMENT_SAVER (data);
1094 switch (progress_info->status)
1096 case GNOME_VFS_XFER_PROGRESS_STATUS_OK:
1097 return async_xfer_ok (progress_info, saver);
1098 case GNOME_VFS_XFER_PROGRESS_STATUS_VFSERROR:
1099 return async_xfer_error (progress_info, saver);
1101 /* we should never go in these */
1102 case GNOME_VFS_XFER_PROGRESS_STATUS_OVERWRITE:
1103 case GNOME_VFS_XFER_PROGRESS_STATUS_DUPLICATE:
1104 default:
1105 g_return_val_if_reached (0);
1109 static gboolean
1110 save_remote_file_real (AnjutaDocumentSaver *saver)
1112 mode_t saved_umask;
1113 gchar *tmp_uri;
1114 GnomeVFSURI *tmp_vfs_uri;
1115 GList *source_uri_list = NULL;
1116 GList *dest_uri_list = NULL;
1117 GnomeVFSResult result;
1119 /* For remote files we use the following strategy:
1120 * we save to a local temp file and then transfer it
1121 * over to the requested location asyncronously.
1122 * There is no backup of the original remote file.
1125 /* We set the umask because some (buggy) implementations
1126 * of mkstemp() use permissions 0666 and we want 0600.
1128 saved_umask = umask (0077);
1129 saver->priv->tmpfd = g_file_open_tmp (".anjuta-save-XXXXXX",
1130 &saver->priv->tmp_fname,
1131 &saver->priv->error);
1132 umask (saved_umask);
1134 if (saver->priv->tmpfd == -1)
1136 GnomeVFSResult result = gnome_vfs_result_from_errno ();
1138 g_set_error (&saver->priv->error,
1139 ANJUTA_DOCUMENT_ERROR,
1140 result,
1141 "%s", gnome_vfs_result_to_string (result));
1143 /* in this case no need to close the tmp file */
1144 save_completed_or_failed (saver);
1146 return FALSE;
1149 tmp_uri = g_filename_to_uri (saver->priv->tmp_fname,
1150 NULL,
1151 &saver->priv->error);
1152 if (tmp_uri == NULL)
1154 goto error;
1157 tmp_vfs_uri = gnome_vfs_uri_new (tmp_uri);
1158 //needs error checking?
1160 g_free (tmp_uri);
1162 source_uri_list = g_list_prepend (source_uri_list, tmp_vfs_uri);
1163 dest_uri_list = g_list_prepend (dest_uri_list, saver->priv->vfs_uri);
1165 if (!write_document_contents (saver->priv->tmpfd,
1166 GTK_TEXT_BUFFER (saver->priv->document),
1167 saver->priv->encoding,
1168 &saver->priv->error))
1170 goto error;
1173 result = gnome_vfs_async_xfer (&saver->priv->handle,
1174 source_uri_list,
1175 dest_uri_list,
1176 GNOME_VFS_XFER_DEFAULT | GNOME_VFS_XFER_TARGET_DEFAULT_PERMS, // CHECK needs more thinking, follow symlinks etc... options are undocumented :(
1177 GNOME_VFS_XFER_ERROR_MODE_ABORT, /* keep it simple, abort on any error */
1178 GNOME_VFS_XFER_OVERWRITE_MODE_REPLACE, /* We have already asked confirm (even if it is racy) */
1179 GNOME_VFS_PRIORITY_DEFAULT,
1180 async_xfer_progress, saver,
1181 NULL, NULL);
1183 gnome_vfs_uri_unref (tmp_vfs_uri);
1184 g_list_free (source_uri_list);
1185 g_list_free (dest_uri_list);
1187 if (result != GNOME_VFS_OK)
1189 g_set_error (&saver->priv->error,
1190 ANJUTA_DOCUMENT_ERROR,
1191 result,
1192 "%s", gnome_vfs_result_to_string (result));
1194 goto error;
1197 /* No errors: stop the timeout */
1198 return FALSE;
1200 error:
1201 remote_save_completed_or_failed (saver);
1203 /* stop the timeout */
1204 return FALSE;
1207 static void
1208 save_remote_file (AnjutaDocumentSaver *saver)
1210 /* saving start */
1211 g_signal_emit (saver,
1212 signals[SAVING],
1214 FALSE,
1215 NULL);
1217 g_timeout_add_full (G_PRIORITY_HIGH,
1219 (GSourceFunc) save_remote_file_real,
1220 saver,
1221 NULL);
1224 /* ---------- public api ---------- */
1226 void
1227 anjuta_document_saver_save (AnjutaDocumentSaver *saver,
1228 const gchar *uri,
1229 const AnjutaEncoding *encoding,
1230 time_t oldmtime,
1231 AnjutaDocumentSaveFlags flags)
1233 gchar *local_path;
1235 g_return_if_fail (ANJUTA_IS_DOCUMENT_SAVER (saver));
1236 g_return_if_fail ((uri != NULL) && (strlen (uri) > 0));
1238 // CHECK:
1239 // - sanity check a max len for the uri?
1240 // report async (in an idle handler) or sync (bool ret)
1241 // async is extra work here, sync is special casing in the caller
1243 saver->priv->uri = g_strdup (uri);
1245 /* fetch saving options */
1246 saver->priv->backup_ext = g_strdup("~");
1248 /* never keep backup of autosaves */
1249 if ((flags & ANJUTA_DOCUMENT_SAVE_PRESERVE_BACKUP) != 0)
1250 saver->priv->keep_backup = FALSE;
1251 else
1252 /* FIXME: This should be configurable */
1253 saver->priv->keep_backup =TRUE;
1255 /* TODO: add support for configurable backup dir */
1256 saver->priv->backups_in_curr_dir = TRUE;
1258 if (encoding != NULL)
1259 saver->priv->encoding = encoding;
1260 else
1261 saver->priv->encoding = anjuta_encoding_get_utf8 ();
1263 saver->priv->doc_mtime = oldmtime;
1265 saver->priv->flags = flags;
1267 local_path = gnome_vfs_get_local_path_from_uri (uri);
1268 if (local_path != NULL)
1270 saver->priv->local_path = local_path;
1271 save_local_file (saver);
1273 else
1275 saver->priv->vfs_uri = gnome_vfs_uri_new (uri);
1276 save_remote_file (saver);
1280 const gchar *
1281 anjuta_document_saver_get_uri (AnjutaDocumentSaver *saver)
1283 g_return_val_if_fail (ANJUTA_IS_DOCUMENT_SAVER (saver), NULL);
1285 return saver->priv->uri;
1288 const gchar *
1289 anjuta_document_saver_get_mime_type (AnjutaDocumentSaver *saver)
1291 g_return_val_if_fail (ANJUTA_IS_DOCUMENT_SAVER (saver), NULL);
1293 return saver->priv->mime_type;
1296 time_t
1297 anjuta_document_saver_get_mtime (AnjutaDocumentSaver *saver)
1299 g_return_val_if_fail (ANJUTA_IS_DOCUMENT_SAVER (saver), 0);
1301 return saver->priv->doc_mtime;
1304 /* Returns 0 if file size is unknown */
1305 GnomeVFSFileSize
1306 anjuta_document_saver_get_file_size (AnjutaDocumentSaver *saver)
1308 g_return_val_if_fail (ANJUTA_IS_DOCUMENT_SAVER (saver), 0);
1310 return saver->priv->size;
1313 GnomeVFSFileSize
1314 anjuta_document_saver_get_bytes_written (AnjutaDocumentSaver *saver)
1316 g_return_val_if_fail (ANJUTA_IS_DOCUMENT_SAVER (saver), 0);
1318 return saver->priv->bytes_written;