Merge branch 'master' of feistymeow.org:feisty_meow
[feisty_meow.git] / octopi / library / tentacles / recursive_file_copy.cpp
1 /*****************************************************************************\
2 *                                                                             *
3 *  Name   : recursive_file_copy                                               *
4 *  Author : Chris Koeritz                                                     *
5 *                                                                             *
6 *******************************************************************************
7 * Copyright (c) 2005-$now By Author.  This program is free software; you can  *
8 * redistribute it and/or modify it under the terms of the GNU General Public  *
9 * License as published by the Free Software Foundation; either version 2 of   *
10 * the License or (at your option) any later version.  This is online at:      *
11 *     http://www.fsf.org/copyleft/gpl.html                                    *
12 * Please send any updates to: fred@gruntose.com                               *
13 \*****************************************************************************/
14
15 #include "file_transfer_infoton.h"
16 #include "file_transfer_tentacle.h"
17 #include "recursive_file_copy.h"
18
19 #include <application/application_shell.h>
20 #include <basis/guards.h>
21 #include <filesystem/directory.h>
22 #include <filesystem/directory_tree.h>
23 #include <filesystem/filename.h>
24 #include <filesystem/filename_list.h>
25 #include <filesystem/heavy_file_ops.h>
26 #include <filesystem/huge_file.h>
27 #include <loggers/program_wide_logger.h>
28 #include <octopus/entity_defs.h>
29 #include <octopus/entity_data_bin.h>
30 #include <octopus/octopus.h>
31 #include <structures/static_memory_gremlin.h>
32 #include <textual/string_manipulation.h>
33 #include <timely/time_control.h>
34
35 using namespace application;
36 using namespace basis;
37 using namespace filesystem;
38 using namespace loggers;
39 using namespace structures;
40 using namespace textual;
41 using namespace timely;
42
43 namespace octopi {
44
45 #define DEBUG_RECURSIVE_FILE_COPY
46   // uncomment for noisier debugging.
47
48 #define FAKE_HOSTNAME "internal_fake_host"
49
50 #undef LOG
51 #define LOG(s) CLASS_EMERGENCY_LOG(program_wide_logger::get(), s)
52 #undef BASE_LOG
53 #define BASE_LOG(s) EMERGENCY_LOG(program_wide_logger::get(), s)
54
55 #define RETURN_ERROR_RFC(msg, err) { \
56   LOG(msg); \
57   return err; \
58 }
59
60 const int MAX_CHUNK_RFC_COPY_HIER = 1 * MEGABYTE;
61   // maximum size for each transfer chunk.
62
63 const int EXPECTED_MAXIMUM_TRANSFER_TIME = 10 * HOUR_ms;
64   // how long do we allow the scanned file lists to stay relevant.
65   // we allow it a long time, since this is a copy and not an active
66   // synchronization.
67
68 recursive_file_copy::~recursive_file_copy() {}
69
70 const char *recursive_file_copy::outcome_name(const outcome &to_name)
71 { return common::outcome_name(to_name); }
72
73 outcome recursive_file_copy::copy_hierarchy(int transfer_mode,
74   const astring &source_dir, const astring &target_dir,
75   const string_array &includes, const astring &source_start)
76 {
77   FUNCDEF("copy_hierarchy");
78
79 /*
80   string_array includes;
81   if (_global_argc >= 5) {
82     for (int i = 4; i < _global_argc; i++) {
83       includes += _global_argv[i];
84     }
85   }
86 */
87
88   const astring transfer_name = "snootums";
89
90   astring source_root = transfer_name;
91   if (source_start.t()) {
92     source_root += filename::default_separator() + source_start;
93   }
94
95   octopus ring_leader(FAKE_HOSTNAME, 10 * MEGABYTE);
96   file_transfer_tentacle *tran = new file_transfer_tentacle
97       (MAX_CHUNK_RFC_COPY_HIER, (file_transfer_tentacle::transfer_modes)transfer_mode);
98   ring_leader.add_tentacle(tran);
99
100   outcome add_ret = tran->add_correspondence(transfer_name, source_dir,
101       EXPECTED_MAXIMUM_TRANSFER_TIME);
102   if (add_ret != tentacle::OKAY)
103     RETURN_ERROR_RFC("failed to add the correspondence", NOT_FOUND);
104
105 //hmmm: this kind of object creation should be packaged in helper functions.
106   file_transfer_infoton *initiate = new file_transfer_infoton;
107   initiate->_request = true;
108   initiate->_command = file_transfer_infoton::BUILD_TARGET_TREE;
109   initiate->_src_root = source_root;
110   initiate->_dest_root = target_dir;
111
112   // make a directory snapshot with just directories, no files.
113   directory_tree target_area_just_dirs(source_dir, "*", true);
114   initiate->package_tree_info(target_area_just_dirs, includes);
115
116   octopus_entity ent = ring_leader.issue_identity();
117   octopus_request_id req_id(ent, 1);
118   outcome start_ret = ring_leader.evaluate(initiate, req_id);
119   if (start_ret != tentacle::OKAY)
120     RETURN_ERROR_RFC("failed to build target tree", NONE_READY);
121
122   file_transfer_infoton *reply_from_init
123       = (file_transfer_infoton *)ring_leader.acquire_specific_result(req_id);
124   if (!reply_from_init) {
125 LOG("spewing list of what IS there...");
126 LOG(ring_leader.responses().text_form());
127     RETURN_ERROR_RFC("no response to request to build target tree", NONE_READY);
128   }
129
130   if (reply_from_init->_success != OKAY) {
131     RETURN_ERROR_RFC("failed to get good result from building target tree", reply_from_init->_success);
132   }
133
134 //now repeating a lot of above to get tree compare going.
135
136 //hmmm: this kind of object creation should be packaged in helper functions.
137   file_transfer_infoton *comparison_req = new file_transfer_infoton;
138   comparison_req->_request = true;
139   comparison_req->_command = file_transfer_infoton::TREE_COMPARISON;
140   comparison_req->_src_root = source_root;
141   comparison_req->_dest_root = target_dir;
142   // make a directory snapshot with just directories, no files.
143   directory_tree target_area(target_dir);
144
145 //hmmm: simple asset counting debugging in calculate would be nice too.
146   target_area.calculate( !(transfer_mode & file_transfer_tentacle::COMPARE_CONTENT_SAMPLE) );
147
148   comparison_req->package_tree_info(target_area, includes);
149
150   ent = ring_leader.issue_identity();
151   req_id = octopus_request_id(ent, 1);
152   start_ret = ring_leader.evaluate(comparison_req, req_id);
153   if (start_ret != tentacle::OKAY)
154     RETURN_ERROR_RFC("failed to build target tree", NONE_READY);
155
156   reply_from_init = (file_transfer_infoton *)ring_leader.acquire_specific_result(req_id);
157   if (!reply_from_init) {
158 LOG("spewing list of what IS there...");
159 LOG(ring_leader.responses().text_form());
160     RETURN_ERROR_RFC("no response to request to build target tree", NONE_READY);
161   }
162
163
164 //resuming
165
166
167   filename_list diffs;
168   byte_array pack_copy = reply_from_init->_packed_data;
169   if (!diffs.unpack(pack_copy)) {
170     RETURN_ERROR_RFC("could not unpack filename list!", GARBAGE);
171   }
172 //  LOG(astring("got list of diffs:\n") + diffs.text_form());
173
174   octopus client_spider(FAKE_HOSTNAME, 10 * MEGABYTE);
175   file_transfer_tentacle *tran2 = new file_transfer_tentacle
176       (MAX_CHUNK_RFC_COPY_HIER, (file_transfer_tentacle::transfer_modes)transfer_mode);
177   tran2->register_file_transfer(ent, source_root, target_dir, includes);
178   client_spider.add_tentacle(tran2);
179
180   octopus_request_id resp_id(ent, 2);
181   outcome ini_resp_ret = client_spider.evaluate(reply_from_init, resp_id);
182   if (ini_resp_ret != tentacle::OKAY)
183     RETURN_ERROR_RFC("failed to process the start response!", FAILURE);
184
185   infoton *junk = client_spider.acquire_specific_result(resp_id);
186   if (junk)
187     RETURN_ERROR_RFC("got a response we shouldn't have!", FAILURE);
188
189   astring current_file;  // what file is in progress right now?
190
191   int iter = 0;
192   while (true) {
193 #ifdef DEBUG_RECURSIVE_FILE_COPY
194     LOG(a_sprintf("ongoing chunk %d", ++iter));
195 #endif
196
197     // keep going until we find a broken reply.
198     file_transfer_infoton *ongoing = new file_transfer_infoton;
199     ongoing->_request = true;
200     ongoing->_command = file_transfer_infoton::PLACE_FILE_CHUNKS;
201     ongoing->_src_root = source_root;
202     ongoing->_dest_root = target_dir;
203
204     octopus_request_id chunk_id(ent, iter + 10);
205     outcome place_ret = ring_leader.evaluate(ongoing, chunk_id);
206     if (place_ret != tentacle::OKAY)
207       RETURN_ERROR_RFC("failed to run ongoing transfer", FAILURE);
208   
209     file_transfer_infoton *reply = (file_transfer_infoton *)ring_leader
210          .acquire_specific_result(chunk_id);
211     if (!reply)
212       RETURN_ERROR_RFC("failed to get ongoing transfer reply", NONE_READY);
213
214     if (reply->_command == file_transfer_infoton::CONCLUDE_TRANSFER_MARKER) {
215       BASE_LOG(astring("finished transfer from \"") + source_dir
216           + "\" to \"" + target_dir + "\"");
217       break;
218     }
219
220     byte_array copy = reply->_packed_data;
221     while (copy.length()) {
222       file_time empty;
223       file_transfer_header head(empty);
224       if (!head.unpack(copy)) 
225         RETURN_ERROR_RFC("failed to unpack header", GARBAGE);
226       if (copy.length() < head._length)
227         RETURN_ERROR_RFC("not enough length in array", GARBAGE);
228       if (head._length > 0)
229         copy.zap(0, head._length - 1);
230
231 //hmmm: this needs better formatting, and should not repeat the same file name even
232 //      if it's in multiple chunks.
233       BASE_LOG(head.readable_text_form());
234     }
235     if (copy.length())
236       RETURN_ERROR_RFC("still had data in array", GARBAGE);
237
238     octopus_request_id resp_id(ent, iter + 11);
239     outcome resp_ret = client_spider.evaluate(reply, resp_id);
240     if (resp_ret != tentacle::OKAY)
241       RETURN_ERROR_RFC("failed to process the transfer reply!", FAILURE);
242   }
243
244   return OKAY;
245 }
246
247 } //namespace.
248
249