find_missing.cpp

Go to the documentation of this file.
00001 /*****************************************************************************\
00002 *                                                                             *
00003 *  Name   : find_missing                                                      *
00004 *  Author : Chris Koeritz                                                     *
00005 *                                                                             *
00006 *******************************************************************************
00007 * Copyright (c) 2002-$now By Author.  This program is free software; you can  *
00008 * redistribute it and/or modify it under the terms of the GNU General Public  *
00009 * License as published by the Free Software Foundation; either version 2 of   *
00010 * the License or (at your option) any later version.  This is online at:      *
00011 *     http://www.fsf.org/copyleft/gpl.html                                    *
00012 * Please send any updates to: fred@gruntose.com                               *
00013 \*****************************************************************************/
00014 
00015 #include <basis/byte_array.h>
00016 #include <basis/istring.h>
00017 #include <basis/portable.h>
00018 #include <cromp/cromp_client.h>
00019 #include <cromp/cromp_server.h>
00020 #include <mechanisms/time_stamp.h>
00021 #include <octopus/entity_defs.h>
00022 #include <octopus/tentacle.h>
00023 #include <opsystem/application_shell.h>
00024 #include <opsystem/command_line.h>
00025 #include <loggers/console_logger.h>
00026 #include <opsystem/directory_tree.h>
00027 #include <loggers/file_logger.h>
00028 #include <opsystem/filename_list.h>
00029 #include <data_struct/static_memory_gremlin.h>
00030 #include <sockets/address.h>
00031 #include <sockets/machine_uid.h>
00032 #include <sockets/tcpip_stack.h>
00033 #include <tentacles/file_transfer_tentacle.h>
00034 
00035 #define LOG(a) CLASS_EMERGENCY_LOG(program_wide_logger(), a)
00036 
00037 const int REPORTING_INTERVAL = 28 * SECOND_ms;  // how often to squawk.
00038 
00039 const int REFRESH_INTERVAL = 20 * MINUTE_ms;  // how often we check tree.
00040 
00041 const int COMPARATOR_PORT = 10809;
00042   // simple port grabbed randomly for the default.
00043 
00044 const int MAX_CHUNK = 16 * KILOBYTE;
00045   // chunk size doesn't matter here; not transferring.
00046 
00048 
00049 class find_missing : public application_shell
00050 {
00051 public:
00052   find_missing();
00053   ~find_missing();
00054 
00055   virtual int execute();
00056 
00057   IMPLEMENT_CLASS_NAME("find_missing");
00058 
00059   int retrieve_info_from_server();
00060     // for a client side comparison, this finds out which files are
00061     // different and reports them.
00062 
00063   int print_instructions();
00064     // shows the instructions for this program.
00065 
00066 private:
00067   bool _saw_clients;  // true if we ever got a connection.
00068   cromp_server *_server_side;
00069     // provides connection and transmission services for servers.
00070   cromp_client *_client_side;  // client side connection.
00071   bool _leave_when_no_clients;  // true if we should just do one run.
00072   bool _encryption;  // true if we're encrypting.
00073   istring _source;  // the source path which a client will ask the server for.
00074   istring _target;  // the target path where files are stored for the client.
00075   bool _started_okay;  // got through the command line checking.
00076 };
00077 
00079 
00080 find_missing::find_missing()
00081 : application_shell("find_missing"),
00082   _saw_clients(false),
00083   _server_side(NIL),
00084   _client_side(NIL),
00085   _leave_when_no_clients(false),
00086   _encryption(false),
00087   _started_okay(false)
00088 {
00089   FUNCDEF("constructor");
00090   SET_DEFAULT_COMBO_LOGGER;
00091   LOG("");
00092   LOG("");
00093 
00094   command_line args(__argc, __argv);
00095   // check for a port on the command line.
00096   istring port_text;
00097   int port = COMPARATOR_PORT;
00098   if (args.get_value("port", port_text, false))
00099     port = port_text.convert(COMPARATOR_PORT);
00100   int posn = 0;
00101   if (args.find("exit", posn)) {
00102     LOG("seeing the 'exit without clients' flag set.");
00103     _leave_when_no_clients = true;
00104   }
00105 
00106   int indy = 0;
00107   if (args.find("encrypt", indy, false)
00108       || (args.find('e', indy, false)) ) {
00109 LOG("enabling encryption!");
00110     // they're saying that we should encrypt the communication.
00111     _encryption = true;
00112   }
00113 
00114   bool server = true;
00115   indy = 0;
00116   if (args.find("client", indy, false)) {
00117 LOG("client role chosen.");
00118     server = false;
00119   } else {
00120 LOG("server role chosen.");
00121   }
00122 
00123   internet_address addr;
00124   addr.port = port;
00125 
00126   // check for a hostname on the command line.
00127   istring hostname("local");
00128   istring host_temp;
00129   if (args.get_value("host", host_temp, false)) {
00130     LOG(istring("using host: ") + host_temp);
00131     hostname = host_temp;
00132   } else LOG(istring("using host: ") + hostname);
00133   strcpy(addr.hostname, hostname.s());
00134 
00135   if (server) {
00136     istring key;
00137     if (!args.get_value("key", key, false)) {
00138       print_instructions();
00139       LOG("No keyword specified on command line.");
00140       return;
00141     }
00142     istring root;
00143     if (!args.get_value("root", root, false)) {
00144       print_instructions();
00145       LOG("No transfer root was specified on the command line.");
00146       return;
00147     }
00148 
00149     LOG("starting comparison server");
00150     _server_side = new cromp_server(cromp_server::any_address(port));
00151     file_transfer_tentacle *new_tent = new file_transfer_tentacle
00152         (MAX_CHUNK, true);
00153     new_tent->add_correspondence(key, root, REFRESH_INTERVAL);
00154     _server_side->add_tentacle(new_tent);
00155     _server_side->enable_servers(_encryption);
00156   } else {
00157     LOG("starting comparison client");
00158     _client_side = new cromp_client(addr);
00159     if (_encryption) _client_side->enable_encryption();
00160 
00161     outcome ret = _client_side->connect();
00162     if (ret != cromp_client::OKAY)
00163       non_continuable_error(class_name(), func, istring("failed to connect to "
00164           "the server: ") + cromp_client::outcome_name(ret));
00165 
00166     file_transfer_tentacle *new_tent = new file_transfer_tentacle
00167         (MAX_CHUNK, true);
00168 
00169     if (!args.get_value("source", _source, false)) {
00170       print_instructions();
00171       LOG("No source path was specified on the command line.");
00172       return;
00173     }
00174     if (!args.get_value("target", _target, false)) {
00175       print_instructions();
00176       LOG("No target path was specified on the command line.");
00177       return;
00178     }
00179 
00180     string_array includes;
00181     outcome regis = new_tent->register_file_transfer
00182         (_client_side->entity(), _source, _target, includes);
00183     if (regis != cromp_client::OKAY)
00184       non_continuable_error(class_name(), func, "failed to register transfer");
00185 
00186     _client_side->add_tentacle(new_tent);
00187   }
00188 
00189   _started_okay = true;
00190 
00191 }
00192 
00193 find_missing::~find_missing()
00194 {
00195   WHACK(_client_side);
00196   WHACK(_server_side);
00197 }
00198 
00199 int find_missing::print_instructions()
00200 {
00201   istring name = filename(__argv[0]).basename().raw();
00202   log(isprintf("%s usage:", name.s()));
00203   log("");
00204   log(isprintf("\
00205 This program can compare directory trees and report the files that are\n\
00206 missing on the client's side compared to what the server is offering.\n\
00207 The program can function as either the server side or the client side.\n\
00208 The available flags are:\n\
00209 \n\
00210 %s --client --host srvname --port P --source key_path --target cli_dest\n\
00211 \n\
00212 The client side needs to know the server host (srvname) and the port where\n\
00213 the server is listening for connections (P).  The client will compare its\n\
00214 local path (cli_dest) with the server's keyed path (key_path).  The key\n\
00215 path will begin with whatever keyword the server is offering, plus optional\n\
00216 additional path components to retrieve less than the whole tree being\n\
00217 served.\n\
00218 \n\
00219 \n\
00220 %s --server --host srvname --port P --key keyname --root srv_path\n\
00221 \n\
00222 The server side needs to know what address and port to listen on (srvname\n\
00223 and P).  It will open a server there that provides a directory hierarchy\n\
00224 starting at the root specified (srv_path).  The directory tree will be known\n\
00225 to clients as the key word (keyname), thus freeing the clients from needing\n\
00226 to know absolute paths on the server.\n\
00227 \n\
00228 ", name.s(), name.s()));
00229 
00230   return 23;
00231 }
00232 
00233 int find_missing::retrieve_info_from_server()
00234 {
00235   FUNCDEF("retrieve_info_from_server");
00236   // prepare a client request
00237   file_transfer_infoton initiate;
00238   initiate._request = true;
00239   initiate._command = file_transfer_infoton::TREE_COMPARISON;
00240   initiate._src_root = _source;
00241   initiate._dest_root = _target;
00242   directory_tree target_area(_target);
00243   target_area.calculate();
00244   string_set includes;
00245   initiate.package_tree_info(target_area, includes);
00246   octopus_request_id cmd_id;
00247   outcome start_ret = _client_side->submit(initiate, cmd_id);
00248   if (start_ret != tentacle::OKAY)
00249     non_continuable_error(class_name(), func, istring("failed to initiate "
00250         " the transfer: ") + cromp_client::outcome_name(start_ret));
00251 
00252   infoton *start_reply_tmp = NIL;
00253 //hmmm: set timeout appropriate to the speed of the connection!
00254   outcome first_receipt = _client_side->acquire(start_reply_tmp, cmd_id);
00255   if (first_receipt != cromp_client::OKAY)
00256     non_continuable_error(class_name(), func, istring("failed to receive response: ")
00257         + cromp_client::outcome_name(start_ret));
00258   file_transfer_infoton *start_reply = dynamic_cast<file_transfer_infoton *>
00259       (start_reply_tmp);
00260   if (!start_reply)
00261     non_continuable_error(class_name(), func, "failed to cast starting infoton to "
00262         "proper type");
00263 
00264   filename_list diffs;
00265   byte_array pack_copy = start_reply->_packed_data;
00266   if (!diffs.unpack(pack_copy))
00267     non_continuable_error(class_name(), func, "could not unpack filename list!");
00268   log("Differences found between local target and server's tree:");
00270   for (int i = 0; i < diffs.elements(); i++) {
00271     log(isprintf("%d: %s", i + 1, diffs[i]->raw().s()));
00272   }
00273 
00274   return 0;
00275 }
00276 
00277 int find_missing::execute()
00278 {
00279   FUNCDEF("execute");
00280 
00281   if (!_started_okay) return 32;
00282 
00283   time_stamp next_report(REPORTING_INTERVAL);
00284 
00285   while (true) {
00286     // make sure we didn't see our exit condition.
00287 
00288     if (_server_side && !_server_side->clients() && _leave_when_no_clients
00289         && _saw_clients) {
00290       LOG("exiting now");
00291       break;
00292     }
00293 
00294     if (_client_side) return retrieve_info_from_server();
00295 
00296     if (time_stamp() > next_report) {
00297       if (_server_side)
00298         LOG(isprintf("There are %d clients.", _server_side->clients()));
00299 //report about client side also.
00300       next_report.reset(REPORTING_INTERVAL);
00301     }
00302 
00303     portable::sleep_ms(100); 
00304   }
00305   return 0;
00306 }
00307 
00309 
00310 HOOPLE_MAIN(find_missing, )
00311 

Generated on Thu Nov 20 04:28:44 2008 for HOOPLE Libraries by  doxygen 1.5.1