1 #define _XOPEN_SOURCE 500
4 #include <readline/readline.h>
5 #include <readline/history.h>
12 #include "shigofumi.h"
14 #include "completition.h"
16 static _Bool progress_started
= 0;
17 static _Bool progress_abort_requested
= 0;
18 static char *upload_current_formated
= NULL
;
19 static char *upload_total_formated
= NULL
;
20 static char *download_current_formated
= NULL
;
21 static char *download_total_formated
= NULL
;
23 /* Generates possible completions for base commands set
24 * @text is partial user input word
25 * @state is 0 for first completion attempt, non-zero otherwise
26 * @return next suggested complete word or NULL if no possibility */
27 static char *shi_command_generator(const char *text
, int state
) {
28 static size_t text_length
, index
;
32 text_length
= strlen(text
);
36 if (!commands
) return NULL
;
38 while ((command_name
= (*commands
)[index
++].name
)) {
39 if (!strncmp(command_name
, text
, text_length
))
40 return strdup(command_name
);
47 /* Generates possible message ID completions
48 * @text_locale is partial user input word in locale encoding
49 * @state is 0 for first completion attempt, non-zero otherwise
50 * @return next suggested complete word or NULL if no possibility */
51 static char *shi_msgid_generator(const char *text_locale
, int state
) {
52 static char *text
= NULL
;
53 static size_t text_length
;
54 static struct isds_list
*item
;
59 text
= locale2utf8(text_locale
);
60 if (text
) text_length
= strlen(text
);
65 for (; item
; item
= item
->next
) {
66 if (item
->data
&& ((struct isds_message
*)item
->data
)->envelope
) {
67 id
= ((struct isds_message
*)item
->data
)->envelope
->dmID
;
68 if (id
&& !strncmp(id
, text
, text_length
)) {
70 return utf82locale(id
);
80 /* Generates possible document ID completions
81 * @text is partial user input word
82 * @state is 0 for first completion attempt, non-zero otherwise
83 * @return next suggested complete word or NULL if no possibility */
84 static char *shi_docid_generator(const char *text
, int state
) {
85 static size_t text_length
;
86 static struct isds_list
*item
;
91 text_length
= strlen(text
);
93 item
= message
->documents
;
99 for (; item
; item
= item
->next
) {
104 shi_asprintf(&document_id
, "%d", order
);
113 /* Generates possible box ID completions
114 * @text_locale is partial user input word in locale encoding
115 * @state is 0 for first completion attempt, non-zero otherwise
116 * @return next suggested complete word or NULL if no possibility */
117 static char *shi_boxid_generator(const char *text_locale
, int state
) {
118 static char *text
= NULL
;
119 static size_t text_length
;
120 static struct isds_list
*item
;
125 text
= locale2utf8(text_locale
);
126 if (text
) text_length
= strlen(text
);
131 for (; item
; item
= item
->next
) {
132 if (item
->data
&& ((struct isds_DbOwnerInfo
*)item
->data
)->dbID
) {
133 id
= ((struct isds_DbOwnerInfo
*)item
->data
)->dbID
;
134 if (id
&& !strncmp(id
, text
, text_length
)) {
136 return utf82locale(id
);
146 struct command
*find_command(const char *text
) {
148 if (!text
|| !commands
) return NULL
;
150 for (index
= 0; (*commands
)[index
].name
; index
++) {
151 if (!strcmp((*commands
)[index
].name
, text
))
152 return &(*commands
)[index
];
159 arg_type
find_arg_type(const char *line
) {
163 if (!line
|| !commands
) return ARGTYPE_NONE
;
165 for (index
= 0; (*commands
)[index
].name
; index
++) {
166 length
= strlen((*commands
)[index
].name
);
167 if (rl_point
>= length
&&
168 !strncmp(line
, (*commands
)[index
].name
, length
) &&
169 isspace(line
[length
]))
170 return (*commands
)[index
].arg
;
177 static char **shi_completition_none(const char *text
, int start
, int end
) {
178 rl_attempted_completion_over
= 1;
182 static char **shi_completition_command(const char *text
, int start
, int end
) {
183 rl_attempted_completion_over
= 1;
187 return rl_completion_matches(text
, shi_command_generator
);
189 /* Command argument */
190 switch (find_arg_type(rl_line_buffer
)) {
191 case ARGTYPE_COMMAND
:
192 return rl_completion_matches(text
, shi_command_generator
);
194 rl_attempted_completion_over
= 0;
197 return rl_completion_matches(text
, shi_msgid_generator
);
199 return rl_completion_matches(text
, shi_docid_generator
);
201 return rl_completion_matches(text
, shi_boxid_generator
);
203 rl_attempted_completion_over
= 1;
209 /* Compares two command names */
210 int commandcmp(const void *a
, const void *b
) {
212 return strcoll(((struct command
*)a
)->name
, ((struct command
*)b
)->name
);
216 /* Make list of currently available commands */
217 static int build_command_list(const struct command unsorted_commands
[]) {
220 for (count
= 0; unsorted_commands
[count
].name
; count
++);
223 commands
= calloc(count
+ 1, sizeof(struct command
));
226 _("Fatal error: Non enough memory to sort commands\n"));
230 for (i
= 0; i
< count
; i
++) {
231 (*commands
)[i
].name
= unsorted_commands
[i
].name
;
232 (*commands
)[i
].function
= unsorted_commands
[i
].function
;
233 (*commands
)[i
].description
= unsorted_commands
[i
].description
;
234 (*commands
)[i
].usage
= unsorted_commands
[i
].usage
;
235 (*commands
)[i
].arg
= unsorted_commands
[i
].arg
;
238 qsort(*commands
, count
, sizeof(struct command
), commandcmp
);
244 /* Switch completion function */
245 int select_completition(const completition_type completition
) {
246 switch (completition
) {
248 rl_attempted_completion_function
= shi_completition_none
;
251 if (build_command_list(base_commands
)) return -1;
252 rl_attempted_completion_function
= shi_completition_command
;
255 if (build_command_list(message_commands
)) return -1;
256 rl_attempted_completion_function
= shi_completition_command
;
259 if (build_command_list(list_commands
)) return -1;
260 rl_attempted_completion_function
= shi_completition_command
;
267 /* Free list of chars recursively */
268 void argv_free(char **argv
) {
270 for (char **arg
= argv
; *arg
; arg
++)
278 /* Decides whether character at @index offset of @text is quoted */
279 int shi_char_is_quoted(char *text
, int index
) {
283 fprintf(stderr
, "shi_char_is_quoted(text=%s, index=%d)\n", text
, index
);
284 if (!text
|| index
< 0) return 0;
286 for (i
= 0; i
<= index
; i
++) {
287 if (text
[i
] == ESCAPER
&& !escaped
) {
298 /* Escapes file name */
299 char *shi_quote_filename(char *text
, int match_type
, char *quote_pointer
) {
300 char *quoted_text
= NULL
;
304 "shi_quote_filename(text=%s, match_type=%d, quote_pointer='%c')\n",
305 text
, match_type
, (quote_pointer
)?*quote_pointer
:0);
306 if (!text
) return NULL
;
308 quoted_text
= malloc(strlen(text
) * 2 + 1);
309 if (!quoted_text
) return strdup(text
);
311 for (i
= 0; *text
; text
++, i
++) {
312 if (*text
== ESCAPER
) quoted_text
[i
++] = ESCAPER
;
313 if (isspace(*text
)) quoted_text
[i
++] = ESCAPER
;
314 quoted_text
[i
] = *text
;
316 quoted_text
[i
] = '\0';
322 /* Deescapes file name */
323 char *shi_dequote_filename(char *text
, int quote_char
) {
325 char *unquoted_text
= NULL
;
329 "shi_dequote_filename(text=%s, quote_char=%d)\n",
331 if (!text
) return NULL
;
333 unquoted_text
= malloc(strlen(text
) + 1);
334 if (!unquoted_text
) return strdup(text
);
336 for (escaped
= 0, i
= 0; *text
; text
++) {
337 if (*text
== ESCAPER
&& !escaped
) {
343 unquoted_text
[i
++] = *text
;
345 unquoted_text
[i
] = '\0';
347 return unquoted_text
;
351 #define MAX_TOKENS 256
352 /* Split string into tokens
353 * @command_line is line to parse
354 * @argc outputs number of parsed arguments
355 * @return NULL-terminated array of tokens or NULL in case of error */
356 char **tokenize(const char *command_line
, int *argc
) {
358 const char *start
, *end
;
361 char *home
= getenv("HOME");
363 if (!argc
) return NULL
;
366 if (!command_line
) return NULL
;
368 argv
= calloc(MAX_TOKENS
, sizeof(*argv
));
370 fprintf(stderr
, _("Not enough memory to tokenize command line\n"));
375 /*for (start = command_line; *start; start++) {
376 if (isspace(*start)) continue;
378 if (*argc >= MAX_TOKENS - 1) {
379 fprintf(stderr, _("To much arguments\n"));
383 for (end = start; *end && !isspace(*end); end++);
384 argv[*argc] = malloc(end-start + 1);
386 fprintf(stderr, _("Not enough memory to tokenize command line\n"));
389 memcpy(argv[*argc], start, end-start);
390 argv[*argc][end-start] = '\0';
396 for (start
= command_line
; *start
; start
++) {
397 if (isspace(*start
)) continue;
399 if (*argc
>= MAX_TOKENS
- 1) {
400 fprintf(stderr
, _("To much arguments\n"));
404 /* Lokalize token boundaries */
405 for (end
= start
; *end
; end
++) {
406 if (*end
== ESCAPER
&& !escaped
) {
410 if (isspace(*end
) && !escaped
) break;
411 if (escaped
) escaped
= 0;
414 /* Allocate memory and expand first tilde to $HOME */
415 if (*start
== '~' && home
) {
416 size_t home_length
= strlen(home
);
418 argv
[*argc
] = malloc(home_length
+ end
-start
+ 1);
420 strcpy(argv
[*argc
], home
);
421 target
= argv
[*argc
] + home_length
;
424 argv
[*argc
] = malloc(end
-start
+ 1);
425 target
= argv
[*argc
];
428 fprintf(stderr
, _("Not enough memory to tokenize command line\n"));
432 /* Copy unquoted token */
433 for (escaped
= 0; start
< end
; start
++) {
434 if (*start
== ESCAPER
&& !escaped
) {
458 /* Add line into history if not NULL or empty string */
459 void shi_add_history(const char *line
) {
460 if (line
&& *line
) add_history(line
);
464 /* Ask user for a password */
465 char *ask_for_password(const char *prompt
) {
466 struct termios terminal
;
468 char *password
= NULL
;
470 tcerr
= tcgetattr(fileno(stdin
), &terminal
);
472 fprintf(stderr
, "Could not get terminal characteristics\n");
474 /* TODO: handle SIGINT to restore echo */
475 terminal
.c_lflag
&= ~ECHO
;
476 tcerr
= tcsetattr(fileno(stdin
), TCSAFLUSH
, &terminal
);
478 fprintf(stderr
, "Could not switch off local echo\n");
482 fprintf(stderr
, "Password will be visible on your terminal\n");
485 password
= readline(prompt
);
488 terminal
.c_lflag
|= ECHO
;
489 tcerr
= tcsetattr(fileno(stdin
), TCSAFLUSH
, &terminal
);
491 fprintf(stderr
, "Could not switch on local echo\n");
500 /* Prompt user and supply default value if user does input nothing. Original
501 * default value can be deallocated in this function. If *@value is NULL, use
502 * as default read-only @backup_value. You can always free *@value. * */
503 void shi_replace_string(char **value
, const char *prompt
,
504 const char *backup_value
, _Bool batch_mode
) {
509 shi_add_history(backup_value
);
510 shi_add_history(*value
);
512 if (!*value
&& backup_value
) *value
= strdup(backup_value
);
515 if (prompt
) printf(_("%s%s\n"), prompt
,
516 (*value
) ? *value
: _("<Empty value>"));
518 if (*value
) printf(_("Default value: %s\n"), *value
);
519 answer
= readline(prompt
);
520 if (answer
&& answer
[0] == '\0') zfree(answer
);
527 if (*value
) printf(_("Using default value `%s'.\n"), *value
);
533 /* Prompt user for password and supply default value if user does input
534 * nothing. Original default value can be deallocated in this function. If
535 * *@value is NULL, use as default read-only @backup_value. You can always
537 void shi_replace_password(char **value
, const char *prompt
,
538 const char *backup_value
, _Bool batch_mode
) {
543 if (!*value
&& backup_value
) *value
= strdup(backup_value
);
546 if (prompt
) printf(_("%s%s\n"), prompt
,
547 (*value
) ? _("<Password provided>") : _("<Empty value>"));
549 if (*value
) printf(_("Default password exists\n"));
550 answer
= ask_for_password(prompt
);
551 if (answer
&& answer
[0] == '\0') zfree(answer
);
558 if (*value
) printf(_("Using default value.\n"));
564 static void shi_format_size(char **buffer
, double value
) {
566 shi_asprintf(buffer
, _("---"));
567 } else if (value
< (1<<10)) {
568 shi_asprintf(buffer
, _("%0.2f B"), value
);
569 } else if (value
< (1<<20)) {
570 shi_asprintf(buffer
, _("%0.2f KiB"), value
/(1<<10));
572 shi_asprintf(buffer
, _("%0.2f MiB"), value
/(1<<20));
577 /* Signal handler aborting ISDS transfer */
578 static void shi_abort_transfer(int signo
) {
579 if (progress_started
) progress_abort_requested
= 1;
583 /* This is ISDS context network progress call back. It prints progress meter
584 * and allows user to abort current network transfer. */
585 int shi_progressbar(double upload_total
, double upload_current
,
586 double download_total
, double download_current
, void *data
) {
587 if (!progress_started
) {
588 progress_started
= 1;
589 progress_abort_requested
= 0;
590 signal(SIGINT
, shi_abort_transfer
);
593 shi_format_size(&upload_current_formated
, upload_current
);
594 shi_format_size(&upload_total_formated
, upload_total
);
595 shi_format_size(&download_current_formated
, download_current
);
596 shi_format_size(&download_total_formated
, download_total
);
599 printf(_("Progress: upload %s/%s, download %s/%s"),
600 upload_current_formated
, upload_total_formated
,
601 download_current_formated
, download_total_formated
);
603 return progress_abort_requested
;
607 /* Finish progress meter output. */
608 void shi_progressbar_finish(void) {
609 if (progress_started
) {
610 progress_started
= 0;
611 /* Remove progress bar info */
612 /* FIXME: Progress bar can be longer than summary. We must gather
613 * longest tainted column in shi_progressbar() and blank only that
614 * necessary width. */
618 printf(_("Transfer summary: upload %s, download %s\n"),
619 upload_current_formated
, download_current_formated
);