medit/moo/mooedit/mooeditfileops.c

781 lines
23 KiB
C

/*
* mooedit/mooeditfileops.c
*
* Copyright (C) 2004-2005 by Yevgen Muntyan <muntyan@math.tamu.edu>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* See COPYING file that comes with this distribution.
*/
#define MOOEDIT_COMPILATION
#include "mooedit/mooedit-private.h"
#include "mooedit/mooeditdialogs.h"
#include "mooedit/mooeditlangmgr.h"
#include "mooutils/moofileutils.h"
#include <string.h>
static void block_buffer_signals (MooEdit *edit);
static void unblock_buffer_signals (MooEdit *edit);
static void start_file_watch (MooEdit *edit);
static void stop_file_watch (MooEdit *edit);
static gboolean file_watch_timeout_func (MooEdit *edit);
static gboolean focus_in_cb (MooEdit *edit);
static gboolean check_file_status (MooEdit *edit,
gboolean in_focus_only);
static void file_modified_on_disk (MooEdit *edit);
static void file_deleted (MooEdit *edit);
static void file_watch_error (MooEdit *edit);
static void file_watch_access_denied (MooEdit *edit);
gboolean _moo_edit_open (MooEdit *edit,
const char *file,
const char *encoding)
{
MooEditFileInfo *info = NULL;
GError *err = NULL;
gboolean undo;
g_return_val_if_fail (MOO_IS_EDIT (edit), FALSE);
if (!moo_edit_get_clean (edit) && moo_edit_get_modified (edit)) {
int resp = moo_edit_save_changes_dialog (edit);
if (resp == GTK_RESPONSE_YES) {
if (!moo_edit_save (edit))
return FALSE;
}
else if (resp == GTK_RESPONSE_CANCEL)
return FALSE;
}
if (!file || !file[0]) {
info = moo_edit_open_dialog (GTK_WIDGET (edit));
if (!info)
return FALSE;
file = info->filename;
encoding = info->encoding;
}
block_buffer_signals (edit);
if (moo_edit_is_empty (edit))
undo = FALSE;
else
undo = TRUE;
if (undo)
gtk_text_buffer_begin_user_action (edit->priv->text_buffer);
else
gtk_source_buffer_begin_not_undoable_action (edit->priv->source_buffer);
{
GtkTextIter start, end;
gtk_text_buffer_get_bounds (edit->priv->text_buffer, &start, &end);
gtk_text_buffer_delete (edit->priv->text_buffer, &start, &end);
}
if (!moo_edit_load (edit, file, encoding, &err)) {
if (err) {
moo_edit_open_error_dialog (GTK_WIDGET (edit), err->message);
g_error_free (err);
}
else
moo_edit_open_error_dialog (GTK_WIDGET (edit), NULL);
moo_edit_file_info_free (info);
edit->priv->status = 0;
stop_file_watch (edit);
unblock_buffer_signals (edit);
gtk_text_buffer_end_user_action (edit->priv->text_buffer);
moo_edit_set_modified (edit, FALSE);
return FALSE;
}
{
GtkTextIter start;
gtk_text_buffer_get_start_iter (edit->priv->text_buffer, &start);
gtk_text_buffer_place_cursor (edit->priv->text_buffer, &start);
}
unblock_buffer_signals (edit);
edit->priv->status = 0;
_moo_edit_set_filename (edit, file, encoding);
moo_edit_set_modified (edit, FALSE);
start_file_watch (edit);
moo_edit_file_info_free (info);
if (undo)
gtk_text_buffer_end_user_action (edit->priv->text_buffer);
else
gtk_source_buffer_end_not_undoable_action (edit->priv->source_buffer);
return TRUE;
}
gboolean _moo_edit_save (MooEdit *edit)
{
GError *err = NULL;
g_return_val_if_fail (MOO_IS_EDIT (edit), FALSE);
if (!edit->priv->filename)
return moo_edit_save_as (edit, NULL, NULL);
if (edit->priv->status & MOO_EDIT_DOC_MODIFIED_ON_DISK) {
if (!moo_edit_overwrite_modified_dialog (edit))
return FALSE;
}
else if (edit->priv->status & MOO_EDIT_DOC_DELETED) {
if (!moo_edit_overwrite_deleted_dialog (edit))
return FALSE;
}
if (!moo_edit_write (edit, edit->priv->filename,
edit->priv->encoding, &err))
{
if (err) {
moo_edit_save_error_dialog (GTK_WIDGET (edit), err->message);
g_error_free (err);
}
else
moo_edit_save_error_dialog (GTK_WIDGET (edit), NULL);
return FALSE;
}
edit->priv->status = 0;
moo_edit_set_modified (edit, FALSE);
start_file_watch (edit);
return TRUE;
}
gboolean _moo_edit_save_as (MooEdit *edit,
const char *file,
const char *encoding)
{
GError *err = NULL;
MooEditFileInfo *info = NULL;
g_return_val_if_fail (MOO_IS_EDIT (edit), FALSE);
if (!file || !file[0]) {
info = moo_edit_save_as_dialog (edit);
if (!info)
return FALSE;
file = info->filename;
encoding = info->encoding;
}
if (!moo_edit_write (edit, file, encoding, &err))
{
if (err) {
moo_edit_save_error_dialog (GTK_WIDGET (edit), err->message);
g_error_free (err);
}
else
moo_edit_save_error_dialog (GTK_WIDGET (edit), NULL);
moo_edit_file_info_free (info);
return FALSE;
}
_moo_edit_set_filename (edit, file, encoding);
edit->priv->status = 0;
moo_edit_set_modified (edit, FALSE);
start_file_watch (edit);
moo_edit_file_info_free (info);
return TRUE;
}
gboolean _moo_edit_close (MooEdit *edit)
{
int response;
if (moo_edit_get_clean (edit) || !moo_edit_get_modified (edit))
return TRUE;
response = moo_edit_save_changes_dialog (edit);
if (response == GTK_RESPONSE_YES) {
if (!moo_edit_save (edit))
return FALSE;
}
else if (response == GTK_RESPONSE_CANCEL)
return FALSE;
return TRUE;
}
gboolean _moo_edit_reload (MooEdit *edit,
GError **error)
{
GtkTextIter start, end;
g_return_val_if_fail (edit->priv->filename != NULL, FALSE);
// if (!edit->priv->filename)
// return FALSE;
if (!moo_edit_get_clean (edit) && moo_edit_get_modified (edit))
if (!moo_edit_reload_dialog (edit))
return FALSE;
block_buffer_signals (edit);
gtk_text_buffer_begin_user_action (edit->priv->text_buffer);
gtk_text_buffer_get_bounds (edit->priv->text_buffer,
&start, &end);
gtk_text_buffer_delete (edit->priv->text_buffer,
&start, &end);
if (!moo_edit_load (edit, edit->priv->filename,
edit->priv->encoding, error))
{
gtk_text_buffer_end_user_action (edit->priv->text_buffer);
unblock_buffer_signals (edit);
return FALSE;
}
else {
gtk_text_buffer_end_user_action (edit->priv->text_buffer);
edit->priv->status = 0;
unblock_buffer_signals (edit);
moo_edit_set_modified (edit, FALSE);
start_file_watch (edit);
return TRUE;
}
}
void _moo_edit_set_filename (MooEdit *edit,
const char *file,
const char *encoding)
{
MooEditLang *lang;
g_free (edit->priv->filename);
g_free (edit->priv->basename);
g_free (edit->priv->display_filename);
g_free (edit->priv->display_basename);
if (!file)
{
edit->priv->filename = NULL;
edit->priv->basename = NULL;
/* TODO TODO */
edit->priv->display_filename = g_strdup ("Untitled");
edit->priv->display_basename = g_strdup ("Untitled");
}
else
{
GError *err = NULL;
edit->priv->filename = g_strdup (file);
edit->priv->basename = g_path_get_basename (file);
edit->priv->display_filename =
g_filename_to_utf8 (file, -1, NULL, NULL, &err);
if (!edit->priv->display_filename)
{
g_critical ("%s: could not convert filename to utf8", G_STRLOC);
if (err) {
g_critical ("%s: %s", G_STRLOC, err->message);
g_error_free (err);
err = NULL;
}
edit->priv->display_filename = g_strdup ("<Unknown>");
}
edit->priv->display_basename =
g_filename_to_utf8 (edit->priv->basename, -1, NULL, NULL, &err);
if (!edit->priv->display_basename)
{
g_critical ("%s: could not convert filename to utf8", G_STRLOC);
if (err) {
g_critical ("%s: %s", G_STRLOC, err->message);
g_error_free (err);
err = NULL;
}
edit->priv->display_basename = g_strdup ("<Unknown>");
}
}
if (!edit->priv->lang_custom)
{
if (file)
lang = moo_edit_lang_mgr_get_language_for_file (_moo_edit_get_lang_mgr (edit), file);
else
lang = moo_edit_lang_mgr_get_default_language (_moo_edit_get_lang_mgr (edit));
moo_edit_set_lang (edit, lang);
}
g_free (edit->priv->encoding);
edit->priv->encoding = g_strdup (encoding);
g_signal_emit_by_name (edit, "filename-changed", edit->priv->filename, NULL);
g_signal_emit_by_name (edit, "doc-status-changed", NULL);
}
static void block_buffer_signals (MooEdit *edit)
{
g_signal_handler_block (edit->priv->source_buffer,
edit->priv->can_undo_handler_id);
g_signal_handler_block (edit->priv->source_buffer,
edit->priv->can_redo_handler_id);
g_signal_handler_block (edit->priv->source_buffer,
edit->priv->modified_changed_handler_id);
}
static void unblock_buffer_signals (MooEdit *edit)
{
g_signal_handler_unblock (edit->priv->source_buffer,
edit->priv->can_undo_handler_id);
g_signal_handler_unblock (edit->priv->source_buffer,
edit->priv->can_redo_handler_id);
g_signal_handler_unblock (edit->priv->source_buffer,
edit->priv->modified_changed_handler_id);
}
static void start_file_watch (MooEdit *edit)
{
GTime timestamp;
if (edit->priv->file_watch_id)
g_source_remove (edit->priv->file_watch_id);
edit->priv->file_watch_id = 0;
g_return_if_fail ((edit->priv->status & MOO_EDIT_DOC_CHANGED_ON_DISK) == 0);
g_return_if_fail (edit->priv->filename != NULL);
timestamp = moo_get_file_mtime (edit->priv->filename);
g_return_if_fail (timestamp > 0); /* TODO TODO */
edit->priv->timestamp = timestamp;
if (edit->priv->file_watch_timeout)
edit->priv->file_watch_id =
g_timeout_add (edit->priv->file_watch_timeout,
(GSourceFunc) file_watch_timeout_func,
edit);
if (!edit->priv->focus_in_handler_id)
edit->priv->focus_in_handler_id =
g_signal_connect (edit, "focus-in-event",
G_CALLBACK (focus_in_cb),
NULL);
}
static void stop_file_watch (MooEdit *edit)
{
if (edit->priv->file_watch_id)
g_source_remove (edit->priv->file_watch_id);
edit->priv->file_watch_id = 0;
if (edit->priv->focus_in_handler_id) {
g_signal_handler_disconnect (edit, edit->priv->focus_in_handler_id);
edit->priv->focus_in_handler_id = 0;
}
}
static gboolean file_watch_timeout_func (MooEdit *edit)
{
return check_file_status (edit, TRUE);
}
static gboolean focus_in_cb (MooEdit *edit)
{
check_file_status (edit, TRUE);
return FALSE;
}
static gboolean check_file_status (MooEdit *edit,
gboolean in_focus_only)
{
GTime stamp;
if (in_focus_only && !GTK_WIDGET_HAS_FOCUS (edit))
return TRUE;
g_return_val_if_fail (edit->priv->filename != NULL, FALSE);
g_return_val_if_fail ((edit->priv->status & MOO_EDIT_DOC_CHANGED_ON_DISK) == 0, FALSE);
g_return_val_if_fail (edit->priv->timestamp > 0, FALSE);
g_return_val_if_fail (edit->priv->filename != NULL, FALSE);
stamp = moo_get_file_mtime (edit->priv->filename);
if (stamp < 0) {
switch (stamp) {
case MOO_EACCESS:
file_watch_access_denied (edit);
return FALSE;
case MOO_ENOENT:
file_deleted (edit);
return FALSE;
case MOO_EIO:
default:
g_critical ("%s: error in moo_get_file_mtime", G_STRLOC);
file_watch_error (edit);
return FALSE;
}
}
if (stamp == edit->priv->timestamp)
return TRUE;
if (stamp < edit->priv->timestamp) {
g_warning ("%s: mtime of file on disk is less than timestamp", G_STRLOC);
file_watch_error (edit);
return FALSE;
}
file_modified_on_disk (edit);
return FALSE;
}
static void add_status (MooEdit *edit,
MooEditDocStatus s)
{
edit->priv->status |= s;
g_signal_emit_by_name (edit, "doc-status-changed", NULL);
}
static void ask_reload_or_not (MooEdit *edit)
{
switch (moo_edit_file_modified_on_disk_dialog (edit)) {
case GTK_RESPONSE_CANCEL:
add_status (edit, MOO_EDIT_DOC_MODIFIED_ON_DISK);
stop_file_watch (edit);
return;
case GTK_RESPONSE_OK:
moo_edit_reload (edit, NULL);
return;
default:
g_critical ("%s: invalid return value from "
"moo_edit_modified_on_disk_dialog()",
G_STRLOC);
return;
}
g_assert_not_reached ();
return;
}
static void file_modified_on_disk (MooEdit *edit)
{
g_return_if_fail (edit->priv->filename != NULL);
g_message ("%s: file '%s'", G_STRLOC, edit->priv->filename);
switch (edit->priv->file_watch_policy) {
case MOO_EDIT_DONT_WATCH_FILE:
g_warning ("%s", G_STRLOC);
stop_file_watch (edit);
return;
case MOO_EDIT_ALWAYS_ALERT:
ask_reload_or_not (edit);
return;
case MOO_EDIT_ALWAYS_RELOAD:
moo_edit_reload (edit, NULL);
return;
case MOO_EDIT_RELOAD_IF_SAFE:
if (edit->priv->status & MOO_EDIT_DOC_MODIFIED)
ask_reload_or_not (edit);
else
moo_edit_reload (edit, NULL);
return;
default:
g_critical ("%s: invalid edit->priv->file_watch_policy "
"value", G_STRLOC);
stop_file_watch (edit);
return;
}
}
static void file_deleted (MooEdit *edit)
{
g_return_if_fail (edit->priv->filename != NULL);
g_message ("%s: file '%s'", G_STRLOC, edit->priv->filename);
switch (edit->priv->file_watch_policy) {
case MOO_EDIT_DONT_WATCH_FILE:
g_warning ("%s", G_STRLOC);
stop_file_watch (edit);
return;
default:
moo_edit_file_deleted_dialog (edit);
stop_file_watch (edit);
add_status (edit, MOO_EDIT_DOC_DELETED);
return;
}
}
static void file_watch_error (MooEdit *edit)
{
g_critical ("%s", G_STRLOC);
stop_file_watch (edit);
add_status (edit, MOO_EDIT_DOC_DELETED);
}
static void file_watch_access_denied (MooEdit *edit)
{
g_critical ("%s", G_STRLOC);
stop_file_watch (edit);
add_status (edit, MOO_EDIT_DOC_DELETED);
}
/***************************************************************************/
/* File loading and saving
*/
static const char *line_end[3] = {"\n", "\r\n", "\r"};
static guint line_end_len[3] = {1, 2, 1};
static gboolean moo_edit_load_enc (MooEdit *edit,
const char *file,
const char *encoding,
GError **error);
gboolean _moo_edit_load (MooEdit *edit,
const char *file,
const char *encoding,
GError **error)
{
GError *err = NULL;
g_return_val_if_fail (file != NULL, FALSE);
if (!encoding) {
if (moo_edit_load_enc (edit, file, NULL, &err)) {
if (error) *error = NULL;
edit->priv->encoding = g_strdup ("UTF8");
return TRUE;
}
else if (err->code == G_CONVERT_ERROR_ILLEGAL_SEQUENCE) {
const char *enc;
if (g_get_charset (&enc)) {
if (error) *error = err;
return FALSE;
}
else {
if (moo_edit_load_enc (edit, file, enc, &err)) {
if (error) *error = NULL;
edit->priv->encoding = g_strdup (enc);
return TRUE;
}
else {
if (error) *error = err;
else g_error_free (err);
return FALSE;
}
}
}
else {
if (error) *error = err;
else g_error_free (err);
return FALSE;
}
}
else {
if (!strcmp (encoding, "UTF8")) encoding = NULL;
if (moo_edit_load_enc (edit, file, encoding, &err)) {
if (error) *error = NULL;
edit->priv->encoding = g_strdup (encoding);
return TRUE;
}
else {
if (error) *error = err;
else g_error_free (err);
return FALSE;
}
}
}
static gboolean moo_edit_load_enc (MooEdit *edit,
const char *filename,
const char *encoding,
GError **error)
{
GIOChannel *file = NULL;
GIOStatus status;
*error = NULL;
file = g_io_channel_new_file (filename, "r", error);
if (!file)
return FALSE;
if (encoding && g_io_channel_set_encoding (file, encoding, error) != G_IO_STATUS_NORMAL) {
g_io_channel_shutdown (file, TRUE, NULL);
g_io_channel_unref (file);
return FALSE;
}
while (TRUE)
{
char *line = NULL;
gsize len;
status = g_io_channel_read_line (file, &line, &len, NULL, error);
if (line) {
if (!g_utf8_validate (line, len, NULL)) {
g_io_channel_shutdown (file, TRUE, NULL);
g_io_channel_unref (file);
g_free (line);
*error = g_error_new (G_CONVERT_ERROR,
G_CONVERT_ERROR_ILLEGAL_SEQUENCE,
"Invalid UTF8 data read from file");
return FALSE;
}
gtk_text_buffer_insert_at_cursor (edit->priv->text_buffer,
line, len);
g_free (line);
}
if (status != G_IO_STATUS_NORMAL || *error) {
if (status != G_IO_STATUS_EOF) {
g_io_channel_shutdown (file, TRUE, NULL);
g_io_channel_unref (file);
return FALSE;
}
else {
g_io_channel_shutdown (file, TRUE, NULL);
g_io_channel_unref (file);
*error = NULL;
return TRUE;
}
}
}
}
gboolean _moo_edit_write (MooEdit *edit,
const char *filename,
const char *encoding,
GError **error)
{
GError *err = NULL;
GIOChannel *file;
GtkTextIter line_start;
const char *le = line_end[edit->priv->line_end_type];
gssize le_len = line_end_len[edit->priv->line_end_type];
GtkTextBuffer *buf = edit->priv->text_buffer;
g_return_val_if_fail (filename != NULL, FALSE);
if (!encoding) {
if (edit->priv->encoding)
encoding = edit->priv->encoding;
else
encoding = NULL;
}
if (encoding && !strcmp (encoding, "UTF8"))
encoding = NULL;
file = g_io_channel_new_file (filename, "w", &err);
if (!file) {
if (err) {
if (error) *error = err;
else g_error_free (err);
}
else if (error) *error = NULL;
return FALSE;
}
if (encoding && g_io_channel_set_encoding (file, encoding, &err) != G_IO_STATUS_NORMAL) {
g_io_channel_shutdown (file, TRUE, NULL);
g_io_channel_unref (file);
if (err) {
if (error) *error = err;
else g_error_free (err);
}
else if (error) *error = NULL;
return FALSE;
}
gtk_text_buffer_get_start_iter (buf, &line_start);
do {
gsize written;
GtkTextIter line_end = line_start;
gboolean write_line_end = FALSE;
if (!gtk_text_iter_ends_line (&line_start)) {
char *line;
gssize len = -1;
gtk_text_iter_forward_to_line_end (&line_end);
line = gtk_text_buffer_get_text (buf, &line_start, &line_end, FALSE);
g_io_channel_write_chars (file, line, len, &written, &err);
g_free (line);
if (err) {
if (error) *error = err;
else g_error_free (err);
g_io_channel_shutdown (file, TRUE, NULL);
g_io_channel_unref (file);
return FALSE;
}
}
if (gtk_text_iter_ends_line (&line_start))
write_line_end = !gtk_text_iter_is_end (&line_start);
else
write_line_end = !gtk_text_iter_is_end (&line_end);
if (write_line_end) {
g_io_channel_write_chars (file, le, le_len, &written, &err);
if (written < (gsize)le_len || err) {
if (err) {
if (error) *error = err;
else g_error_free (err);
}
else if (error) *error = NULL;
g_io_channel_shutdown (file, TRUE, NULL);
g_io_channel_unref (file);
return FALSE;
}
}
}
while (gtk_text_iter_forward_line (&line_start));
g_io_channel_shutdown (file, TRUE, &err);
if (err) {
if (error) *error = err;
else g_error_free (err);
g_io_channel_unref (file);
return FALSE;
}
else if (error) *error = NULL;
g_io_channel_unref (file);
return TRUE;
}