making the revamp available in general
[feisty_meow.git] / octopi / applications / transporter / find_missing.cpp
1 /*****************************************************************************\
2 *                                                                             *
3 *  Name   : find_missing                                                      *
4 *  Author : Chris Koeritz                                                     *
5 *                                                                             *
6 *******************************************************************************
7 * Copyright (c) 2002-$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 <basis/byte_array.h>
16 #include <basis/astring.h>
17
18 #include <application/hoople_main.h>
19 //#include <application/command_line.h>
20 #include <basis/astring.h>
21 #include <cromp/cromp_client.h>
22 #include <cromp/cromp_server.h>
23 #include <filesystem/directory_tree.h>
24 #include <filesystem/filename_list.h>
25 #include <loggers/critical_events.h>
26 #include <loggers/combo_logger.h>
27 //#include <loggers/program_wide_logger.h>
28 #include <octopus/entity_defs.h>
29 #include <octopus/tentacle.h>
30 #include <sockets/internet_address.h>
31 #include <sockets/machine_uid.h>
32 #include <sockets/tcpip_stack.h>
33 #include <structures/static_memory_gremlin.h>
34 #include <tentacles/file_transfer_tentacle.h>
35 #include <timely/time_control.h>
36 #include <timely/time_stamp.h>
37
38 using namespace application;
39 using namespace basis;
40 using namespace cromp;
41 using namespace filesystem;
42 using namespace loggers;
43 using namespace octopi;
44 using namespace sockets;
45 using namespace structures;
46 using namespace textual;
47 using namespace timely;
48
49 #undef BASE_LOG
50 #define BASE_LOG(a) EMERGENCY_LOG(program_wide_logger::get(), astring(a))
51 #undef LOG
52 #define LOG(a) CLASS_EMERGENCY_LOG(program_wide_logger::get(), astring(a))
53
54 const int REPORTING_INTERVAL = 28 * SECOND_ms;  // how often to squawk.
55
56 const int REFRESH_INTERVAL = 20 * MINUTE_ms;  // how often we check tree.
57
58 const int COMPARATOR_PORT = 10809;
59   // simple port grabbed randomly for the default.
60
61 const int MAX_CHUNK = 16 * KILOBYTE;
62   // chunk size doesn't matter here; not transferring.
63
64 //////////////
65
66 class find_missing : public application_shell
67 {
68 public:
69   find_missing();
70   ~find_missing();
71
72   virtual int execute();
73
74   DEFINE_CLASS_NAME("find_missing");
75
76   int retrieve_info_from_server();
77     // for a client side comparison, this finds out which files are
78     // different and reports them.
79
80   int print_instructions();
81     // shows the instructions for this program.
82
83 private:
84   bool _saw_clients;  // true if we ever got a connection.
85   cromp_server *_server_side;
86     // provides connection and transmission services for servers.
87   cromp_client *_client_side;  // client side connection.
88   bool _leave_when_no_clients;  // true if we should just do one run.
89   bool _encryption;  // true if we're encrypting.
90   astring _source;  // the source path which a client will ask the server for.
91   astring _target;  // the target path where files are stored for the client.
92   bool _started_okay;  // got through the command line checking.
93 };
94
95 //////////////
96
97 find_missing::find_missing()
98 : application_shell(),
99   _saw_clients(false),
100   _server_side(NULL_POINTER),
101   _client_side(NULL_POINTER),
102   _leave_when_no_clients(false),
103   _encryption(false),
104   _started_okay(false)
105 {
106   FUNCDEF("constructor");
107   SETUP_COMBO_LOGGER;
108   LOG("");
109   LOG("");
110
111   command_line args(_global_argc, _global_argv);
112   // check for a port on the command line.
113   astring port_text;
114   int port = COMPARATOR_PORT;
115   if (args.get_value("port", port_text, false))
116     port = port_text.convert(COMPARATOR_PORT);
117   int posn = 0;
118   if (args.find("exit", posn)) {
119     LOG("seeing the 'exit without clients' flag set.");
120     _leave_when_no_clients = true;
121   }
122
123   int indy = 0;
124   if (args.find("encrypt", indy, false)
125       || (args.find('e', indy, false)) ) {
126 LOG("enabling encryption!");
127     // they're saying that we should encrypt the communication.
128     _encryption = true;
129   }
130
131   bool server = true;
132   indy = 0;
133   if (args.find("client", indy, false)) {
134 LOG("client role chosen.");
135     server = false;
136   } else {
137 LOG("server role chosen.");
138   }
139
140   internet_address addr;
141   addr.port = port;
142
143   // check for a hostname on the command line.
144   astring hostname("local");
145   astring host_temp;
146   if (args.get_value("host", host_temp, false)) {
147     LOG(astring("using host: ") + host_temp);
148     hostname = host_temp;
149   } else LOG(astring("using host: ") + hostname);
150   strcpy(addr.hostname, hostname.s());
151
152   if (server) {
153     astring key;
154     if (!args.get_value("key", key, false)) {
155       print_instructions();
156       LOG("No keyword specified on command line.");
157       return;
158     }
159     astring root;
160     if (!args.get_value("root", root, false)) {
161       print_instructions();
162       LOG("No transfer root was specified on the command line.");
163       return;
164     }
165
166     LOG("starting comparison server");
167     _server_side = new cromp_server(cromp_server::any_address(port));
168     file_transfer_tentacle *new_tent = new file_transfer_tentacle(MAX_CHUNK,
169         (file_transfer_tentacle::transfer_modes)(file_transfer_tentacle::ONLY_REPORT_DIFFS
170         | file_transfer_tentacle::COMPARE_SIZE_AND_TIME
171         | file_transfer_tentacle::COMPARE_CONTENT_SAMPLE));
172     new_tent->add_correspondence(key, root, REFRESH_INTERVAL);
173     _server_side->add_tentacle(new_tent);
174     _server_side->enable_servers(_encryption);
175   } else {
176     LOG("starting comparison client");
177     _client_side = new cromp_client(addr);
178     if (_encryption) _client_side->enable_encryption();
179
180     outcome ret = _client_side->connect();
181     if (ret != cromp_client::OKAY)
182       non_continuable_error(class_name(), func, astring("failed to connect to "
183           "the server: ") + cromp_client::outcome_name(ret));
184
185     file_transfer_tentacle *new_tent = new file_transfer_tentacle(MAX_CHUNK,
186         (file_transfer_tentacle::transfer_modes)(file_transfer_tentacle::ONLY_REPORT_DIFFS
187         | file_transfer_tentacle::COMPARE_SIZE_AND_TIME
188         | file_transfer_tentacle::COMPARE_CONTENT_SAMPLE));
189     if (!args.get_value("source", _source, false)) {
190       print_instructions();
191       LOG("No source path was specified on the command line.");
192       return;
193     }
194     if (!args.get_value("target", _target, false)) {
195       print_instructions();
196       LOG("No target path was specified on the command line.");
197       return;
198     }
199
200     string_array includes;
201     outcome regis = new_tent->register_file_transfer
202         (_client_side->entity(), _source, _target, includes);
203     if (regis != cromp_client::OKAY)
204       non_continuable_error(class_name(), func, "failed to register transfer");
205
206     _client_side->add_tentacle(new_tent);
207   }
208
209   _started_okay = true;
210
211 }
212
213 find_missing::~find_missing()
214 {
215   WHACK(_client_side);
216   WHACK(_server_side);
217 }
218
219 int find_missing::print_instructions()
220 {
221   astring name = filename(_global_argv[0]).basename().raw();
222   BASE_LOG(a_sprintf("%s usage:", name.s()));
223   BASE_LOG("");
224   BASE_LOG(a_sprintf("\
225 This program can compare directory trees and report the files that are\n\
226 missing on the client's side compared to what the server is offering.\n\
227 The program can function as either the server side or the client side.\n\
228 The available flags are:\n\
229 \n\
230 %s --client --host srvname --port P --source key_path --target cli_dest\n\
231 \n\
232 The client side needs to know the server host (srvname) and the port where\n\
233 the server is listening for connections (P).  The client will compare its\n\
234 local path (cli_dest) with the server's keyed path (key_path).  The key\n\
235 path will begin with whatever keyword the server is offering, plus optional\n\
236 additional path components to retrieve less than the whole tree being\n\
237 served.\n\
238 \n\
239 \n\
240 %s --server --host srvname --port P --key keyname --root srv_path\n\
241 \n\
242 The server side needs to know what address and port to listen on (srvname\n\
243 and P).  It will open a server there that provides a directory hierarchy\n\
244 starting at the root specified (srv_path).  The directory tree will be known\n\
245 to clients as the key word (keyname), thus freeing the clients from needing\n\
246 to know absolute paths on the server.\n\
247 \n\
248 ", name.s(), name.s()));
249
250   return 23;
251 }
252
253 int find_missing::retrieve_info_from_server()
254 {
255   FUNCDEF("retrieve_info_from_server");
256   // prepare a client request
257   file_transfer_infoton initiate;
258   initiate._request = true;
259   initiate._command = file_transfer_infoton::TREE_COMPARISON;
260   initiate._src_root = _source;
261   initiate._dest_root = _target;
262   directory_tree target_area(_target);
263   target_area.calculate(false);
264   string_set includes;
265   initiate.package_tree_info(target_area, includes);
266   octopus_request_id cmd_id;
267   outcome start_ret = _client_side->submit(initiate, cmd_id);
268   if (start_ret != tentacle::OKAY)
269     non_continuable_error(class_name(), func, astring("failed to initiate "
270         " the transfer: ") + cromp_client::outcome_name(start_ret));
271
272   infoton *start_reply_tmp = NULL_POINTER;
273 //hmmm: set timeout appropriate to the speed of the connection!
274   outcome first_receipt = _client_side->acquire(start_reply_tmp, cmd_id);
275   if (first_receipt != cromp_client::OKAY)
276     non_continuable_error(class_name(), func, astring("failed to receive response: ")
277         + cromp_client::outcome_name(start_ret));
278   file_transfer_infoton *start_reply = dynamic_cast<file_transfer_infoton *>
279       (start_reply_tmp);
280   if (!start_reply)
281     non_continuable_error(class_name(), func, "failed to cast starting infoton to "
282         "proper type");
283
284   filename_list diffs;
285   byte_array pack_copy = start_reply->_packed_data;
286   if (!diffs.unpack(pack_copy))
287     non_continuable_error(class_name(), func, "could not unpack filename list!");
288   BASE_LOG("Differences found between local target and server's tree:");
289 ///  BASE_LOG(diffs.text_form());
290   for (int i = 0; i < diffs.elements(); i++) {
291     BASE_LOG(a_sprintf("%d: %s", i + 1, diffs[i]->raw().s()));
292   }
293
294   return 0;
295 }
296
297 int find_missing::execute()
298 {
299   FUNCDEF("execute");
300
301   if (!_started_okay) return 32;
302
303   time_stamp next_report(REPORTING_INTERVAL);
304
305   while (true) {
306     // make sure we didn't see our exit condition.
307
308     if (_server_side && !_server_side->clients() && _leave_when_no_clients
309         && _saw_clients) {
310       LOG("exiting now");
311       break;
312     }
313
314     if (_client_side) return retrieve_info_from_server();
315
316     if (time_stamp() > next_report) {
317       if (_server_side)
318         LOG(a_sprintf("There are %d clients.", _server_side->clients()));
319 //report about client side also.
320       next_report.reset(REPORTING_INTERVAL);
321     }
322
323     time_control::sleep_ms(100); 
324   }
325   return 0;
326 }
327
328 //////////////
329
330 HOOPLE_MAIN(find_missing, )
331