1 /* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 4; tab-width: 4 -*- */
2 /*
3  * anjuta-trunk
4  * Copyright (C) Johannes Schmid 2008 <jhs@gnome.org>
5  *
6  * anjuta-trunk is free software: you can redistribute it and/or modify it
7  * under the terms of the GNU General Public License as published by the
8  * Free Software Foundation, either version 3 of the License, or
9  * (at your option) any later version.
10  *
11  * anjuta-trunk is distributed in the hope that it will be useful, but
12  * WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
14  * See the GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License along
17  * with this program.  If not, see <http://www.gnu.org/licenses/>.
18  */
19 
20 #include "sourceview-io.h"
21 #include "sourceview-private.h"
22 #include <libanjuta/interfaces/ianjuta-editor.h>
23 #include <libanjuta/anjuta-convert.h>
24 #include <libanjuta/anjuta-encodings.h>
25 
26 #define READ_SIZE 4096
27 #define RATE_LIMIT 5000 /* Use a big rate limit to avoid duplicates */
28 
29 enum
30 {
31 	SAVE_STATUS,
32 	SAVE_FINISHED,
33 	OPEN_STATUS,
34 	OPEN_FINISHED,
35 	OPEN_FAILED,
36 	SAVE_FAILED,
37 	FILE_DELETED,
38 
39 	LAST_SIGNAL
40 };
41 static guint io_signals[LAST_SIGNAL] = { 0 };
42 
43 #define IO_ERROR_QUARK g_quark_from_string ("SourceviewIO-Error")
44 
45 #define IO_PRIORITY G_PRIORITY_DEFAULT
46 
47 G_DEFINE_TYPE (SourceviewIO, sourceview_io, G_TYPE_OBJECT);
48 
49 static void
on_sourceview_finalized(gpointer data,GObject * where_the_object_was)50 on_sourceview_finalized (gpointer data, GObject* where_the_object_was)
51 {
52 	SourceviewIO* sio = SOURCEVIEW_IO (data);
53 
54 	sio->sv = NULL;
55 	/* Cancel all open operations */
56 	g_cancellable_cancel (sio->open_cancel);
57 }
58 
59 static void
sourceview_io_init(SourceviewIO * object)60 sourceview_io_init (SourceviewIO *object)
61 {
62 	object->file = NULL;
63 	object->filename = NULL;
64 	object->read_buffer = NULL;
65 	object->write_buffer = NULL;
66 	object->open_cancel = g_cancellable_new();
67 	object->monitor = NULL;
68 	object->last_encoding = NULL;
69 	object->bytes_read = 0;
70 }
71 
72 static void
sourceview_io_finalize(GObject * object)73 sourceview_io_finalize (GObject *object)
74 {
75 	SourceviewIO* sio = SOURCEVIEW_IO(object);
76 
77 	if (sio->sv)
78 		g_object_weak_unref (G_OBJECT (sio->sv), on_sourceview_finalized, sio);
79 
80 	if (sio->file)
81 		g_object_unref (sio->file);
82 	g_free (sio->etag);
83 	g_free(sio->filename);
84 	g_free(sio->read_buffer);
85 	g_free(sio->write_buffer);
86 	g_object_unref (sio->open_cancel);
87 	if (sio->monitor)
88 		g_object_unref (sio->monitor);
89 
90 	G_OBJECT_CLASS (sourceview_io_parent_class)->finalize (object);
91 }
92 
93 static void
sourceview_io_class_init(SourceviewIOClass * klass)94 sourceview_io_class_init (SourceviewIOClass *klass)
95 {
96 	GObjectClass* object_class = G_OBJECT_CLASS (klass);
97 
98 	object_class->finalize = sourceview_io_finalize;
99 
100 	klass->changed = NULL;
101 	klass->deleted = NULL;
102 	klass->save_finished = NULL;
103 	klass->open_finished = NULL;
104 	klass->open_failed = NULL;
105 	klass->save_failed = NULL;
106 
107 	io_signals[SAVE_STATUS] =
108 		g_signal_new ("changed",
109 		              G_OBJECT_CLASS_TYPE (klass),
110 		              0,
111 		              G_STRUCT_OFFSET (SourceviewIOClass, changed),
112 		              NULL, NULL,
113 		              g_cclosure_marshal_VOID__VOID,
114 		              G_TYPE_NONE, 0,
115 		              NULL);
116 
117 	io_signals[SAVE_FINISHED] =
118 		g_signal_new ("save-finished",
119 		              G_OBJECT_CLASS_TYPE (klass),
120 		              0,
121 		              G_STRUCT_OFFSET (SourceviewIOClass, save_finished),
122 		              NULL, NULL,
123 		              g_cclosure_marshal_VOID__VOID,
124 		              G_TYPE_NONE, 0,
125 		              NULL);
126 
127 	io_signals[OPEN_FINISHED] =
128 		g_signal_new ("open-finished",
129 		              G_OBJECT_CLASS_TYPE (klass),
130 		              0,
131 		              G_STRUCT_OFFSET (SourceviewIOClass, open_finished),
132 		              NULL, NULL,
133 		              g_cclosure_marshal_VOID__VOID,
134 		              G_TYPE_NONE, 0,
135 		              NULL);
136 
137 	io_signals[OPEN_FAILED] =
138 		g_signal_new ("open-failed",
139 		              G_OBJECT_CLASS_TYPE (klass),
140 		              0,
141 		              G_STRUCT_OFFSET (SourceviewIOClass, open_failed),
142 		              NULL, NULL,
143 		              g_cclosure_marshal_VOID__POINTER,
144 		              G_TYPE_NONE, 1,
145 		              G_TYPE_POINTER);
146 
147 	io_signals[SAVE_FAILED] =
148 		g_signal_new ("save-failed",
149 		              G_OBJECT_CLASS_TYPE (klass),
150 		              0,
151 		              G_STRUCT_OFFSET (SourceviewIOClass, save_failed),
152 		              NULL, NULL,
153 		              g_cclosure_marshal_VOID__POINTER,
154 		              G_TYPE_NONE, 1,
155 		              G_TYPE_POINTER);
156 
157 	io_signals[FILE_DELETED] =
158 		g_signal_new ("deleted",
159 		              G_OBJECT_CLASS_TYPE (klass),
160 		              0,
161 		              G_STRUCT_OFFSET (SourceviewIOClass, deleted),
162 		              NULL, NULL,
163 		              g_cclosure_marshal_VOID__VOID,
164 		              G_TYPE_NONE, 0,
165 		              NULL);
166 }
167 
on_file_changed(GFileMonitor * monitor,GFile * file,GFile * other_file,GFileMonitorEvent event_type,gpointer data)168 static void on_file_changed (GFileMonitor* monitor,
169 							 GFile* file,
170 							 GFile* other_file,
171 							 GFileMonitorEvent event_type,
172 							 gpointer data)
173 {
174 	SourceviewIO* sio = SOURCEVIEW_IO(data);
175 
176 	switch (event_type)
177 	{
178 		case G_FILE_MONITOR_EVENT_CREATED:
179 		case G_FILE_MONITOR_EVENT_CHANGES_DONE_HINT:
180 		{
181 			GFileInfo* info;
182 			const gchar* etag;
183 
184 			/* Ignore changes that happen during a save. */
185 			if (sio->write_buffer)
186 				break;
187 
188 			info = g_file_query_info (file, G_FILE_ATTRIBUTE_ETAG_VALUE,
189 			                          G_FILE_QUERY_INFO_NONE, NULL, NULL);
190 			if (!info)
191 				break;
192 
193 			etag = g_file_info_get_etag (info);
194 
195 			/* Only emit "changed" if the etag has changed. */
196 			if (g_strcmp0 (sio->etag, etag))
197 				g_signal_emit_by_name (sio, "changed");
198 
199 			g_object_unref (info);
200 			break;
201 		}
202 		case G_FILE_MONITOR_EVENT_DELETED:
203 		{
204 			gchar* filename = NULL;
205 
206 			if (G_IS_FILE (file))
207 				filename = g_file_get_basename (file);
208 			/* Only emit "deleted" if monitored file isn't temporary */
209 			if (filename != NULL && !g_str_has_prefix (filename, ".goutputstream-"))
210 				g_signal_emit_by_name (sio, "deleted");
211 
212 			g_free (filename);
213 			break;
214 		}
215 		default:
216 			break;
217 	}
218 }
219 
220 static void
setup_monitor(SourceviewIO * sio)221 setup_monitor(SourceviewIO* sio)
222 {
223 	if (sio->monitor != NULL)
224 		g_object_unref (sio->monitor);
225 	sio->monitor = g_file_monitor_file (sio->file,
226 	                                    G_FILE_MONITOR_NONE,
227 	                                    NULL,
228 	                                    NULL);
229 	if (sio->monitor)
230 	{
231 		g_signal_connect (sio->monitor, "changed",
232 		                  G_CALLBACK(on_file_changed), sio);
233 		g_file_monitor_set_rate_limit (sio->monitor, RATE_LIMIT);
234 	}
235 }
236 
237 static void
sourceview_io_unset_current_file(SourceviewIO * sio)238 sourceview_io_unset_current_file (SourceviewIO* sio)
239 {
240 	g_clear_object (&sio->file);
241 	g_clear_object (&sio->monitor);
242 
243 	g_free (sio->etag);
244 	sio->etag = NULL;
245 
246 	g_free (sio->filename);
247 	sio->filename = NULL;
248 }
249 
250 static void
set_display_name(SourceviewIO * sio)251 set_display_name (SourceviewIO* sio)
252 {
253 	GFileInfo* file_info = g_file_query_info (sio->file,
254 											  G_FILE_ATTRIBUTE_STANDARD_DISPLAY_NAME,
255 											  G_FILE_QUERY_INFO_NONE,
256 											  NULL,
257 											  NULL);
258 	if (file_info)
259 	{
260 		g_free (sio->filename);
261 		sio->filename = g_strdup(g_file_info_get_display_name (file_info));
262 	}
263 	else
264 	{
265 		g_free (sio->filename);
266 		sio->filename = NULL;
267 	}
268 	g_object_unref (file_info);
269 }
270 
271 /*
272  * This function may be called after the corresponding Sourceview (sio->sv)
273  * has been destroyed.
274  */
275 static void
on_save_finished(GObject * file,GAsyncResult * result,gpointer data)276 on_save_finished (GObject* file, GAsyncResult* result, gpointer data)
277 {
278 	SourceviewIO* sio = SOURCEVIEW_IO(data);
279 
280 	GError* err = NULL;
281 	gchar* etag;
282 
283 	g_file_replace_contents_finish (G_FILE (file),
284 	                                result,
285 	                                &etag,
286 	                                &err);
287 	g_free (sio->write_buffer);
288 	sio->write_buffer = NULL;
289 	if (err)
290 	{
291 		g_signal_emit_by_name (sio, "save-failed", err);
292 		g_error_free (err);
293 	}
294 	else
295 	{
296 		set_display_name (sio);
297 		if (!sio->monitor)
298 			setup_monitor (sio);
299 
300 		g_free (sio->etag);
301 		sio->etag = etag;
302 
303 		g_signal_emit_by_name (sio, "save-finished");
304 	}
305 
306 	if (sio->shell)
307 		anjuta_shell_saving_pop (sio->shell);
308 
309 	g_object_unref (sio);
310 }
311 
312 void
sourceview_io_save(SourceviewIO * sio)313 sourceview_io_save (SourceviewIO* sio)
314 {
315 	g_return_if_fail (SOURCEVIEW_IS_IO (sio));
316 	g_return_if_fail (sio->sv != NULL);
317 
318 	if (!sio->file)
319 	{
320 		GError* error = NULL;
321 		g_set_error (&error, IO_ERROR_QUARK, 0,
322 					 _("Could not save file because filename not yet specified"));
323 		g_signal_emit_by_name (sio, "save-failed", error);
324 		g_error_free(error);
325 	}
326 	else
327 		sourceview_io_save_as (sio, sio->file);
328 }
329 
330 void
sourceview_io_save_as(SourceviewIO * sio,GFile * file)331 sourceview_io_save_as (SourceviewIO* sio, GFile* file)
332 {
333 	gboolean backup = TRUE;
334 	gsize len;
335 
336 	g_return_if_fail (SOURCEVIEW_IS_IO (sio));
337 	g_return_if_fail (sio->sv != NULL);
338 	g_return_if_fail (G_IS_FILE (file));
339 
340 	if (sio->file != file)
341 	{
342 		sourceview_io_unset_current_file (sio);
343 
344 		sio->file = g_object_ref (file);
345 	}
346 
347 	backup = g_settings_get_boolean (sio->sv->priv->settings,
348 	                                 "backup");
349 
350 	if (sio->last_encoding == NULL)
351 	{
352 		sio->write_buffer = ianjuta_editor_get_text_all (IANJUTA_EDITOR(sio->sv),
353 														 NULL);
354 		len = strlen (sio->write_buffer);
355 	}
356 	else
357 	{
358 		GError* err = NULL;
359 		gchar* buffer_text = ianjuta_editor_get_text_all (IANJUTA_EDITOR(sio->sv),
360 														  NULL);
361 		sio->write_buffer = anjuta_convert_from_utf8 (buffer_text,
362 													  -1,
363 													  sio->last_encoding,
364 													  &len,
365 													  &err);
366 		g_free (buffer_text);
367 		if (err != NULL)
368 		{
369 			g_signal_emit_by_name (sio, "save-failed", err);
370 			g_error_free(err);
371 			return;
372 		}
373 	}
374 	g_file_replace_contents_async (file,
375 	                               sio->write_buffer,
376 	                               len,
377 	                               NULL,
378 	                               backup,
379 	                               G_FILE_CREATE_NONE,
380 	                               NULL,
381 	                               on_save_finished,
382 	                               sio);
383 	anjuta_shell_saving_push (sio->shell);
384 
385 	g_object_ref (sio);
386 }
387 
insert_text_in_document(SourceviewIO * sio,const gchar * text,gsize len)388 static void insert_text_in_document(SourceviewIO* sio, const gchar* text, gsize len)
389 {
390 	GtkSourceBuffer* document = GTK_SOURCE_BUFFER (sio->sv->priv->document);
391 	gtk_source_buffer_begin_not_undoable_action (GTK_SOURCE_BUFFER (sio->sv->priv->document));
392 
393 	/* Insert text in the buffer */
394 	gtk_text_buffer_set_text (GTK_TEXT_BUFFER (document),
395 							  text,
396 							  len);
397 
398 	gtk_text_buffer_set_modified (GTK_TEXT_BUFFER (document),
399 				      FALSE);
400 
401 	gtk_source_buffer_end_not_undoable_action (document);
402 }
403 
404 static gboolean
append_buffer(SourceviewIO * sio,gsize size)405 append_buffer (SourceviewIO* sio, gsize size)
406 {
407 	/* Text is utf-8 - good */
408 	if (g_utf8_validate (sio->read_buffer, size, NULL))
409 	{
410 		insert_text_in_document (sio, sio->read_buffer, size);
411 	}
412 	else
413 	{
414 		/* Text is not utf-8 */
415 		GError *conv_error = NULL;
416 		gchar *converted_text = NULL;
417 		gsize new_len = size;
418 		const AnjutaEncoding* enc = NULL;
419 
420 		converted_text = anjuta_convert_to_utf8 (sio->read_buffer,
421 												 size,
422 												 &enc,
423 												 &new_len,
424 												 &conv_error);
425 		if  (converted_text == NULL)
426 		{
427 			/* Last chance, let's try 8859-15 */
428 			enc = anjuta_encoding_get_from_charset( "ISO-8859-15");
429 			conv_error = NULL;
430 			converted_text = anjuta_convert_to_utf8 (sio->read_buffer,
431 													 size,
432 													 &enc,
433 													 &new_len,
434 													 &conv_error);
435 		}
436 		if (converted_text == NULL)
437 		{
438 			g_return_val_if_fail (conv_error != NULL, FALSE);
439 
440 			g_signal_emit_by_name (sio, "open-failed", conv_error);
441 			g_error_free (conv_error);
442 			return FALSE;
443 		}
444 		sio->last_encoding = enc;
445 		insert_text_in_document (sio, converted_text, new_len);
446 		g_free (converted_text);
447 	}
448 	return TRUE;
449 }
450 
451 static void
on_read_finished(GObject * input,GAsyncResult * result,gpointer data)452 on_read_finished (GObject* input, GAsyncResult* result, gpointer data)
453 {
454 	SourceviewIO* sio = SOURCEVIEW_IO(data);
455 	GInputStream* input_stream = G_INPUT_STREAM(input);
456 	gsize current_bytes = 0;
457 	GError* err = NULL;
458 
459 	if (!g_cancellable_set_error_if_cancelled (sio->open_cancel, &err))
460 		current_bytes = g_input_stream_read_finish (input_stream, result, &err);
461 	if (err)
462 	{
463 		g_signal_emit_by_name (sio, "open-failed", err);
464 		g_error_free (err);
465 	}
466 	else
467 	{
468 		/* Check that the parent Sourceview is still alive. */
469 		if (sio->sv == NULL)
470 		{
471 			g_warning ("Sourceview was destroyed without canceling SourceviewIO open operation");
472 			goto out;
473 		}
474 
475 		sio->bytes_read += current_bytes;
476 		if (current_bytes != 0)
477 		{
478 			sio->read_buffer = g_realloc (sio->read_buffer, sio->bytes_read + READ_SIZE);
479 			g_input_stream_read_async (G_INPUT_STREAM (input_stream),
480 									   sio->read_buffer + sio->bytes_read,
481 									   READ_SIZE,
482 									   IO_PRIORITY,
483 									   sio->open_cancel,
484 									   on_read_finished,
485 									   sio);
486 			return;
487 		}
488 		else
489 		{
490 			GFileInfo* info;
491 
492 			info = g_file_input_stream_query_info (G_FILE_INPUT_STREAM (input_stream),
493 			                                       G_FILE_ATTRIBUTE_ETAG_VALUE,
494 			                                       NULL, &err);
495 			if (!info)
496 			{
497 				g_signal_emit_by_name (sio, "open-failed", err);
498 				g_error_free (err);
499 			}
500 			else
501 			{
502 				g_free (sio->etag);
503 				sio->etag = g_strdup (g_file_info_get_etag (info));
504 				g_object_unref (info);
505 
506 				if (append_buffer (sio, sio->bytes_read))
507 					g_signal_emit_by_name (sio, "open-finished");
508 				setup_monitor (sio);
509 			}
510 		}
511 	}
512 
513 out:
514 	g_object_unref (input_stream);
515 	g_free (sio->read_buffer);
516 	sio->read_buffer = NULL;
517 	sio->bytes_read = 0;
518 	g_object_unref (sio);
519 }
520 
521 void
sourceview_io_open(SourceviewIO * sio,GFile * file)522 sourceview_io_open (SourceviewIO* sio, GFile* file)
523 {
524 	GFileInputStream* input_stream;
525 	GError* err = NULL;
526 
527 	g_return_if_fail (SOURCEVIEW_IS_IO (sio));
528 	g_return_if_fail (sio->sv != NULL);
529 	g_return_if_fail (G_IS_FILE (file));
530 
531 	if (sio->file != file)
532 	{
533 		sourceview_io_unset_current_file (sio);
534 
535 		sio->file = g_object_ref (file);
536 		set_display_name(sio);
537 	}
538 
539 	input_stream = g_file_read (file, NULL, &err);
540 	if (!input_stream)
541 	{
542 		g_signal_emit_by_name (sio, "open-failed", err);
543 		g_error_free (err);
544 		return;
545 	}
546 	sio->read_buffer = g_realloc (sio->read_buffer, READ_SIZE);
547 	g_input_stream_read_async (G_INPUT_STREAM (input_stream),
548 							   sio->read_buffer,
549 							   READ_SIZE,
550 							   IO_PRIORITY,
551 							   sio->open_cancel,
552 							   on_read_finished,
553 							   g_object_ref (sio));
554 }
555 
556 GFile*
sourceview_io_get_file(SourceviewIO * sio)557 sourceview_io_get_file (SourceviewIO* sio)
558 {
559 	if (sio->file)
560 		g_object_ref (sio->file);
561 	return sio->file;
562 }
563 
564 const gchar*
sourceview_io_get_filename(SourceviewIO * sio)565 sourceview_io_get_filename (SourceviewIO* sio)
566 {
567 	static gint new_file_count = 1;
568 	if (sio->filename == NULL) /* new file */
569 	{
570 		sio->filename = g_strdup_printf (_("New file %d"), new_file_count++);
571 	}
572 	return sio->filename;
573 }
574 
575 void
sourceview_io_set_filename(SourceviewIO * sio,const gchar * filename)576 sourceview_io_set_filename (SourceviewIO* sio, const gchar* filename)
577 {
578 	g_free (sio->filename);
579 	sio->filename = g_strdup(filename);
580 }
581 
582 gchar*
sourceview_io_get_mime_type(SourceviewIO * sio)583 sourceview_io_get_mime_type (SourceviewIO* sio)
584 {
585 	GFileInfo* file_info;
586 
587 	if (!sio->file)
588 		return NULL;
589 
590 	file_info = g_file_query_info (sio->file,
591 								   G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE,
592 								   G_FILE_QUERY_INFO_NONE,
593 								   NULL,
594 								   NULL);
595 	if (file_info)
596 	{
597 		gchar* mime_type = g_strdup (g_file_info_get_content_type (file_info));
598 		g_object_unref (file_info);
599 		return mime_type;
600 	}
601 	else
602 		return NULL;
603 
604 }
605 
606 gboolean
sourceview_io_get_read_only(SourceviewIO * sio)607 sourceview_io_get_read_only (SourceviewIO* sio)
608 {
609 	GFileInfo* file_info;
610 	gboolean retval;
611 
612 	if (!sio->file)
613 		return FALSE;
614 
615 	file_info = g_file_query_info (sio->file,
616 								   G_FILE_ATTRIBUTE_ACCESS_CAN_WRITE,
617 								   G_FILE_QUERY_INFO_NONE,
618 								   NULL,
619 								   NULL);
620 	if (!file_info)
621 		return FALSE;
622 
623 	retval = !g_file_info_get_attribute_boolean (file_info,
624 												G_FILE_ATTRIBUTE_ACCESS_CAN_WRITE);
625 	g_object_unref (file_info);
626 	return retval;
627 }
628 
629 SourceviewIO*
sourceview_io_new(Sourceview * sv)630 sourceview_io_new (Sourceview* sv)
631 {
632 	SourceviewIO* sio;
633 
634 	g_return_val_if_fail (ANJUTA_IS_SOURCEVIEW(sv), NULL);
635 
636 	sio = SOURCEVIEW_IO(g_object_new (SOURCEVIEW_TYPE_IO, NULL));
637 
638 	sio->sv = sv;
639 	g_object_weak_ref (G_OBJECT (sv), on_sourceview_finalized, sio);
640 
641 	/* Store a separate pointer to the shell since we want to have access
642 	 * to it even though the parent Sourceview has been destroyed .*/
643 	sio->shell = ANJUTA_PLUGIN (sv->priv->plugin)->shell;
644 	g_object_add_weak_pointer (G_OBJECT (sio->shell), (gpointer*)&sio->shell);
645 
646 	return sio;
647 }
648