Mercurial > geeqie
annotate src/editors.c @ 190:c2923efebfdc
whitelist of files that can have an xmp sidecar, sample external command
for creating sidecar
| author | nadvornik |
|---|---|
| date | Sun, 16 Mar 2008 14:11:22 +0000 |
| parents | b2266996fa83 |
| children | f6e307c7bad6 |
| rev | line source |
|---|---|
| 9 | 1 /* |
| 2 * GQview | |
|
123
3602a4aa7c71
Sat Dec 2 20:15:22 2006 John Ellis <johne@verizon.net>
gqview
parents:
60
diff
changeset
|
3 * (C) 2006 John Ellis |
| 9 | 4 * |
| 5 * Author: John Ellis | |
| 6 * | |
| 7 * This software is released under the GNU General Public License (GNU GPL). | |
| 8 * Please read the included file COPYING for more information. | |
| 9 * This software comes with no warranty of any kind, use at your own risk! | |
| 10 */ | |
| 11 | |
| 12 | |
| 13 #include "gqview.h" | |
| 14 #include "editors.h" | |
| 15 | |
| 16 #include "utilops.h" | |
| 17 #include "ui_fileops.h" | |
| 18 #include "ui_spinner.h" | |
| 19 #include "ui_utildlg.h" | |
| 20 | |
| 140 | 21 #include "filelist.h" |
| 22 | |
| 9 | 23 #include <errno.h> |
| 24 | |
| 25 | |
| 26 #define EDITOR_WINDOW_WIDTH 500 | |
| 27 #define EDITOR_WINDOW_HEIGHT 300 | |
| 28 | |
| 140 | 29 #define COMMAND_SHELL "/bin/sh" |
| 9 | 30 #define COMMAND_OPT "-c" |
| 31 | |
| 32 | |
| 33 typedef struct _EditorVerboseData EditorVerboseData; | |
| 34 struct _EditorVerboseData { | |
| 35 GenericDialog *gd; | |
| 36 GtkWidget *button_close; | |
| 37 GtkWidget *button_stop; | |
| 38 GtkWidget *text; | |
| 39 GtkWidget *progress; | |
| 40 GtkWidget *spinner; | |
| 140 | 41 }; |
| 42 | |
| 43 typedef struct _EditorData EditorData; | |
| 44 struct _EditorData { | |
| 45 gint flags; | |
| 46 GPid pid; | |
| 47 gchar *command_template; | |
| 48 GList *list; | |
| 9 | 49 gint count; |
| 50 gint total; | |
| 140 | 51 gboolean stopping; |
| 52 EditorVerboseData *vd; | |
| 53 EditorCallback callback; | |
| 54 gpointer data; | |
| 9 | 55 }; |
| 56 | |
| 57 | |
|
134
9009856628f7
started implementation of external commands; external Delete should work
nadvornik
parents:
123
diff
changeset
|
58 static gchar *editor_slot_defaults[GQVIEW_EDITOR_SLOTS * 2] = { |
|
147
b2266996fa83
added possibility to specify prefered file type for external commands
nadvornik
parents:
140
diff
changeset
|
59 N_("The Gimp"), "gimp-remote -n %{.cr2;.crw;.nef;.raw;*}f", |
| 9 | 60 N_("XV"), "xv %f", |
| 61 N_("Xpaint"), "xpaint %f", | |
|
147
b2266996fa83
added possibility to specify prefered file type for external commands
nadvornik
parents:
140
diff
changeset
|
62 N_("UFraw"), "ufraw %{.cr2;.crw;.nef;.raw}p", |
|
190
c2923efebfdc
whitelist of files that can have an xmp sidecar, sample external command
nadvornik
parents:
147
diff
changeset
|
63 N_("Add XMP sidecar"), "%vFILE=%{.cr2;.crw;.nef;.raw}p;XMP=`echo \"$FILE\"|sed -e 's|\\.[^.]*$|.xmp|'`; exiftool -tagsfromfile \"$FILE\" \"$XMP\"", |
| 9 | 64 NULL, NULL, |
| 65 NULL, NULL, | |
| 66 NULL, NULL, | |
|
147
b2266996fa83
added possibility to specify prefered file type for external commands
nadvornik
parents:
140
diff
changeset
|
67 N_("Rotate jpeg clockwise"), "%vif jpegtran -rotate 90 -copy all -outfile %{.jpg;.jpeg}p_tmp %{.jpg;.jpeg}p; then mv %{.jpg;.jpeg}p_tmp %{.jpg;.jpeg}p;else rm %{.jpg;.jpeg}p_tmp;fi", |
|
b2266996fa83
added possibility to specify prefered file type for external commands
nadvornik
parents:
140
diff
changeset
|
68 N_("Rotate jpeg counterclockwise"), "%vif jpegtran -rotate 270 -copy all -outfile %{.jpg;.jpeg}p_tmp %{.jpg;.jpeg}p; then mv %{.jpg;.jpeg}p_tmp %{.jpg;.jpeg}p;else rm %{.jpg;.jpeg}p_tmp;fi", |
|
134
9009856628f7
started implementation of external commands; external Delete should work
nadvornik
parents:
123
diff
changeset
|
69 /* special slots */ |
| 136 | 70 #if 1 |
| 71 /* for testing */ | |
| 140 | 72 "External Copy command", "%vset -x;cp %p %d", |
| 73 "External Move command", "%vset -x;mv %p %d", | |
| 74 "External Rename command", "%vset -x;mv %p %d", | |
| 136 | 75 "External Delete command", "%vset -x;rm %f", |
| 76 "External New Folder command", NULL | |
| 77 #else | |
|
134
9009856628f7
started implementation of external commands; external Delete should work
nadvornik
parents:
123
diff
changeset
|
78 "External Copy command", NULL, |
|
9009856628f7
started implementation of external commands; external Delete should work
nadvornik
parents:
123
diff
changeset
|
79 "External Move command", NULL, |
|
9009856628f7
started implementation of external commands; external Delete should work
nadvornik
parents:
123
diff
changeset
|
80 "External Rename command", NULL, |
|
9009856628f7
started implementation of external commands; external Delete should work
nadvornik
parents:
123
diff
changeset
|
81 "External Delete command", NULL, |
|
9009856628f7
started implementation of external commands; external Delete should work
nadvornik
parents:
123
diff
changeset
|
82 "External New Folder command", NULL |
| 136 | 83 #endif |
| 9 | 84 }; |
| 85 | |
| 140 | 86 static void editor_verbose_window_progress(EditorData *ed, const gchar *text); |
| 87 static gint editor_command_next_start(EditorData *ed); | |
| 88 static gint editor_command_next_finish(EditorData *ed, gint status); | |
| 89 static gint editor_command_done(EditorData *ed); | |
| 9 | 90 |
| 91 /* | |
| 92 *----------------------------------------------------------------------------- | |
| 93 * external editor routines | |
| 94 *----------------------------------------------------------------------------- | |
| 95 */ | |
| 96 | |
| 97 void editor_reset_defaults(void) | |
| 98 { | |
| 99 gint i; | |
| 100 | |
| 101 for (i = 0; i < GQVIEW_EDITOR_SLOTS; i++) | |
| 102 { | |
| 103 g_free(editor_name[i]); | |
| 104 editor_name[i] = g_strdup(_(editor_slot_defaults[i * 2])); | |
| 105 g_free(editor_command[i]); | |
| 106 editor_command[i] = g_strdup(editor_slot_defaults[i * 2 + 1]); | |
| 107 } | |
| 108 } | |
| 109 | |
| 140 | 110 static void editor_verbose_data_free(EditorData *ed) |
| 111 { | |
| 112 if (!ed->vd) return; | |
| 113 g_free(ed->vd); | |
| 114 ed->vd = NULL; | |
| 115 } | |
| 116 | |
| 117 static void editor_data_free(EditorData *ed) | |
| 118 { | |
| 119 editor_verbose_data_free(ed); | |
| 120 g_free(ed->command_template); | |
| 121 g_free(ed); | |
| 122 } | |
| 123 | |
| 9 | 124 static void editor_verbose_window_close(GenericDialog *gd, gpointer data) |
| 125 { | |
| 140 | 126 EditorData *ed = data; |
| 9 | 127 |
| 128 generic_dialog_close(gd); | |
| 140 | 129 editor_verbose_data_free(ed); |
| 130 if (ed->pid == -1) editor_data_free(ed); /* the process has already terminated */ | |
| 9 | 131 } |
| 132 | |
| 133 static void editor_verbose_window_stop(GenericDialog *gd, gpointer data) | |
| 134 { | |
| 140 | 135 EditorData *ed = data; |
| 136 ed->stopping = TRUE; | |
| 137 ed->count = 0; | |
| 138 editor_verbose_window_progress(ed, _("stopping...")); | |
| 9 | 139 } |
| 140 | |
| 141 static void editor_verbose_window_enable_close(EditorVerboseData *vd) | |
| 142 { | |
| 143 vd->gd->cancel_cb = editor_verbose_window_close; | |
| 144 | |
| 145 spinner_set_interval(vd->spinner, -1); | |
| 146 gtk_widget_set_sensitive(vd->button_stop, FALSE); | |
| 147 gtk_widget_set_sensitive(vd->button_close, TRUE); | |
| 148 } | |
| 149 | |
| 140 | 150 static EditorVerboseData *editor_verbose_window(EditorData *ed, const gchar *text) |
| 9 | 151 { |
| 152 EditorVerboseData *vd; | |
| 153 GtkWidget *scrolled; | |
| 154 GtkWidget *hbox; | |
| 155 gchar *buf; | |
| 156 | |
| 157 vd = g_new0(EditorVerboseData, 1); | |
| 158 | |
| 159 vd->gd = file_util_gen_dlg(_("Edit command results"), "GQview", "editor_results", | |
| 160 NULL, FALSE, | |
| 140 | 161 NULL, ed); |
| 9 | 162 buf = g_strdup_printf(_("Output of %s"), text); |
| 163 generic_dialog_add_message(vd->gd, NULL, buf, NULL); | |
| 164 g_free(buf); | |
| 165 vd->button_stop = generic_dialog_add_button(vd->gd, GTK_STOCK_STOP, NULL, | |
| 166 editor_verbose_window_stop, FALSE); | |
| 167 gtk_widget_set_sensitive(vd->button_stop, FALSE); | |
| 168 vd->button_close = generic_dialog_add_button(vd->gd, GTK_STOCK_CLOSE, NULL, | |
| 169 editor_verbose_window_close, TRUE); | |
| 170 gtk_widget_set_sensitive(vd->button_close, FALSE); | |
| 171 | |
| 172 scrolled = gtk_scrolled_window_new(NULL, NULL); | |
| 173 gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scrolled), GTK_SHADOW_IN); | |
| 174 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolled), | |
| 175 GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC); | |
| 176 gtk_box_pack_start(GTK_BOX(vd->gd->vbox), scrolled, TRUE, TRUE, 5); | |
| 177 gtk_widget_show(scrolled); | |
| 178 | |
| 179 vd->text = gtk_text_view_new(); | |
| 180 gtk_text_view_set_editable(GTK_TEXT_VIEW(vd->text), FALSE); | |
| 181 gtk_widget_set_size_request(vd->text, EDITOR_WINDOW_WIDTH, EDITOR_WINDOW_HEIGHT); | |
| 182 gtk_container_add(GTK_CONTAINER(scrolled), vd->text); | |
| 183 gtk_widget_show(vd->text); | |
| 184 | |
| 185 hbox = gtk_hbox_new(FALSE, 0); | |
| 186 gtk_box_pack_start(GTK_BOX(vd->gd->vbox), hbox, FALSE, FALSE, 0); | |
| 187 gtk_widget_show(hbox); | |
| 188 | |
| 189 vd->progress = gtk_progress_bar_new(); | |
| 190 gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(vd->progress), 0.0); | |
| 191 gtk_box_pack_start(GTK_BOX(hbox), vd->progress, TRUE, TRUE, 0); | |
| 192 gtk_widget_show(vd->progress); | |
| 193 | |
| 194 vd->spinner = spinner_new(NULL, SPINNER_SPEED); | |
| 195 gtk_box_pack_start(GTK_BOX(hbox), vd->spinner, FALSE, FALSE, 0); | |
| 196 gtk_widget_show(vd->spinner); | |
| 197 | |
| 198 gtk_widget_show(vd->gd->dialog); | |
| 199 | |
| 140 | 200 ed->vd = vd; |
| 9 | 201 return vd; |
| 202 } | |
| 203 | |
| 204 static void editor_verbose_window_fill(EditorVerboseData *vd, gchar *text, gint len) | |
| 205 { | |
| 206 GtkTextBuffer *buffer; | |
| 207 GtkTextIter iter; | |
| 208 | |
| 209 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(vd->text)); | |
| 210 gtk_text_buffer_get_iter_at_offset(buffer, &iter, -1); | |
| 211 gtk_text_buffer_insert(buffer, &iter, text, len); | |
| 212 } | |
| 213 | |
| 140 | 214 static void editor_verbose_window_progress(EditorData *ed, const gchar *text) |
| 9 | 215 { |
| 140 | 216 if (!ed->vd) return; |
| 217 | |
| 218 if (ed->total) | |
| 9 | 219 { |
| 140 | 220 gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(ed->vd->progress), (double)ed->count / ed->total); |
| 9 | 221 } |
| 222 | |
| 140 | 223 gtk_progress_bar_set_text(GTK_PROGRESS_BAR(ed->vd->progress), (text) ? text : ""); |
| 9 | 224 } |
| 225 | |
| 226 static gboolean editor_verbose_io_cb(GIOChannel *source, GIOCondition condition, gpointer data) | |
| 227 { | |
| 140 | 228 EditorData *ed = data; |
| 9 | 229 gchar buf[512]; |
| 230 gsize count; | |
| 231 | |
| 140 | 232 if (condition & G_IO_IN) |
| 9 | 233 { |
| 140 | 234 while (g_io_channel_read_chars(source, buf, sizeof(buf), &count, NULL) == G_IO_STATUS_NORMAL) |
| 235 { | |
| 236 if (!g_utf8_validate(buf, count, NULL)) | |
| 9 | 237 { |
| 140 | 238 gchar *utf8; |
| 239 utf8 = g_locale_to_utf8(buf, count, NULL, NULL, NULL); | |
| 240 if (utf8) | |
| 9 | 241 { |
| 140 | 242 editor_verbose_window_fill(ed->vd, utf8, -1); |
| 243 g_free(utf8); | |
| 9 | 244 } |
| 245 else | |
| 246 { | |
| 140 | 247 editor_verbose_window_fill(ed->vd, "GQview: Error converting text to valid utf8\n", -1); |
| 9 | 248 } |
| 249 } | |
| 140 | 250 else |
| 251 { | |
| 252 editor_verbose_window_fill(ed->vd, buf, count); | |
| 253 } | |
| 254 } | |
| 9 | 255 } |
| 256 | |
| 140 | 257 if (condition & (G_IO_ERR | G_IO_HUP)) |
| 9 | 258 { |
| 140 | 259 g_io_channel_shutdown(source, TRUE, NULL); |
| 9 | 260 return FALSE; |
| 261 } | |
| 262 | |
| 263 return TRUE; | |
| 264 } | |
| 265 | |
| 138 | 266 typedef enum { |
| 267 PATH_FILE, | |
| 140 | 268 PATH_DEST |
| 138 | 269 } PathType; |
| 270 | |
| 271 | |
| 140 | 272 static gchar *editor_command_path_parse(const FileData *fd, PathType type, const gchar *extensions) |
|
123
3602a4aa7c71
Sat Dec 2 20:15:22 2006 John Ellis <johne@verizon.net>
gqview
parents:
60
diff
changeset
|
273 { |
|
3602a4aa7c71
Sat Dec 2 20:15:22 2006 John Ellis <johne@verizon.net>
gqview
parents:
60
diff
changeset
|
274 GString *string; |
|
3602a4aa7c71
Sat Dec 2 20:15:22 2006 John Ellis <johne@verizon.net>
gqview
parents:
60
diff
changeset
|
275 gchar *pathl; |
|
147
b2266996fa83
added possibility to specify prefered file type for external commands
nadvornik
parents:
140
diff
changeset
|
276 const gchar *p = NULL; |
|
123
3602a4aa7c71
Sat Dec 2 20:15:22 2006 John Ellis <johne@verizon.net>
gqview
parents:
60
diff
changeset
|
277 |
|
3602a4aa7c71
Sat Dec 2 20:15:22 2006 John Ellis <johne@verizon.net>
gqview
parents:
60
diff
changeset
|
278 string = g_string_new(""); |
| 138 | 279 |
| 280 if (type == PATH_FILE) | |
| 281 { | |
|
147
b2266996fa83
added possibility to specify prefered file type for external commands
nadvornik
parents:
140
diff
changeset
|
282 GList *ext_list = filter_to_list(extensions); |
|
b2266996fa83
added possibility to specify prefered file type for external commands
nadvornik
parents:
140
diff
changeset
|
283 GList *work = ext_list; |
|
b2266996fa83
added possibility to specify prefered file type for external commands
nadvornik
parents:
140
diff
changeset
|
284 |
|
b2266996fa83
added possibility to specify prefered file type for external commands
nadvornik
parents:
140
diff
changeset
|
285 if (!work) |
|
b2266996fa83
added possibility to specify prefered file type for external commands
nadvornik
parents:
140
diff
changeset
|
286 p = fd->path; |
|
b2266996fa83
added possibility to specify prefered file type for external commands
nadvornik
parents:
140
diff
changeset
|
287 else |
|
b2266996fa83
added possibility to specify prefered file type for external commands
nadvornik
parents:
140
diff
changeset
|
288 { |
|
b2266996fa83
added possibility to specify prefered file type for external commands
nadvornik
parents:
140
diff
changeset
|
289 while(work) |
|
b2266996fa83
added possibility to specify prefered file type for external commands
nadvornik
parents:
140
diff
changeset
|
290 { |
|
b2266996fa83
added possibility to specify prefered file type for external commands
nadvornik
parents:
140
diff
changeset
|
291 gchar *ext = work->data; |
|
b2266996fa83
added possibility to specify prefered file type for external commands
nadvornik
parents:
140
diff
changeset
|
292 work = work->next; |
|
b2266996fa83
added possibility to specify prefered file type for external commands
nadvornik
parents:
140
diff
changeset
|
293 |
|
b2266996fa83
added possibility to specify prefered file type for external commands
nadvornik
parents:
140
diff
changeset
|
294 if (strcmp(ext, "*") == 0 || |
|
b2266996fa83
added possibility to specify prefered file type for external commands
nadvornik
parents:
140
diff
changeset
|
295 strcasecmp(ext, fd->extension) == 0) |
|
b2266996fa83
added possibility to specify prefered file type for external commands
nadvornik
parents:
140
diff
changeset
|
296 { |
|
b2266996fa83
added possibility to specify prefered file type for external commands
nadvornik
parents:
140
diff
changeset
|
297 p = fd->path; |
|
b2266996fa83
added possibility to specify prefered file type for external commands
nadvornik
parents:
140
diff
changeset
|
298 break; |
|
b2266996fa83
added possibility to specify prefered file type for external commands
nadvornik
parents:
140
diff
changeset
|
299 } |
|
b2266996fa83
added possibility to specify prefered file type for external commands
nadvornik
parents:
140
diff
changeset
|
300 |
|
b2266996fa83
added possibility to specify prefered file type for external commands
nadvornik
parents:
140
diff
changeset
|
301 GList *work2 = fd->sidecar_files; |
|
b2266996fa83
added possibility to specify prefered file type for external commands
nadvornik
parents:
140
diff
changeset
|
302 |
|
b2266996fa83
added possibility to specify prefered file type for external commands
nadvornik
parents:
140
diff
changeset
|
303 while(work2) |
|
b2266996fa83
added possibility to specify prefered file type for external commands
nadvornik
parents:
140
diff
changeset
|
304 { |
|
b2266996fa83
added possibility to specify prefered file type for external commands
nadvornik
parents:
140
diff
changeset
|
305 FileData *sfd = work2->data; |
|
b2266996fa83
added possibility to specify prefered file type for external commands
nadvornik
parents:
140
diff
changeset
|
306 work2 = work2->next; |
|
b2266996fa83
added possibility to specify prefered file type for external commands
nadvornik
parents:
140
diff
changeset
|
307 |
|
b2266996fa83
added possibility to specify prefered file type for external commands
nadvornik
parents:
140
diff
changeset
|
308 if (strcasecmp(ext, sfd->extension) == 0) |
|
b2266996fa83
added possibility to specify prefered file type for external commands
nadvornik
parents:
140
diff
changeset
|
309 { |
|
b2266996fa83
added possibility to specify prefered file type for external commands
nadvornik
parents:
140
diff
changeset
|
310 p = sfd->path; |
|
b2266996fa83
added possibility to specify prefered file type for external commands
nadvornik
parents:
140
diff
changeset
|
311 break; |
|
b2266996fa83
added possibility to specify prefered file type for external commands
nadvornik
parents:
140
diff
changeset
|
312 } |
|
b2266996fa83
added possibility to specify prefered file type for external commands
nadvornik
parents:
140
diff
changeset
|
313 } |
|
b2266996fa83
added possibility to specify prefered file type for external commands
nadvornik
parents:
140
diff
changeset
|
314 if (p) break; |
|
b2266996fa83
added possibility to specify prefered file type for external commands
nadvornik
parents:
140
diff
changeset
|
315 } |
|
b2266996fa83
added possibility to specify prefered file type for external commands
nadvornik
parents:
140
diff
changeset
|
316 string_list_free(ext_list); |
|
b2266996fa83
added possibility to specify prefered file type for external commands
nadvornik
parents:
140
diff
changeset
|
317 if (!p) return NULL; |
|
b2266996fa83
added possibility to specify prefered file type for external commands
nadvornik
parents:
140
diff
changeset
|
318 } |
| 138 | 319 } |
| 140 | 320 else if (type == PATH_DEST) |
| 138 | 321 { |
| 322 if (fd->change && fd->change->dest) | |
| 323 p = fd->change->dest; | |
| 324 else | |
| 325 p = ""; | |
| 326 } | |
|
123
3602a4aa7c71
Sat Dec 2 20:15:22 2006 John Ellis <johne@verizon.net>
gqview
parents:
60
diff
changeset
|
327 while (*p != '\0') |
|
3602a4aa7c71
Sat Dec 2 20:15:22 2006 John Ellis <johne@verizon.net>
gqview
parents:
60
diff
changeset
|
328 { |
|
3602a4aa7c71
Sat Dec 2 20:15:22 2006 John Ellis <johne@verizon.net>
gqview
parents:
60
diff
changeset
|
329 /* must escape \, ", `, and $ to avoid problems, |
|
3602a4aa7c71
Sat Dec 2 20:15:22 2006 John Ellis <johne@verizon.net>
gqview
parents:
60
diff
changeset
|
330 * we assume system shell supports bash-like escaping |
|
3602a4aa7c71
Sat Dec 2 20:15:22 2006 John Ellis <johne@verizon.net>
gqview
parents:
60
diff
changeset
|
331 */ |
|
3602a4aa7c71
Sat Dec 2 20:15:22 2006 John Ellis <johne@verizon.net>
gqview
parents:
60
diff
changeset
|
332 if (strchr("\\\"`$", *p) != NULL) |
|
3602a4aa7c71
Sat Dec 2 20:15:22 2006 John Ellis <johne@verizon.net>
gqview
parents:
60
diff
changeset
|
333 { |
|
3602a4aa7c71
Sat Dec 2 20:15:22 2006 John Ellis <johne@verizon.net>
gqview
parents:
60
diff
changeset
|
334 string = g_string_append_c(string, '\\'); |
|
3602a4aa7c71
Sat Dec 2 20:15:22 2006 John Ellis <johne@verizon.net>
gqview
parents:
60
diff
changeset
|
335 } |
|
3602a4aa7c71
Sat Dec 2 20:15:22 2006 John Ellis <johne@verizon.net>
gqview
parents:
60
diff
changeset
|
336 string = g_string_append_c(string, *p); |
|
3602a4aa7c71
Sat Dec 2 20:15:22 2006 John Ellis <johne@verizon.net>
gqview
parents:
60
diff
changeset
|
337 p++; |
|
3602a4aa7c71
Sat Dec 2 20:15:22 2006 John Ellis <johne@verizon.net>
gqview
parents:
60
diff
changeset
|
338 } |
|
3602a4aa7c71
Sat Dec 2 20:15:22 2006 John Ellis <johne@verizon.net>
gqview
parents:
60
diff
changeset
|
339 |
|
3602a4aa7c71
Sat Dec 2 20:15:22 2006 John Ellis <johne@verizon.net>
gqview
parents:
60
diff
changeset
|
340 pathl = path_from_utf8(string->str); |
|
3602a4aa7c71
Sat Dec 2 20:15:22 2006 John Ellis <johne@verizon.net>
gqview
parents:
60
diff
changeset
|
341 g_string_free(string, TRUE); |
|
3602a4aa7c71
Sat Dec 2 20:15:22 2006 John Ellis <johne@verizon.net>
gqview
parents:
60
diff
changeset
|
342 |
|
3602a4aa7c71
Sat Dec 2 20:15:22 2006 John Ellis <johne@verizon.net>
gqview
parents:
60
diff
changeset
|
343 return pathl; |
|
3602a4aa7c71
Sat Dec 2 20:15:22 2006 John Ellis <johne@verizon.net>
gqview
parents:
60
diff
changeset
|
344 } |
|
3602a4aa7c71
Sat Dec 2 20:15:22 2006 John Ellis <johne@verizon.net>
gqview
parents:
60
diff
changeset
|
345 |
| 9 | 346 |
| 347 /* | |
| 348 * The supported macros for editor commands: | |
| 349 * | |
| 350 * %f first occurence replaced by quoted sequence of filenames, command is run once. | |
| 351 * only one occurence of this macro is supported. | |
| 352 * ([ls %f] results in [ls "file1" "file2" ... "lastfile"]) | |
| 353 * %p command is run for each filename in turn, each instance replaced with single filename. | |
| 354 * multiple occurences of this macro is supported for complex shell commands. | |
| 355 * This macro will BLOCK THE APPLICATION until it completes, since command is run once | |
| 356 * for every file in syncronous order. To avoid blocking add the %v macro, below. | |
| 357 * ([ls %p] results in [ls "file1"], [ls "file2"] ... [ls "lastfile"]) | |
| 358 * none if no macro is supplied, the result is equivalent to "command %f" | |
| 359 * ([ls] results in [ls "file1" "file2" ... "lastfile"]) | |
| 360 * | |
| 361 * Only one of the macros %f or %p may be used in a given commmand. | |
| 362 * | |
|
60
9c0c402b0ef3
Mon Jun 13 17:31:46 2005 John Ellis <johne@verizon.net>
gqview
parents:
9
diff
changeset
|
363 * %v must be the first two characters[1] in a command, causes a window to display |
| 9 | 364 * showing the output of the command(s). |
| 365 * %V same as %v except in the case of %p only displays a window for multiple files, | |
| 366 * operating on a single file is suppresses the output dialog. | |
|
60
9c0c402b0ef3
Mon Jun 13 17:31:46 2005 John Ellis <johne@verizon.net>
gqview
parents:
9
diff
changeset
|
367 * |
|
9c0c402b0ef3
Mon Jun 13 17:31:46 2005 John Ellis <johne@verizon.net>
gqview
parents:
9
diff
changeset
|
368 * %w must be first two characters in a command, presence will disable full screen |
|
9c0c402b0ef3
Mon Jun 13 17:31:46 2005 John Ellis <johne@verizon.net>
gqview
parents:
9
diff
changeset
|
369 * from exiting upon invocation. |
|
9c0c402b0ef3
Mon Jun 13 17:31:46 2005 John Ellis <johne@verizon.net>
gqview
parents:
9
diff
changeset
|
370 * |
|
9c0c402b0ef3
Mon Jun 13 17:31:46 2005 John Ellis <johne@verizon.net>
gqview
parents:
9
diff
changeset
|
371 * |
|
9c0c402b0ef3
Mon Jun 13 17:31:46 2005 John Ellis <johne@verizon.net>
gqview
parents:
9
diff
changeset
|
372 * [1] Note: %v,%V may also be preceded by "%w". |
| 9 | 373 */ |
| 140 | 374 |
| 375 | |
| 376 gint editor_command_parse(const gchar *template, GList *list, gchar **output) | |
| 9 | 377 { |
| 140 | 378 gint flags = 0; |
| 379 const gchar *p = template; | |
| 380 GString *result = NULL; | |
| 381 gchar *extensions = NULL; | |
| 382 GList *work; | |
| 383 | |
| 384 if (output) | |
| 385 result = g_string_new(""); | |
| 386 | |
| 387 if (!template || template[0] == '\0') | |
| 388 { | |
| 389 flags |= EDITOR_ERROR_EMPTY; | |
| 390 goto err; | |
| 391 } | |
| 9 | 392 |
| 140 | 393 |
| 394 /* global flags */ | |
| 395 while (*p == '%') | |
| 396 { | |
| 397 switch (*++p) | |
| 398 { | |
| 399 case 'w': | |
| 400 flags |= EDITOR_KEEP_FS; | |
| 401 p++; | |
| 402 break; | |
| 403 case 'v': | |
| 404 flags |= EDITOR_VERBOSE; | |
| 405 p++; | |
| 406 break; | |
| 407 case 'V': | |
| 408 flags |= EDITOR_VERBOSE_MULTI; | |
| 409 p++; | |
| 410 break; | |
| 411 } | |
| 412 } | |
| 413 | |
| 414 /* command */ | |
| 415 | |
| 416 while (*p) | |
| 417 { | |
| 418 if (*p != '%') | |
| 419 { | |
| 420 if (output) result = g_string_append_c(result, *p); | |
| 421 } | |
| 422 else /* *p == '%' */ | |
| 423 { | |
| 424 extensions = NULL; | |
| 425 gchar *pathl = NULL; | |
| 9 | 426 |
| 140 | 427 p++; |
| 428 | |
|
147
b2266996fa83
added possibility to specify prefered file type for external commands
nadvornik
parents:
140
diff
changeset
|
429 /* for example "%f" or "%{.crw;.raw;.cr2}f" */ |
| 140 | 430 if (*p == '{') |
| 431 { | |
| 432 p++; | |
| 433 gchar *end = strchr(p, '}'); | |
| 434 if (!end) | |
| 435 { | |
| 436 flags |= EDITOR_ERROR_SYNTAX; | |
| 437 goto err; | |
| 438 } | |
| 439 | |
| 440 extensions = g_strndup(p, end - p); | |
| 441 p = end + 1; | |
| 442 } | |
| 443 | |
| 444 switch (*p) | |
| 445 { | |
| 446 case 'd': | |
| 447 flags |= EDITOR_DEST; | |
| 448 case 'p': | |
| 449 flags |= EDITOR_FOR_EACH; | |
| 450 if (flags & EDITOR_SINGLE_COMMAND) | |
| 451 { | |
| 452 flags |= EDITOR_ERROR_INCOMPATIBLE; | |
| 453 goto err; | |
| 454 } | |
| 455 if (output) | |
| 456 { | |
| 457 /* use the first file from the list */ | |
| 458 if (!list || !list->data) | |
| 459 { | |
| 460 flags |= EDITOR_ERROR_NO_FILE; | |
| 461 goto err; | |
| 462 } | |
| 463 pathl = editor_command_path_parse((FileData *)list->data, (*p == 'd') ? PATH_DEST : PATH_FILE, extensions); | |
| 464 if (!pathl) | |
| 465 { | |
| 466 flags |= EDITOR_ERROR_NO_FILE; | |
| 467 goto err; | |
| 468 } | |
| 469 result = g_string_append_c(result, '"'); | |
| 470 result = g_string_append(result, pathl); | |
| 471 g_free(pathl); | |
| 472 result = g_string_append_c(result, '"'); | |
| 473 } | |
| 474 break; | |
|
60
9c0c402b0ef3
Mon Jun 13 17:31:46 2005 John Ellis <johne@verizon.net>
gqview
parents:
9
diff
changeset
|
475 |
| 140 | 476 case 'f': |
| 477 flags |= EDITOR_SINGLE_COMMAND; | |
| 478 if (flags & (EDITOR_FOR_EACH | EDITOR_DEST)) | |
| 479 { | |
| 480 flags |= EDITOR_ERROR_INCOMPATIBLE; | |
| 481 goto err; | |
| 482 } | |
| 483 | |
| 484 if (output) | |
| 485 { | |
| 486 /* use whole list */ | |
| 487 GList *work = list; | |
| 488 gboolean ok = FALSE; | |
| 489 while (work) | |
| 490 { | |
| 491 FileData *fd = work->data; | |
| 492 pathl = editor_command_path_parse(fd, PATH_FILE, extensions); | |
| 493 | |
| 494 if (pathl) | |
| 495 { | |
| 496 ok = TRUE; | |
| 497 if (work != list) g_string_append_c(result, ' '); | |
| 498 result = g_string_append_c(result, '"'); | |
| 499 result = g_string_append(result, pathl); | |
| 500 g_free(pathl); | |
| 501 result = g_string_append_c(result, '"'); | |
| 502 } | |
| 503 work = work->next; | |
| 504 } | |
| 505 if (!ok) | |
| 506 { | |
| 507 flags |= EDITOR_ERROR_NO_FILE; | |
| 508 goto err; | |
| 509 } | |
| 510 } | |
| 511 break; | |
| 512 default: | |
| 513 flags |= EDITOR_ERROR_SYNTAX; | |
| 514 goto err; | |
| 515 } | |
| 516 if (extensions) g_free(extensions); | |
| 517 extensions = NULL; | |
| 518 } | |
| 519 p++; | |
| 9 | 520 } |
| 521 | |
| 140 | 522 if (output) *output = g_string_free(result, FALSE); |
| 523 return flags; | |
| 524 | |
| 525 | |
| 526 err: | |
| 527 if (output) | |
| 9 | 528 { |
| 140 | 529 g_string_free(result, TRUE); |
| 530 *output = NULL; | |
| 531 } | |
| 532 if (extensions) g_free(extensions); | |
| 533 return flags; | |
| 534 } | |
| 535 | |
| 536 static void editor_child_exit_cb (GPid pid, gint status, gpointer data) | |
| 537 { | |
| 538 EditorData *ed = data; | |
| 539 g_spawn_close_pid(pid); | |
| 540 ed->pid = -1; | |
| 541 | |
| 542 editor_command_next_finish(ed, status); | |
| 543 } | |
| 544 | |
| 545 | |
| 546 static gint editor_command_one(const gchar *template, GList *list, EditorData *ed) | |
| 547 { | |
| 548 gchar *command; | |
| 549 gchar *working_directory; | |
| 550 FileData *fd = list->data; | |
| 551 gchar *args[4]; | |
| 552 GPid pid; | |
| 553 gint standard_output; | |
| 554 gint standard_error; | |
| 555 gboolean ok; | |
| 556 | |
| 557 | |
| 558 ed->pid = -1; | |
| 559 | |
| 560 working_directory = remove_level_from_path(fd->path); | |
| 561 | |
| 562 ed->flags = editor_command_parse(template, list, &command); | |
| 563 | |
| 564 ok = !(ed->flags & EDITOR_ERROR_MASK); | |
| 565 | |
| 566 | |
| 567 args[0] = COMMAND_SHELL; | |
| 568 args[1] = COMMAND_OPT; | |
| 569 args[2] = command; | |
| 570 args[3] = NULL; | |
| 571 | |
| 572 if (ok) | |
| 573 { | |
| 574 ok = g_spawn_async_with_pipes(working_directory, args, NULL, | |
| 575 G_SPAWN_DO_NOT_REAP_CHILD, /* GSpawnFlags */ | |
| 576 NULL, NULL, | |
| 577 &pid, | |
| 578 NULL, | |
| 579 ed->vd ? &standard_output : NULL, | |
| 580 ed->vd ? &standard_error : NULL, | |
| 581 NULL); | |
| 582 | |
| 583 if (!ok) ed->flags |= EDITOR_ERROR_CANT_EXEC; | |
| 584 } | |
| 585 | |
| 586 if (ok) | |
| 587 { | |
| 588 g_child_watch_add(pid, editor_child_exit_cb, ed); | |
| 589 ed->pid = pid; | |
| 590 } | |
| 591 | |
| 592 | |
| 593 if (ed->vd) | |
| 594 { | |
| 595 | |
| 596 if (!ok) | |
| 9 | 597 { |
| 140 | 598 gchar *buf; |
| 599 | |
|
147
b2266996fa83
added possibility to specify prefered file type for external commands
nadvornik
parents:
140
diff
changeset
|
600 buf = g_strdup_printf(_("Failed to run command:\n%s\n"), template); |
| 140 | 601 editor_verbose_window_fill(ed->vd, buf, strlen(buf)); |
| 602 g_free(buf); | |
| 603 | |
| 604 } | |
| 605 else | |
| 606 { | |
| 607 | |
| 608 GIOChannel *channel_output; | |
| 609 GIOChannel *channel_error; | |
| 610 channel_output = g_io_channel_unix_new(standard_output); | |
| 611 g_io_channel_set_flags(channel_output, G_IO_FLAG_NONBLOCK, NULL); | |
| 612 | |
| 613 g_io_add_watch_full(channel_output, G_PRIORITY_HIGH, G_IO_IN | G_IO_ERR | G_IO_HUP, | |
| 614 editor_verbose_io_cb, ed, NULL); | |
| 615 g_io_channel_unref(channel_output); | |
| 616 | |
| 617 channel_error = g_io_channel_unix_new(standard_error); | |
| 618 g_io_channel_set_flags(channel_error, G_IO_FLAG_NONBLOCK, NULL); | |
| 619 | |
| 620 g_io_add_watch_full(channel_error, G_PRIORITY_HIGH, G_IO_IN | G_IO_ERR | G_IO_HUP, | |
| 621 editor_verbose_io_cb, ed, NULL); | |
| 622 g_io_channel_unref(channel_error); | |
| 623 } | |
| 624 } | |
| 625 | |
| 9 | 626 |
| 140 | 627 |
| 628 g_free(command); | |
| 629 g_free(working_directory); | |
| 630 | |
| 631 return ed->flags & EDITOR_ERROR_MASK; | |
| 632 } | |
| 633 | |
| 634 static gint editor_command_next_start(EditorData *ed) | |
| 635 { | |
| 636 | |
| 637 if (ed->vd) editor_verbose_window_fill(ed->vd, "\n", 1); | |
| 638 | |
| 639 if (ed->list && ed->count < ed->total) | |
| 640 { | |
| 641 FileData *fd; | |
| 642 gint error; | |
| 643 | |
| 644 fd = ed->list->data; | |
| 645 | |
| 646 if (ed->vd) | |
| 647 { | |
| 648 editor_verbose_window_progress(ed, (ed->flags & EDITOR_FOR_EACH) ? fd->path : _("running...")); | |
| 649 } | |
| 650 ed->count++; | |
| 651 | |
| 652 error = editor_command_one(ed->command_template, ed->list, ed); | |
| 653 if (!error && ed->vd) | |
| 654 { | |
| 655 gtk_widget_set_sensitive(ed->vd->button_stop, (ed->list != NULL) ); | |
| 656 if (ed->flags & EDITOR_FOR_EACH) | |
| 9 | 657 { |
| 140 | 658 editor_verbose_window_fill(ed->vd, fd->path, strlen(fd->path)); |
| 659 editor_verbose_window_fill(ed->vd, "\n", 1); | |
| 9 | 660 } |
| 661 } | |
| 140 | 662 |
| 663 if (!error) | |
| 664 return 0; | |
| 665 else | |
| 666 /* command was not started, call the finish immediately */ | |
| 667 return editor_command_next_finish(ed, 0); | |
| 668 } | |
| 669 | |
| 670 /* everything is done */ | |
| 671 editor_command_done(ed); | |
| 672 | |
| 673 } | |
| 674 | |
| 675 static gint editor_command_next_finish(EditorData *ed, gint status) | |
| 676 { | |
| 677 gint cont = ed->stopping ? EDITOR_CB_SKIP : EDITOR_CB_CONTINUE; | |
| 678 | |
| 679 if (status) | |
| 680 ed->flags |= EDITOR_ERROR_STATUS; | |
| 681 | |
| 682 if (ed->flags & EDITOR_FOR_EACH) | |
| 683 { | |
| 684 /* handle the first element from the list */ | |
| 685 GList *fd_element = ed->list; | |
| 686 ed->list = g_list_remove_link(ed->list, fd_element); | |
| 687 if (ed->callback) | |
| 688 cont = ed->callback(ed->list ? ed : NULL, ed->flags, fd_element, ed->data); | |
| 689 filelist_free(fd_element); | |
| 9 | 690 } |
| 691 else | |
| 692 { | |
| 140 | 693 /* handle whole list */ |
| 694 if (ed->callback) | |
| 695 cont = ed->callback(NULL, ed->flags, ed->list, ed->data); | |
| 696 filelist_free(ed->list); | |
| 697 ed->list = NULL; | |
| 698 } | |
| 9 | 699 |
| 140 | 700 if (cont == EDITOR_CB_SUSPEND) |
| 701 return ed->flags & EDITOR_ERROR_MASK; | |
| 702 else if (cont == EDITOR_CB_SKIP) | |
| 703 return editor_command_done(ed); | |
| 704 else | |
| 705 return editor_command_next_start(ed); | |
| 706 | |
| 707 } | |
| 9 | 708 |
| 140 | 709 static gint editor_command_done(EditorData *ed) |
| 710 { | |
| 711 gint flags; | |
| 712 const gchar *text; | |
| 9 | 713 |
| 140 | 714 if (ed->vd) |
| 715 { | |
| 716 if (ed->count == ed->total) | |
| 9 | 717 { |
| 140 | 718 text = _("done"); |
| 9 | 719 } |
| 720 else | |
| 721 { | |
| 140 | 722 text = _("stopped by user"); |
| 9 | 723 } |
| 140 | 724 editor_verbose_window_progress(ed, text); |
| 725 editor_verbose_window_enable_close(ed->vd); | |
| 726 } | |
| 727 | |
| 728 /* free the not-handled items */ | |
| 729 if (ed->list) | |
| 730 { | |
| 731 ed->flags |= EDITOR_ERROR_SKIPPED; | |
| 732 if (ed->callback) ed->callback(NULL, ed->flags, ed->list, ed->data); | |
| 733 filelist_free(ed->list); | |
| 734 ed->list = NULL; | |
| 735 } | |
| 9 | 736 |
| 140 | 737 ed->count = 0; |
| 738 | |
| 739 flags = ed->flags & EDITOR_ERROR_MASK; | |
| 740 | |
| 741 if (!ed->vd) editor_data_free(ed); | |
| 742 | |
| 743 return flags; | |
| 744 } | |
| 745 | |
| 746 void editor_resume(gpointer ed) | |
| 747 { | |
| 748 editor_command_next_start(ed); | |
| 749 } | |
| 750 void editor_skip(gpointer ed) | |
| 751 { | |
| 752 editor_command_done(ed); | |
| 9 | 753 } |
| 754 | |
| 140 | 755 static gint editor_command_start(const gchar *template, const gchar *text, GList *list, EditorCallback cb, gpointer data) |
| 756 { | |
| 757 EditorData *ed; | |
| 758 gint flags = editor_command_parse(template, NULL, NULL); | |
| 759 | |
| 760 if (flags & EDITOR_ERROR_MASK) return flags & EDITOR_ERROR_MASK; | |
| 761 | |
| 762 ed = g_new0(EditorData, 1); | |
| 763 ed->list = filelist_copy(list); | |
| 764 ed->flags = flags; | |
| 765 ed->command_template = g_strdup(template); | |
| 766 ed->total = (flags & EDITOR_SINGLE_COMMAND) ? 1 : g_list_length(list); | |
| 767 ed->count = 0; | |
| 768 ed->stopping = FALSE; | |
| 769 ed->callback = cb; | |
| 770 ed->data = data; | |
| 771 | |
| 772 if ((flags & EDITOR_VERBOSE_MULTI) && list && list->next) | |
| 773 flags |= EDITOR_VERBOSE; | |
| 774 | |
| 775 | |
| 776 if (flags & EDITOR_VERBOSE) | |
| 777 editor_verbose_window(ed, text); | |
| 778 | |
| 779 editor_command_next_start(ed); | |
| 780 /* errors from editor_command_next_start will be handled via callback */ | |
| 781 return flags & EDITOR_ERROR_MASK; | |
| 782 } | |
| 783 | |
| 784 gint start_editor_from_filelist_full(gint n, GList *list, EditorCallback cb, gpointer data) | |
| 9 | 785 { |
| 786 gchar *command; | |
| 140 | 787 gint error; |
| 9 | 788 |
| 789 if (n < 0 || n >= GQVIEW_EDITOR_SLOTS || !list || | |
| 790 !editor_command[n] || | |
|
134
9009856628f7
started implementation of external commands; external Delete should work
nadvornik
parents:
123
diff
changeset
|
791 strlen(editor_command[n]) == 0) return FALSE; |
| 9 | 792 |
| 793 command = g_locale_from_utf8(editor_command[n], -1, NULL, NULL, NULL); | |
| 140 | 794 error = editor_command_start(command, editor_name[n], list, cb, data); |
| 9 | 795 g_free(command); |
| 140 | 796 return error; |
| 9 | 797 } |
| 798 | |
| 140 | 799 gint start_editor_from_filelist(gint n, GList *list) |
| 800 { | |
| 801 return start_editor_from_filelist_full(n, list, NULL, NULL); | |
| 802 } | |
| 803 | |
| 804 | |
| 805 gint start_editor_from_file_full(gint n, FileData *fd, EditorCallback cb, gpointer data) | |
| 9 | 806 { |
| 807 GList *list; | |
| 140 | 808 gint error; |
| 9 | 809 |
| 138 | 810 if (!fd) return FALSE; |
| 9 | 811 |
| 138 | 812 list = g_list_append(NULL, fd); |
| 140 | 813 error = start_editor_from_filelist_full(n, list, cb, data); |
| 9 | 814 g_list_free(list); |
| 140 | 815 return error; |
| 9 | 816 } |
|
60
9c0c402b0ef3
Mon Jun 13 17:31:46 2005 John Ellis <johne@verizon.net>
gqview
parents:
9
diff
changeset
|
817 |
| 140 | 818 gint start_editor_from_file(gint n, FileData *fd) |
| 136 | 819 { |
| 140 | 820 return start_editor_from_file_full(n, fd, NULL, NULL); |
| 136 | 821 } |
| 822 | |
|
60
9c0c402b0ef3
Mon Jun 13 17:31:46 2005 John Ellis <johne@verizon.net>
gqview
parents:
9
diff
changeset
|
823 gint editor_window_flag_set(gint n) |
|
9c0c402b0ef3
Mon Jun 13 17:31:46 2005 John Ellis <johne@verizon.net>
gqview
parents:
9
diff
changeset
|
824 { |
|
9c0c402b0ef3
Mon Jun 13 17:31:46 2005 John Ellis <johne@verizon.net>
gqview
parents:
9
diff
changeset
|
825 if (n < 0 || n >= GQVIEW_EDITOR_SLOTS || |
|
9c0c402b0ef3
Mon Jun 13 17:31:46 2005 John Ellis <johne@verizon.net>
gqview
parents:
9
diff
changeset
|
826 !editor_command[n] || |
|
9c0c402b0ef3
Mon Jun 13 17:31:46 2005 John Ellis <johne@verizon.net>
gqview
parents:
9
diff
changeset
|
827 strlen(editor_command[n]) == 0) return TRUE; |
|
9c0c402b0ef3
Mon Jun 13 17:31:46 2005 John Ellis <johne@verizon.net>
gqview
parents:
9
diff
changeset
|
828 |
| 140 | 829 return (editor_command_parse(editor_command[n], NULL, NULL) & EDITOR_KEEP_FS); |
|
60
9c0c402b0ef3
Mon Jun 13 17:31:46 2005 John Ellis <johne@verizon.net>
gqview
parents:
9
diff
changeset
|
830 } |
|
9c0c402b0ef3
Mon Jun 13 17:31:46 2005 John Ellis <johne@verizon.net>
gqview
parents:
9
diff
changeset
|
831 |
|
9c0c402b0ef3
Mon Jun 13 17:31:46 2005 John Ellis <johne@verizon.net>
gqview
parents:
9
diff
changeset
|
832 |
| 140 | 833 const gchar *editor_get_error_str(gint flags) |
| 834 { | |
| 835 if (flags & EDITOR_ERROR_EMPTY) return _("Editor template is empty."); | |
| 836 if (flags & EDITOR_ERROR_SYNTAX) return _("Editor template has incorrect syntax."); | |
| 837 if (flags & EDITOR_ERROR_INCOMPATIBLE) return _("Editor template uses incompatible macros."); | |
| 838 if (flags & EDITOR_ERROR_NO_FILE) return _("Can't find matching file type."); | |
| 839 if (flags & EDITOR_ERROR_CANT_EXEC) return _("Can't execute external editor."); | |
| 840 if (flags & EDITOR_ERROR_STATUS) return _("External editor returned error status."); | |
| 841 if (flags & EDITOR_ERROR_SKIPPED) return _("File was skipped."); | |
| 842 return _("Unknown error."); | |
| 843 } |
