+/*
+ * Get date from user
+ */
+static int get_date(UAContext *ua, char *date, int date_len)
+{
+ bsendmsg(ua, _("The restored files will the most current backup\n"
+ "BEFORE the date you specify below.\n\n"));
+ for ( ;; ) {
+ if (!get_cmd(ua, _("Enter date as YYYY-MM-DD HH:MM:SS :"))) {
+ return 0;
+ }
+ if (str_to_utime(ua->cmd) != 0) {
+ break;
+ }
+ bsendmsg(ua, _("Improper date format.\n"));
+ }
+ bstrncpy(date, ua->cmd, date_len);
+ return 1;
+}
+
+/*
+ * Insert a single file, or read a list of files from a file
+ */
+static void insert_one_file(UAContext *ua, RESTORE_CTX *rx, char *date)
+{
+ FILE *ffd;
+ char file[5000];
+ char *p = ua->cmd;
+ int line = 0;
+
+ switch (*p) {
+ case '<':
+ p++;
+ if ((ffd = fopen(p, "r")) == NULL) {
+ bsendmsg(ua, _("Cannot open file %s: ERR=%s\n"),
+ p, strerror(errno));
+ break;
+ }
+ while (fgets(file, sizeof(file), ffd)) {
+ line++;
+ if (!insert_file_into_findex_list(ua, rx, file, date)) {
+ bsendmsg(ua, _("Error occurred on line %d of %s\n"), line, p);
+ }
+ }
+ fclose(ffd);
+ break;
+ default:
+ insert_file_into_findex_list(ua, rx, ua->cmd, date);
+ break;
+ }
+}
+
+/*
+ * For a given file (path+filename), split into path and file, then
+ * lookup the most recent backup in the catalog to get the JobId
+ * and FileIndex, then insert them into the findex list.
+ */
+static int insert_file_into_findex_list(UAContext *ua, RESTORE_CTX *rx, char *file,
+ char *date)
+{
+ strip_trailing_junk(file);
+ split_path_and_filename(rx, file);
+ Mmsg(&rx->query, uar_jobid_fileindex, date, rx->path, rx->fname, rx->ClientName);
+ rx->found = false;
+ /* Find and insert jobid and File Index */
+ if (!db_sql_query(ua->db, rx->query, jobid_fileindex_handler, (void *)rx)) {
+ bsendmsg(ua, _("Query failed: %s. ERR=%s\n"),
+ rx->query, db_strerror(ua->db));
+ }
+ if (!rx->found) {
+ bsendmsg(ua, _("No database record found for: %s\n"), file);
+ return 0;
+ }
+ rx->selected_files++;
+ /*
+ * Find the MediaTypes for this JobId and add to the name_list
+ */
+ Mmsg(&rx->query, uar_mediatype, rx->JobId);
+ if (!db_sql_query(ua->db, rx->query, unique_name_list_handler, (void *)&rx->name_list)) {
+ bsendmsg(ua, "%s", db_strerror(ua->db));
+ return 0;
+ }
+ return 1;
+}
+
+static void split_path_and_filename(RESTORE_CTX *rx, char *name)
+{
+ char *p, *f;
+
+ /* Find path without the filename.
+ * I.e. everything after the last / is a "filename".
+ * OK, maybe it is a directory name, but we treat it like
+ * a filename. If we don't find a / then the whole name
+ * must be a path name (e.g. c:).
+ */
+ for (p=f=name; *p; p++) {
+ if (*p == '/') {
+ f = p; /* set pos of last slash */
+ }
+ }
+ if (*f == '/') { /* did we find a slash? */
+ f++; /* yes, point to filename */
+ } else { /* no, whole thing must be path name */
+ f = p;
+ }
+
+ /* If filename doesn't exist (i.e. root directory), we
+ * simply create a blank name consisting of a single
+ * space. This makes handling zero length filenames
+ * easier.
+ */
+ rx->fnl = p - f;
+ if (rx->fnl > 0) {
+ rx->fname = check_pool_memory_size(rx->fname, rx->fnl+1);
+ memcpy(rx->fname, f, rx->fnl); /* copy filename */
+ rx->fname[rx->fnl] = 0;
+ } else {
+ rx->fname[0] = ' '; /* blank filename */
+ rx->fname[1] = 0;
+ rx->fnl = 1;
+ }
+
+ rx->pnl = f - name;
+ if (rx->pnl > 0) {
+ rx->path = check_pool_memory_size(rx->path, rx->pnl+1);
+ memcpy(rx->path, name, rx->pnl);
+ rx->path[rx->pnl] = 0;
+ } else {
+ rx->path[0] = ' ';
+ rx->path[1] = 0;
+ rx->pnl = 1;
+ }
+
+ Dmsg2(100, "sllit path=%s file=%s\n", rx->path, rx->fname);
+}
+
+static bool build_directory_tree(UAContext *ua, RESTORE_CTX *rx)
+{
+ TREE_CTX tree;
+ JobId_t JobId, last_JobId;
+ char *p;
+ char *nofname = "";
+ bool OK = true;
+
+ memset(&tree, 0, sizeof(TREE_CTX));
+ /*
+ * Build the directory tree containing JobIds user selected
+ */
+ tree.root = new_tree(rx->TotalFiles);
+ tree.root->fname = nofname;
+ tree.ua = ua;
+ last_JobId = 0;
+ /*
+ * For display purposes, the same JobId, with different volumes may
+ * appear more than once, however, we only insert it once.
+ */
+ int items = 0;
+ for (p=rx->JobIds; get_next_jobid_from_list(&p, &JobId) > 0; ) {
+
+ if (JobId == last_JobId) {
+ continue; /* eliminate duplicate JobIds */
+ }
+ last_JobId = JobId;
+ bsendmsg(ua, _("Building directory tree for JobId %u ...\n"), JobId);
+ items++;
+ /*
+ * Find files for this JobId and insert them in the tree
+ */
+ Mmsg(&rx->query, uar_sel_files, JobId);
+ if (!db_sql_query(ua->db, rx->query, insert_tree_handler, (void *)&tree)) {
+ bsendmsg(ua, "%s", db_strerror(ua->db));
+ }
+ /*
+ * Find the MediaTypes for this JobId and add to the name_list
+ */
+ Mmsg(&rx->query, uar_mediatype, JobId);
+ if (!db_sql_query(ua->db, rx->query, unique_name_list_handler, (void *)&rx->name_list)) {
+ bsendmsg(ua, "%s", db_strerror(ua->db));
+ }
+ }
+ bsendmsg(ua, "%d Job%s inserted into the tree and marked for extraction.\n",
+ items, items==1?"":"s");
+
+ /* Check MediaType and select storage that corresponds */
+ get_storage_from_mediatype(ua, &rx->name_list, rx);
+
+ if (find_arg(ua, _("all")) < 0) {
+ /* Let the user interact in selecting which files to restore */
+ OK = user_select_files_from_tree(&tree);
+ }
+
+ /*
+ * Walk down through the tree finding all files marked to be
+ * extracted making a bootstrap file.
+ */
+ if (OK) {
+ for (TREE_NODE *node=first_tree_node(tree.root); node; node=next_tree_node(node)) {
+ Dmsg2(400, "FI=%d node=0x%x\n", node->FileIndex, node);
+ if (node->extract || node->extract_dir) {
+ Dmsg2(400, "type=%d FI=%d\n", node->type, node->FileIndex);
+ add_findex(rx->bsr, node->JobId, node->FileIndex);
+ rx->selected_files++;
+ }
+ }
+ }
+
+ free_tree(tree.root); /* free the directory tree */
+ return OK;
+}
+
+