1 /*****************************************************************************\
3 * Name : find_missing *
4 * Author : Chris Koeritz *
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 \*****************************************************************************/
15 #include <basis/byte_array.h>
16 #include <basis/astring.h>
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>
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;
50 #define BASE_LOG(a) EMERGENCY_LOG(program_wide_logger::get(), astring(a))
52 #define LOG(a) CLASS_EMERGENCY_LOG(program_wide_logger::get(), astring(a))
54 const int REPORTING_INTERVAL = 28 * SECOND_ms; // how often to squawk.
56 const int REFRESH_INTERVAL = 20 * MINUTE_ms; // how often we check tree.
58 const int COMPARATOR_PORT = 10809;
59 // simple port grabbed randomly for the default.
61 const int MAX_CHUNK = 16 * KILOBYTE;
62 // chunk size doesn't matter here; not transferring.
66 class find_missing : public application_shell
72 virtual int execute();
74 DEFINE_CLASS_NAME("find_missing");
76 int retrieve_info_from_server();
77 // for a client side comparison, this finds out which files are
78 // different and reports them.
80 int print_instructions();
81 // shows the instructions for this program.
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.
97 find_missing::find_missing()
98 : application_shell(),
100 _server_side(NULL_POINTER),
101 _client_side(NULL_POINTER),
102 _leave_when_no_clients(false),
106 FUNCDEF("constructor");
111 command_line args(_global_argc, _global_argv);
112 // check for a port on the command line.
114 int port = COMPARATOR_PORT;
115 if (args.get_value("port", port_text, false))
116 port = port_text.convert(COMPARATOR_PORT);
118 if (args.find("exit", posn)) {
119 LOG("seeing the 'exit without clients' flag set.");
120 _leave_when_no_clients = true;
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.
133 if (args.find("client", indy, false)) {
134 LOG("client role chosen.");
137 LOG("server role chosen.");
140 internet_address addr;
143 // check for a hostname on the command line.
144 astring hostname("local");
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());
154 if (!args.get_value("key", key, false)) {
155 print_instructions();
156 LOG("No keyword specified on command line.");
160 if (!args.get_value("root", root, false)) {
161 print_instructions();
162 LOG("No transfer root was specified on the command line.");
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);
176 LOG("starting comparison client");
177 _client_side = new cromp_client(addr);
178 if (_encryption) _client_side->enable_encryption();
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));
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.");
194 if (!args.get_value("target", _target, false)) {
195 print_instructions();
196 LOG("No target path was specified on the command line.");
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");
206 _client_side->add_tentacle(new_tent);
209 _started_okay = true;
213 find_missing::~find_missing()
219 int find_missing::print_instructions()
221 astring name = filename(_global_argv[0]).basename().raw();
222 BASE_LOG(a_sprintf("%s usage:", name.s()));
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\
230 %s --client --host srvname --port P --source key_path --target cli_dest\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\
240 %s --server --host srvname --port P --key keyname --root srv_path\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\
248 ", name.s(), name.s()));
253 int find_missing::retrieve_info_from_server()
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);
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));
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 *>
281 non_continuable_error(class_name(), func, "failed to cast starting infoton to "
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()));
297 int find_missing::execute()
301 if (!_started_okay) return 32;
303 time_stamp next_report(REPORTING_INTERVAL);
306 // make sure we didn't see our exit condition.
308 if (_server_side && !_server_side->clients() && _leave_when_no_clients
314 if (_client_side) return retrieve_info_from_server();
316 if (time_stamp() > next_report) {
318 LOG(a_sprintf("There are %d clients.", _server_side->clients()));
319 //report about client side also.
320 next_report.reset(REPORTING_INTERVAL);
323 time_control::sleep_ms(100);
330 HOOPLE_MAIN(find_missing, )