client_document_info.hpp

Go to the documentation of this file.
00001 /* libobby - Network text editing library
00002  * Copyright (C) 2005, 2006 0x539 dev group
00003  *
00004  * This program is free software; you can redistribute it and/or
00005  * modify it under the terms of the GNU General Public
00006  * License as published by the Free Software Foundation; either
00007  * version 2 of the License, or (at your option) any later version.
00008  *
00009  * This program is distributed in the hope that it will be useful,
00010  * but WITHOUT ANY WARRANTY; without even the implied warranty of
00011  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
00012  * General Public License for more details.
00013  *
00014  * You should have received a copy of the GNU General Public
00015  * License along with this program; if not, write to the Free
00016  * Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
00017  */
00018 
00019 #ifndef _OBBY_CLIENT_DOCUMENT_INFO_HPP_
00020 #define _OBBY_CLIENT_DOCUMENT_INFO_HPP_
00021 
00022 #include <net6/client.hpp>
00023 #include "format_string.hpp"
00024 #include "no_operation.hpp"
00025 #include "split_operation.hpp"
00026 #include "insert_operation.hpp"
00027 #include "delete_operation.hpp"
00028 #include "record.hpp"
00029 #include "jupiter_client.hpp"
00030 #include "local_document_info.hpp"
00031 
00032 namespace obby
00033 {
00034 
00035 template<typename Document, typename Selector>
00036 class basic_client_buffer;
00037 
00041 template<typename Document, typename Selector>
00042 class basic_client_document_info:
00043         virtual public basic_local_document_info<Document, Selector>
00044 {
00045 public:
00046         typedef basic_document_info<Document, Selector> base_type;
00047         typedef basic_local_document_info<Document, Selector> base_local_type;
00048 
00049         typedef typename base_local_type::document_type document_type;
00050 
00051         typedef basic_client_buffer<Document, Selector> buffer_type;
00052         typedef typename buffer_type::net_type net_type;
00053         typedef jupiter_client<Document> jupiter_type;
00054         typedef typename jupiter_type::record_type record_type;
00055 
00056         typedef typename base_local_type::subscription_state subscription_state;
00057 
00061         basic_client_document_info(const buffer_type& buffer,
00062                                    net_type& net,
00063                                    const user* owner,
00064                                    unsigned int id,
00065                                    const std::string& title,
00066                                    unsigned int suffix,
00067                                    const std::string& encoding);
00068 
00077         basic_client_document_info(const buffer_type& buffer,
00078                                    net_type& net,
00079                                    const user* owner,
00080                                    unsigned int id,
00081                                    const std::string& title,
00082                                    const std::string& encoding,
00083                                    const std::string& content);
00084 
00088         basic_client_document_info(const buffer_type& buffer,
00089                                    net_type& net,
00090                                    const net6::packet& init_pack);
00091 
00094         virtual void insert(position pos, const std::string& text);
00095 
00098         virtual void erase(position pos, position len);
00099 
00102         virtual void rename(const std::string& new_title);
00103 
00107         virtual void subscribe();
00108 
00112         virtual void unsubscribe();
00113 
00117         virtual subscription_state get_subscription_state() const;
00118 
00122         virtual void on_net_packet(const document_packet& pack);
00123 
00126         virtual void obby_session_close();
00127 
00128 protected:
00131         virtual void user_subscribe(const user& user);
00132 
00135         virtual void user_unsubscribe(const user& user);
00136 
00139         bool execute_packet(const document_packet& pack);
00140 
00143         virtual void on_net_rename(const document_packet& pack);
00144 
00147         virtual void on_net_record(const document_packet& pack);
00148 
00151         virtual void on_net_sync_init(const document_packet& pack);
00152 
00155         virtual void on_net_sync_chunk(const document_packet& pack);
00156 
00159         virtual void on_net_subscribe(const document_packet& pack);
00160 
00163         virtual void on_net_unsubscribe(const document_packet& pack);
00164 
00168         virtual void on_jupiter_record(const record_type& rec,
00169                                        const user* from);
00170 
00174         virtual void session_close_impl();
00175 
00176         std::auto_ptr<jupiter_type> m_jupiter;
00177         subscription_state m_subscription_state;
00178 
00179 public:
00182         const buffer_type& get_buffer() const;
00183 
00184 protected:
00187         net_type& get_net6();
00188 
00191         const net_type& get_net6() const;
00192 };
00193 
00194 template<typename Document, typename Selector>
00195 basic_client_document_info<Document, Selector>::
00196 	basic_client_document_info(const buffer_type& buffer,
00197                                    net_type& net,
00198                                    const user* owner,
00199                                    unsigned int id,
00200                                    const std::string& title,
00201                                    unsigned int suffix,
00202                                    const std::string& encoding):
00203         base_type(buffer, net, owner, id, title, suffix, encoding),
00204         base_local_type(buffer, net, owner, id, title, suffix, encoding),
00205         m_subscription_state(base_local_type::UNSUBSCRIBED)
00206 {
00207         // If we created this document, the constructor with initial content
00208         // should be called.
00209         if(owner == &buffer.get_self() )
00210         {
00211                 throw std::logic_error(
00212                         "obby::basic_client_document_info::"
00213                         "basic_client_document_info:\n"
00214                         "Owner of document info without initial content is self"
00215                 );
00216         }
00217 
00218         // Implictly subscribe owner
00219         if(owner != NULL)
00220                 user_subscribe(*owner);
00221 }
00222 
00223 template<typename Document, typename Selector>
00224 basic_client_document_info<Document, Selector>::
00225 	basic_client_document_info(const buffer_type& buffer,
00226                                    net_type& net,
00227                                    const user* owner,
00228                                    unsigned int id,
00229                                    const std::string& title,
00230                                    const std::string& encoding,
00231                                    const std::string& content):
00232         base_type(
00233                 buffer,
00234                 net,
00235                 owner,
00236                 id,
00237                 title,
00238                 encoding
00239         ),
00240         base_local_type(
00241                 buffer,
00242                 net,
00243                 owner,
00244                 id,
00245                 title,
00246                 encoding
00247         ),
00248         m_subscription_state(base_local_type::SUBSCRIBED)
00249 {
00250         // content is provided, so we should have created this document
00251         if(owner != &buffer.get_self() )
00252         {
00253                 throw std::logic_error(
00254                         "obby::basic_client_document_info::"
00255                         "basic_client_document_info:\n"
00256                         "Owner of document info with initial content is "
00257                         "not self"
00258                 );
00259         }
00260 
00261         // Assign document, initialise content
00262         base_type::assign_document();
00263         base_type::m_document->insert(0, content, NULL);
00264 
00265         // Subscribe owner
00266         user_subscribe(*owner);
00267 }
00268 
00269 template<typename Document, typename Selector>
00270 basic_client_document_info<Document, Selector>::
00271 	basic_client_document_info(const buffer_type& buffer,
00272                                    net_type& net,
00273                                    const net6::packet& init_pack):
00274         // TODO: Find a way to only extract the data once out of the packet
00275         base_type(buffer, net, init_pack),
00276         base_local_type(buffer, net, init_pack),
00277         m_subscription_state(base_local_type::UNSUBSCRIBED)
00278 {
00279         // Load initially subscribed users
00280         for(unsigned int i = 5; i < init_pack.get_param_count(); ++ i)
00281         {
00282                 // Get user
00283                 const user* cur_user =
00284                         init_pack.get_param(i).net6::parameter::as<const user*>(
00285                                 ::serialise::hex_context_from<const user*>(
00286                                         buffer.get_user_table()
00287                                 )
00288                         );
00289 
00290                 // Must not be local user (who just joined the session and now
00291                 // synchronises the document list)
00292                 if(cur_user == &buffer.get_self() )
00293                 {
00294                         throw std::logic_error(
00295                                 "obby::basic_client_document_info::"
00296                                 "basic_client_document_info:\n"
00297                                 "Local user is in subscription list of "
00298                                 "initially synchronised document list"
00299                         );
00300                 }
00301 
00302                 // Subscribe it
00303                 user_subscribe(*cur_user);
00304         }
00305 }
00306 
00307 template<typename Document, typename Selector>
00308 void basic_client_document_info<Document, Selector>::
00309 	insert(position pos,
00310                const std::string& text)
00311 {
00312         if(base_type::m_document.get() == NULL)
00313         {
00314                 throw std::logic_error(
00315                         "obby::basic_client_document_info::insert:\n"
00316                         "Local user is not subscribed"
00317                 );
00318         }
00319 
00320         // TODO: Deny insertion when state is not SUBSCRIBED
00321 
00322         if(m_jupiter.get() != NULL)
00323         {
00324                 insert_operation<document_type> op(pos, text);
00325                 m_jupiter->local_op(op, &get_buffer().get_self() );
00326         }
00327         else
00328         {
00329                 // No network connection available: Perform direct insertion
00330                 base_type::m_document->insert(
00331                         pos,
00332                         text,
00333                         &get_buffer().get_self()
00334                 );
00335         }
00336 }
00337 
00338 template<typename Document, typename Selector>
00339 void basic_client_document_info<Document, Selector>::
00340 	erase(position pos,
00341               position len)
00342 {
00343         if(base_type::m_document.get() == NULL)
00344         {
00345                 throw std::logic_error(
00346                         "obby::basic_client_document_info::erase:\n"
00347                         "Local user is not subscribed"
00348                 );
00349         }
00350 
00351         // TODO: Deny erasure when state is not SUBSCRIBED
00352 
00353         if(m_jupiter.get() != NULL)
00354         {
00355                 delete_operation<document_type> op(pos, len);
00356                 m_jupiter->local_op(op, &get_buffer().get_self() );
00357         }
00358         else
00359         {
00360                 base_type::m_document->erase(pos, len);
00361         }
00362 }
00363 
00364 template<typename Document, typename Selector>
00365 void basic_client_document_info<Document, Selector>::
00366 	rename(const std::string& new_title)
00367 {
00368         if(base_type::m_net != NULL)
00369         {
00370                 // Server chooses new suffix
00371                 document_packet pack(*this, "rename");
00372                 pack << new_title;
00373                 get_net6().send(pack);
00374         }
00375         else
00376         {
00377                 base_type::document_rename(
00378                         new_title,
00379                         base_type::m_buffer.find_free_suffix(new_title, this)
00380                 );
00381         }
00382 }
00383 
00384 template<typename Document, typename Selector>
00385 void basic_client_document_info<Document, Selector>::subscribe()
00386 {
00387         // Already subscribed
00388         if(m_subscription_state == base_local_type::SUBSCRIBED ||
00389            m_subscription_state == base_local_type::SUBSCRIBING)
00390         {
00391                 throw std::logic_error(
00392                         "obby::basic_client_document_info::subscribe:\n"
00393                         "Local user is already subscribed or has sent a "
00394                         "subscription request"
00395                 );
00396         }
00397 
00398         if(base_type::m_net != NULL)
00399         {
00400                 // Send request
00401                 document_packet pack(*this, "subscribe");
00402                 get_net6().send(pack);
00403 
00404                 m_subscription_state = base_local_type::SUBSCRIBING;
00405         }
00406         else
00407         {
00408                 throw std::logic_error(
00409                         "obby::basic_client_document_info::subscribe:\n"
00410                         "Cannot subscribe to document without being connected"
00411                 );
00412         }
00413 }
00414 
00415 template<typename Document, typename Selector>
00416 void basic_client_document_info<Document, Selector>::unsubscribe()
00417 {
00418         // Not subscribed?
00419         if(m_subscription_state == base_local_type::UNSUBSCRIBED ||
00420            m_subscription_state == base_local_type::UNSUBSCRIBING)
00421         {
00422                 throw std::logic_error(
00423                         "obby::basic_client_document_info::unsubscribe:\n"
00424                         "Local user is not subscribed or has sent a "
00425                         "unsubscription request"
00426                 );
00427         }
00428 
00429         if(base_type::m_net != NULL)
00430         {
00431                 // Send request
00432                 document_packet pack(*this, "unsubscribe");
00433                 get_net6().send(pack);
00434 
00435                 m_subscription_state = base_local_type::UNSUBSCRIBING;
00436         }
00437         else
00438         {
00439                 user_unsubscribe(get_buffer().get_self() );
00440         }
00441 }
00442 
00443 template<typename Document, typename Selector>
00444 typename basic_client_document_info<Document, Selector>::subscription_state
00445 basic_client_document_info<Document, Selector>::get_subscription_state() const
00446 {
00447         return m_subscription_state;
00448 }
00449 
00450 template<typename Document, typename Selector>
00451 void basic_client_document_info<Document, Selector>::
00452 	on_net_packet(const document_packet& pack)
00453 {
00454         if(!execute_packet(pack) )
00455         {
00456                 throw net6::bad_value(
00457                         "Unexpected command: " + pack.get_command()
00458                 );
00459         }
00460 }
00461 
00462 template<typename Document, typename Selector>
00463 void basic_client_document_info<Document, Selector>::obby_session_close()
00464 {
00465         session_close_impl();
00466         basic_local_document_info<Document, Selector>::session_close_impl();
00467         basic_document_info<Document, Selector>::session_close_impl();
00468 }
00469 
00470 template<typename Document, typename Selector>
00471 void basic_client_document_info<Document, Selector>::
00472 	user_subscribe(const user& user)
00473 {
00474         // Add client to jupiter algo if we are subscribed
00475         if(m_jupiter.get() != NULL)
00476                 m_jupiter->client_add(user);
00477 
00478         // Local user subscription
00479         if(&get_buffer().get_self() == &user)
00480         {
00481                 // Note that the document must be there at this point because
00482                 // the whole document synchronisation process should have been
00483                 // performed before we subscribed to a document.
00484                 if(base_type::m_document.get() == NULL)
00485                 {
00486                         throw std::logic_error(
00487                                 "obby::basic_client_document_info::"
00488                                 "user_subscribe:\n"
00489                                 "Document content has not yet been synced"
00490                         );
00491                 }
00492 
00493                 // Create jupiter algorithm to merge changes
00494                 m_jupiter.reset(
00495                         new jupiter_type(
00496                                 *basic_document_info<Document, Selector>::
00497                                         m_document
00498                         )
00499                 );
00500 
00501                 // Add existing clients
00502                 for(typename base_type::user_iterator iter =
00503                         base_type::user_begin();
00504                     iter != base_type::user_end();
00505                     ++ iter)
00506                 {
00507                         m_jupiter->client_add(*iter);
00508                 }
00509 
00510                 m_jupiter->record_event().connect(
00511                         sigc::mem_fun(
00512                                 *this,
00513                                 &basic_client_document_info::on_jupiter_record
00514                         )
00515                 );
00516 
00517                 m_subscription_state = base_local_type::SUBSCRIBED;
00518         }
00519 
00520         // Call base function
00521         base_type::user_subscribe(user);
00522 }
00523 
00524 template<typename Document, typename Selector>
00525 void basic_client_document_info<Document, Selector>::
00526 	user_unsubscribe(const user& user)
00527 {
00528         // Base function emits signal, unsubscribed should already
00529         // be set then.
00530         if(&get_buffer().get_self() == &user)
00531                 m_subscription_state = base_local_type::UNSUBSCRIBED;
00532 
00533         // Call base function
00534         base_type::user_unsubscribe(user);
00535 
00536         // Remove user from jupiter if we are subscribed
00537         if(m_jupiter.get() != NULL)
00538                 m_jupiter->client_remove(user);
00539 
00540         // Local unsubscription
00541         if(&get_buffer().get_self() == &user)
00542         {
00543                 // Release document if the local user unsubscribed
00544                 base_type::release_document();
00545                 // Release jupiter algorithm
00546                 m_jupiter.reset(NULL);
00547         }
00548 }
00549 
00550 template<typename Document, typename Selector>
00551 bool basic_client_document_info<Document, Selector>::
00552 	execute_packet(const document_packet& pack)
00553 {
00554         // TODO: std::map<> with command to function
00555         if(pack.get_command() == "rename")
00556                 { on_net_rename(pack); return true; }
00557 
00558         if(pack.get_command() == "record")
00559                 { on_net_record(pack); return true; }
00560 
00561         if(pack.get_command() == "sync_init")
00562                 { on_net_sync_init(pack); return true; }
00563 
00564         if(pack.get_command() == "sync_chunk")
00565                 { on_net_sync_chunk(pack); return true; }
00566 
00567         if(pack.get_command() == "subscribe")
00568                 { on_net_subscribe(pack); return true; }
00569 
00570         if(pack.get_command() == "unsubscribe")
00571                 { on_net_unsubscribe(pack); return true; }
00572 
00573         return false;
00574 }
00575 
00576 template<typename Document, typename Selector>
00577 void basic_client_document_info<Document, Selector>::
00578 	on_net_rename(const document_packet& pack)
00579 {
00580         // First parameter is the user who changed the title
00581         const std::string& new_title =
00582                 pack.get_param(1).net6::parameter::as<std::string>();
00583         unsigned int new_suffix =
00584                 pack.get_param(2).net6::parameter::as<unsigned int>();
00585 
00586         // Rename document
00587         base_type::document_rename(new_title, new_suffix);
00588 }
00589 
00590 template<typename Document, typename Selector>
00591 void basic_client_document_info<Document, Selector>::
00592 	on_net_record(const document_packet& pack)
00593 {
00594         // Not subscribed?
00595         if(m_jupiter.get() == NULL)
00596         {
00597                 format_string str(
00598                         "Got record without being subscribed to document "
00599                         "%0%/%1%"
00600                 );
00601 
00602                 str << base_type::get_owner_id() << base_type::get_id();
00603                 throw net6::bad_value(str.str() );
00604         }
00605 
00606         // Get author of record
00607         const user* author = pack.get_param(0).net6::parameter::as<const user*>(
00608                 ::serialise::hex_context_from<const user*>(
00609                         get_buffer().get_user_table()
00610                 )
00611         );
00612 
00613         // Extract record from packet (TODO: virtualness for document_packet,
00614         // would allow to remove "+ 2" here)
00615         unsigned int index = 1 + 2;
00616         record_type rec(pack, index, base_type::m_buffer.get_user_table() );
00617 
00618         // Apply remote operation
00619         m_jupiter->remote_op(rec, author);
00620 }
00621 
00622 template<typename Document, typename Selector>
00623 void basic_client_document_info<Document, Selector>::
00624 	on_net_sync_init(const document_packet& pack)
00625 {
00626         // TODO: Allow subscription without former requisition?
00627         if(m_subscription_state != base_local_type::SUBSCRIBING)
00628         {
00629                 format_string str(
00630                         "Got sync_init without having sent a subscription "
00631                         "request for document %0%/%1%"
00632                 );
00633 
00634                 str << base_type::get_owner_id() << base_type::get_id();
00635                 throw net6::bad_value(str.str() );
00636         }
00637 
00638         // Assign empty document
00639         base_type::assign_document();
00640 }
00641 
00642 template<typename Document, typename Selector>
00643 void basic_client_document_info<Document, Selector>::
00644 	on_net_sync_chunk(const document_packet& pack)
00645 {
00646         // No document assigned or already subscribed?
00647         if(base_type::m_document.get() == NULL ||
00648            m_subscription_state != base_local_type::SUBSCRIBING)
00649         {
00650                 format_string str(
00651                         "Got sync_chunk without sync_init for document %0%/%1%"
00652                 );
00653 
00654                 str << base_type::get_owner_id() << base_type::get_id();
00655                 throw net6::bad_value(str.str() );
00656         }
00657 
00658         // Add chunk to document
00659         unsigned int index = 2;
00660         base_type::m_document->append(
00661                 pack.get_param(0).net6::parameter::as<std::string>(),
00662                 pack.get_param(1).net6::parameter::as<const user*>(
00663                         ::serialise::hex_context_from<
00664                                 const user*
00665                         >(base_type::m_buffer.get_user_table() )
00666                 )
00667         );
00668 }
00669 
00670 template<typename Document, typename Selector>
00671 void basic_client_document_info<Document, Selector>::
00672 	on_net_subscribe(const document_packet& pack)
00673 {
00674         const user* new_user =
00675                 pack.get_param(0).net6::parameter::as<const user*>(
00676                         ::serialise::hex_context_from<const user*>(
00677                                 get_buffer().get_user_table()
00678                         )
00679                 );
00680 
00681         // TODO: Throw bad value when already subscribed? Would be redundant
00682         // check...
00683 
00684         user_subscribe(*new_user);
00685 }
00686 
00687 template<typename Document, typename Selector>
00688 void basic_client_document_info<Document, Selector>::
00689 	on_net_unsubscribe(const document_packet& pack)
00690 {
00691         const user* old_user =
00692                 pack.get_param(0).net6::parameter::as<const user*>(
00693                         ::serialise::hex_context_from<const user*>(
00694                                 get_buffer().get_user_table()
00695                         )
00696                 );
00697 
00698         // TODO: Throw bad value when not subscribed? Would be redundant
00699         // check...
00700 
00701         user_unsubscribe(*old_user);
00702 }
00703 
00704 template<typename Document, typename Selector>
00705 void basic_client_document_info<Document, Selector>::
00706 	on_jupiter_record(const record_type& rec,
00707                           const user* from)
00708 {
00709         // Build packet with record
00710         document_packet pack(*this, "record");
00711         rec.append_packet(pack);
00712         // Send to server
00713         get_net6().send(pack);
00714 }
00715 
00716 template<typename Document, typename Selector>
00717 void basic_client_document_info<Document, Selector>::session_close_impl()
00718 {
00719         // Jupiter has been reset, but we are still subscribed if
00720         // m_document exists.
00721         m_jupiter.reset(NULL);
00722 }
00723 
00724 
00725 template<typename Document, typename Selector>
00726 const typename basic_client_document_info<Document, Selector>::buffer_type&
00727 basic_client_document_info<Document, Selector>::get_buffer() const
00728 {
00729         return dynamic_cast<const buffer_type&>(base_type::get_buffer());
00730 }
00731 
00732 template<typename Document, typename Selector>
00733 typename basic_client_document_info<Document, Selector>::net_type&
00734 basic_client_document_info<Document, Selector>::get_net6()
00735 {
00736         return dynamic_cast<net_type&>(base_type::get_net6() );
00737 }
00738 
00739 template<typename Document, typename Selector>
00740 const typename basic_client_document_info<Document, Selector>::net_type&
00741 basic_client_document_info<Document, Selector>::get_net6() const
00742 {
00743         return dynamic_cast<const net_type&>(base_type::get_net6() );
00744 }
00745 
00746 } // namespace obby
00747 
00748 #endif // _OBBY_CLIENT_DOCUMENT_INFO_HPP_

Generated on Fri Jan 11 10:01:32 2008 for obby by  doxygen 1.5.1