/* * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Library General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * * OlaServer.cpp * OlaServer is the main OLA Server class * Copyright (C) 2005-2008 Simon Newton */ #if HAVE_CONFIG_H # include #endif #include #include #include #include #include #include #include "common/protocol/Ola.pb.h" #include "common/rpc/StreamRpcChannel.h" #include "ola/BaseTypes.h" #include "ola/ExportMap.h" #include "ola/Logging.h" #include "ola/network/InterfacePicker.h" #include "ola/rdm/UID.h" #include "olad/Client.h" #include "olad/DeviceManager.h" #include "olad/InternalRDMController.h" #include "olad/OlaServer.h" #include "olad/OlaServerServiceImpl.h" #include "olad/Plugin.h" #include "olad/PluginAdaptor.h" #include "olad/PluginManager.h" #include "olad/Port.h" #include "olad/PortManager.h" #include "olad/Preferences.h" #include "olad/Universe.h" #include "olad/UniverseStore.h" #ifdef HAVE_LIBMICROHTTPD #include "olad/OlaHttpServer.h" #endif namespace ola { using ola::rpc::StreamRpcChannel; using std::pair; const char OlaServer::UNIVERSE_PREFERENCES[] = "universe"; const char OlaServer::K_CLIENT_VAR[] = "clients-connected"; const char OlaServer::K_UID_VAR[] = "server-uid"; const unsigned int OlaServer::K_HOUSEKEEPING_TIMEOUT_MS = 1000; /* * Create a new OlaServer * @param factory the factory to use to create OlaService objects * @param m_plugin_loader the loader to use for the plugins * @param socket the socket to listen on for new connections */ OlaServer::OlaServer(OlaServerServiceImplFactory *factory, const vector &plugin_loaders, PreferencesFactory *preferences_factory, ola::network::SelectServer *select_server, ola_server_options *ola_options, ola::network::AcceptingSocket *socket, ExportMap *export_map) : m_service_factory(factory), m_plugin_loaders(plugin_loaders), m_ss(select_server), m_accepting_socket(socket), m_device_manager(NULL), m_plugin_manager(NULL), m_plugin_adaptor(NULL), m_preferences_factory(preferences_factory), m_universe_preferences(NULL), m_universe_store(NULL), m_export_map(export_map), m_port_manager(NULL), m_reload_plugins(false), m_init_run(false), m_free_export_map(false), m_housekeeping_timeout(ola::network::INVALID_TIMEOUT), m_httpd(NULL), m_options(*ola_options), m_rdm_controller(NULL) { if (!m_export_map) { m_export_map = new ExportMap(); m_free_export_map = true; } if (!m_options.http_port) m_options.http_port = DEFAULT_HTTP_PORT; m_export_map->GetIntegerVar(K_CLIENT_VAR); } /* * Shutdown the server */ OlaServer::~OlaServer() { #ifdef HAVE_LIBMICROHTTPD if (m_httpd) { m_httpd->Stop(); delete m_httpd; m_httpd = NULL; } #endif if (m_housekeeping_timeout != ola::network::INVALID_TIMEOUT) m_ss->RemoveTimeout(m_housekeeping_timeout); StopPlugins(); // this will remove any input ports and fail any outstanding rdm requests if (m_rdm_controller) delete m_rdm_controller; map::iterator iter; for (iter = m_sd_to_service.begin(); iter != m_sd_to_service.end(); ++iter) { CleanupConnection(iter->second); // TODO(simon): close the socket here /*Socket *socket = ; m_ss->RemoveSocket(socket); socket->Close(); */ } if (m_accepting_socket && m_accepting_socket->ReadDescriptor() != Socket::INVALID_SOCKET) m_ss->RemoveSocket(m_accepting_socket); if (m_universe_store) { m_universe_store->DeleteAll(); delete m_universe_store; } if (m_universe_preferences) { m_universe_preferences->Save(); } delete m_port_manager; delete m_plugin_adaptor; delete m_device_manager; delete m_plugin_manager; if (m_free_export_map) delete m_export_map; } /* * Initialise the server * * @return true on success, false on failure */ bool OlaServer::Init() { if (m_init_run) return false; if (!m_service_factory || !m_ss) return false; // TODO(simon): run without preferences & PluginLoader if (!m_plugin_loaders.size() || !m_preferences_factory) return false; if (m_accepting_socket) { if (!m_accepting_socket->Listen()) return false; m_accepting_socket->SetOnData( ola::NewClosure(this, &OlaServer::AcceptNewConnection, m_accepting_socket)); m_ss->AddSocket(m_accepting_socket); } signal(SIGPIPE, SIG_IGN); // fetch the interface info ola::rdm::UID default_uid(OPEN_LIGHTING_ESTA_CODE, 0); ola::network::Interface interface; ola::network::InterfacePicker *picker = ola::network::InterfacePicker::NewPicker(); if (!picker->ChooseInterface(&interface, "")) { OLA_WARN << "No network interface found"; } else { // default to using the ip as a id default_uid = ola::rdm::UID(OPEN_LIGHTING_ESTA_CODE, interface.ip_address.s_addr); } delete picker; m_export_map->GetStringVar(K_UID_VAR)->Set(default_uid.ToString()); OLA_INFO << "Server UID is " << default_uid; m_universe_preferences = m_preferences_factory->NewPreference( UNIVERSE_PREFERENCES); m_universe_preferences->Load(); m_universe_store = new UniverseStore(m_universe_preferences, m_export_map); m_port_manager = new PortManager(m_universe_store); m_rdm_controller = new InternalRDMController(default_uid, m_port_manager, m_export_map); // setup the objects m_device_manager = new DeviceManager(m_preferences_factory, m_port_manager); m_plugin_adaptor = new PluginAdaptor(m_device_manager, m_ss, m_preferences_factory); m_plugin_manager = new PluginManager(m_plugin_loaders, m_plugin_adaptor); if (!m_universe_store || !m_device_manager || !m_plugin_adaptor || !m_port_manager || !m_plugin_manager) { delete m_plugin_adaptor; delete m_device_manager; delete m_port_manager; delete m_universe_store; delete m_plugin_manager; return false; } m_plugin_manager->LoadAll(); #ifdef HAVE_LIBMICROHTTPD if (!StartHttpServer(interface)) OLA_WARN << "Failed to start the HTTP server."; #endif m_housekeeping_timeout = m_ss->RegisterRepeatingTimeout( K_HOUSEKEEPING_TIMEOUT_MS, ola::NewClosure(this, &OlaServer::RunHousekeeping)); m_ss->RunInLoop(ola::NewClosure(this, &OlaServer::CheckForReload)); m_init_run = true; return true; } /* * Reload all plugins, this can be called from a separate thread or in an * interrupt handler. */ void OlaServer::ReloadPlugins() { m_reload_plugins = true; } /* * Add a new ConnectedSocket to this Server. * @param accepting_socket the AcceptingSocket with the new connection pending. */ void OlaServer::AcceptNewConnection( ola::network::AcceptingSocket *accepting_socket) { ola::network::ConnectedSocket *socket = accepting_socket->Accept(); if (socket) NewConnection(socket); } /* * Add a new ConnectedSocket to this Server. * @param socket the new ConnectedSocket */ bool OlaServer::NewConnection(ola::network::ConnectedSocket *socket) { if (!socket) return false; StreamRpcChannel *channel = new StreamRpcChannel(NULL, socket, m_export_map); socket->SetOnClose(NewSingleClosure(this, &OlaServer::SocketClosed, socket)); OlaClientService_Stub *stub = new OlaClientService_Stub(channel); Client *client = new Client(stub); OlaServerServiceImpl *service = m_service_factory->New(m_universe_store, m_device_manager, m_plugin_manager, client, m_export_map, m_port_manager, m_rdm_controller, m_ss->WakeUpTime()); channel->SetService(service); map::const_iterator iter; iter = m_sd_to_service.find(socket->ReadDescriptor()); if (iter != m_sd_to_service.end()) OLA_INFO << "New socket but the client already exists!"; pair pair(socket->ReadDescriptor(), service); m_sd_to_service.insert(pair); // This hands off ownership to the select server m_ss->AddSocket(socket, true); (*m_export_map->GetIntegerVar(K_CLIENT_VAR))++; return 0; } /* * Called when a socket is closed */ void OlaServer::SocketClosed(ola::network::ConnectedSocket *socket) { map::iterator iter; iter = m_sd_to_service.find(socket->ReadDescriptor()); if (iter == m_sd_to_service.end()) OLA_INFO << "A socket was closed but we didn't find the client"; (*m_export_map->GetIntegerVar(K_CLIENT_VAR))--; CleanupConnection(iter->second); m_sd_to_service.erase(iter); } /* * Run the garbage collector */ bool OlaServer::RunHousekeeping() { OLA_DEBUG << "Garbage collecting"; m_universe_store->GarbageCollectUniverses(); m_rdm_controller->CheckTimeouts(*m_ss->WakeUpTime()); return true; } /* * Called once per loop iteration */ void OlaServer::CheckForReload() { if (m_reload_plugins) { m_reload_plugins = false; OLA_INFO << "Reloading plugins"; StopPlugins(); m_plugin_manager->LoadAll(); } } /* * Setup the HTTP server if required. * @param interface the primary interface that the server is using. */ #ifdef HAVE_LIBMICROHTTPD bool OlaServer::StartHttpServer(const ola::network::Interface &interface) { if (!m_options.http_enable) return true; // create a pipe socket for the http server to communicate with the main // server on. ola::network::PipeSocket *socket = new ola::network::PipeSocket(); if (!socket->Init()) { delete socket; return false; } // ownership of the socket is transferred here. m_httpd = new OlaHttpServer(m_export_map, socket->OppositeEnd(), this, m_options.http_port, m_options.http_enable_quit, m_options.http_data_dir, interface); if (m_httpd->Init()) { m_httpd->Start(); // register the pipe socket as a client NewConnection(socket); return true; } else { socket->Close(); delete socket; delete m_httpd; m_httpd = NULL; return false; } } #endif /* * Stop and unload all the plugins */ void OlaServer::StopPlugins() { if (m_plugin_manager) m_plugin_manager->UnloadAll(); if (m_device_manager) { if (m_device_manager->DeviceCount()) { OLA_WARN << "Some devices failed to unload, we're probably leaking " << "memory now"; } m_device_manager->UnregisterAllDevices(); } } /* * Cleanup everything related to a client connection */ void OlaServer::CleanupConnection(OlaServerServiceImpl *service) { Client *client = service->GetClient(); vector universe_list; m_universe_store->GetList(&universe_list); vector::iterator uni_iter; // O(universes * clients). Clean this up sometime. for (uni_iter = universe_list.begin(); uni_iter != universe_list.end(); ++uni_iter) { (*uni_iter)->RemoveSourceClient(client); (*uni_iter)->RemoveSinkClient(client); } delete client->Stub()->channel(); delete client->Stub(); delete client; delete service; } } // ola