ppp: minor cleanups to match other chatscripts order and documentation
[barry.git] / src / m_desktop.cc
blobe97324ac328fcb23215a754495c3017e1a254fe5
1 ///
2 /// \file m_desktop.cc
3 /// Mode class for the Desktop mode
4 ///
6 /*
7 Copyright (C) 2005-2010, Net Direct Inc. (http://www.netdirect.ca/)
9 This program is free software; you can redistribute it and/or modify
10 it under the terms of the GNU General Public License as published by
11 the Free Software Foundation; either version 2 of the License, or
12 (at your option) any later version.
14 This program is distributed in the hope that it will be useful,
15 but WITHOUT ANY WARRANTY; without even the implied warranty of
16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
18 See the GNU General Public License in the COPYING file at the
19 root directory of this project for more details.
22 #include "m_desktop.h"
23 #include "data.h"
24 #include "protocol.h"
25 #include "protostructs.h"
26 #include "packet.h"
27 #include "endian.h"
28 #include "error.h"
29 #include "usbwrap.h"
30 #include "controller.h"
31 #include "parser.h"
32 #include <stdexcept>
33 #include <sstream>
35 #include "debug.h"
37 namespace Barry { namespace Mode {
40 ///////////////////////////////////////////////////////////////////////////////
41 // Desktop Mode class
43 Desktop::Desktop(Controller &con)
44 : Mode(con, Controller::Desktop)
45 , m_ic(0)
49 Desktop::Desktop(Controller &con, const IConverter &ic)
50 : Mode(con, Controller::Desktop)
51 , m_ic(&ic)
55 Desktop::~Desktop()
59 ///////////////////////////////////////////////////////////////////////////////
60 // protected members
62 void Desktop::LoadCommandTable()
64 char rawCommand[] = { 6, 0, 0x0a, 0, 0x40, 0, 0, 1, 0, 0 };
65 *((uint16_t*) rawCommand) = htobs(m_socket->GetSocket());
67 Data command(rawCommand, sizeof(rawCommand));
69 try {
70 m_socket->Packet(command, m_response);
72 MAKE_PACKET(rpack, m_response);
73 while( rpack->command != SB_COMMAND_DB_DONE ) {
74 m_socket->NextRecord(m_response);
76 rpack = (const Protocol::Packet *) m_response.GetData();
77 if( rpack->command == SB_COMMAND_DB_DATA && btohs(rpack->size) > 10 ) {
78 // second packet is generally large, and contains
79 // the command table
80 m_commandTable.Clear();
81 m_commandTable.Parse(m_response, 6);
85 ddout(m_commandTable);
88 catch( Usb::Error & ) {
89 eout("Desktop: error getting command table");
90 eeout(command, m_response);
91 throw;
95 void Desktop::LoadDBDB()
97 DBPacket packet(*this, m_command, m_response);
98 packet.GetDBDB();
100 m_socket->Packet(packet);
102 while( packet.Command() != SB_COMMAND_DB_DONE ) {
103 if( packet.Command() == SB_COMMAND_DB_DATA ) {
104 m_dbdb.Clear();
105 m_dbdb.Parse(m_response);
108 // advance!
109 m_socket->NextRecord(m_response);
113 void Desktop::OnOpen()
115 // get command table and database database
116 LoadCommandTable();
117 LoadDBDB();
120 ///////////////////////////////////////////////////////////////////////////////
121 // public API
124 // GetDBID
126 /// Get numeric database ID by name.
128 /// \param[in] name Name of database, which matches one of the
129 /// names listed in GetDBDB()
131 /// \exception Barry::Error
132 /// Thrown if name not found.
134 unsigned int Desktop::GetDBID(const std::string &name) const
136 unsigned int ID = 0;
137 // FIXME - this needs a better error handler...
138 if( !m_dbdb.GetDBNumber(name, ID) ) {
139 throw Error("Desktop: database name not found: " + name);
141 return ID;
145 // GetDBCommand
147 /// Get database command from command table. Must call Open()
148 /// before this.
150 unsigned int Desktop::GetDBCommand(CommandType ct)
152 unsigned int cmd = 0;
153 const char *cmdName = "Unknown";
155 switch( ct )
157 case DatabaseAccess:
158 cmdName = "Database Access";
159 cmd = m_commandTable.GetCommand(cmdName);
160 break;
161 default:
162 throw std::logic_error("Desktop: unknown command type");
165 if( cmd == 0 ) {
166 std::ostringstream oss;
167 oss << "Desktop: unable to get command code: " << cmdName;
168 throw Error(oss.str());
171 return cmd;
174 void Desktop::SetIConverter(const IConverter &ic)
176 m_ic = &ic;
180 // GetRecordStateTable
182 /// Retrieve the record state table from the handheld device, using the given
183 /// database ID. Results will be stored in result, which will be cleared
184 /// before adding.
186 void Desktop::GetRecordStateTable(unsigned int dbId, RecordStateTable &result)
188 dout("Database ID: " << dbId);
190 // start fresh
191 result.Clear();
193 DBPacket packet(*this, m_command, m_response);
194 packet.GetRecordStateTable(dbId);
196 m_socket->Packet(packet);
197 result.Parse(m_response);
199 // flush the command sequence
200 while( packet.Command() != SB_COMMAND_DB_DONE )
201 m_socket->NextRecord(m_response);
205 // AddRecord
207 /// Adds a record to the specified database. RecordId is
208 /// retrieved from build, and duplicate IDs are allowed by the device
209 /// (i.e. you can have two records with the same ID)
210 /// but *not* recommended!
212 void Desktop::AddRecord(unsigned int dbId, Builder &build)
214 dout("Database ID: " << dbId);
216 DBPacket packet(*this, m_command, m_response);
218 if( packet.SetRecord(dbId, build, m_ic) ) {
220 std::ostringstream oss;
222 m_socket->Packet(packet);
224 // successful packet transfer, so check the network return code
225 if( packet.Command() != SB_COMMAND_DB_DONE ) {
226 oss << "Desktop: device responded with unexpected packet command code: "
227 << "0x" << std::hex << packet.Command();
228 throw Error(oss.str());
231 if( packet.ReturnCode() != 0 ) {
232 oss << "Desktop: device responded with error code (command: "
233 << packet.Command() << ", code: "
234 << packet.ReturnCode() << ")";
235 throw Error(oss.str());
241 // GetRecord
243 /// Retrieves a specific record from the specified database.
244 /// The stateTableIndex comes from the GetRecordStateTable()
245 /// function. GetRecord() does not clear the dirty flag.
247 void Desktop::GetRecord(unsigned int dbId,
248 unsigned int stateTableIndex,
249 Parser &parser)
251 dout("Database ID: " << dbId);
253 std::string dbName;
254 m_dbdb.GetDBName(dbId, dbName);
256 DBPacket packet(*this, m_command, m_response);
257 packet.GetRecordByIndex(dbId, stateTableIndex);
259 m_socket->Packet(packet);
261 // perform copious packet checks
262 if( m_response.GetSize() < SB_PACKET_RESPONSE_HEADER_SIZE ) {
263 eeout(m_command, m_response);
265 std::ostringstream oss;
266 oss << "Desktop: invalid response packet size of "
267 << std::dec << m_response.GetSize();
268 eout(oss.str());
269 throw Error(oss.str());
271 if( packet.Command() != SB_COMMAND_DB_DATA ) {
272 eeout(m_command, m_response);
274 std::ostringstream oss;
275 oss << "Desktop: unexpected command of 0x"
276 << std::setbase(16) << packet.Command()
277 << " instead of expected 0x"
278 << std::setbase(16) << (unsigned int)SB_COMMAND_DB_DATA;
279 eout(oss.str());
280 throw Error(oss.str());
283 // grab that data
284 packet.Parse(parser, dbName, m_ic);
286 // flush the command sequence
287 while( packet.Command() != SB_COMMAND_DB_DONE )
288 m_socket->NextRecord(m_response);
292 // SetRecord
294 /// Overwrites a specific record in the device as identified by the
295 /// stateTableIndex.
297 void Desktop::SetRecord(unsigned int dbId, unsigned int stateTableIndex,
298 Builder &build)
300 dout("Database ID: " << dbId << " Index: " << stateTableIndex);
302 DBPacket packet(*this, m_command, m_response);
304 // write only if builder object has data
305 if( !packet.SetRecordByIndex(dbId, stateTableIndex, build, m_ic) ) {
306 throw std::logic_error("Desktop: no data available in SetRecord");
309 m_socket->Packet(packet);
311 std::ostringstream oss;
313 // successful packet transfer, so check the network return code
314 if( packet.Command() != SB_COMMAND_DB_DONE ) {
315 oss << "Desktop: device responded with unexpected packet command code: "
316 << "0x" << std::hex << packet.Command();
317 throw Error(oss.str());
320 if( packet.ReturnCode() != 0 ) {
321 oss << "Desktop: device responded with error code (command: "
322 << packet.Command() << ", code: "
323 << packet.ReturnCode() << ")";
324 throw Error(oss.str());
329 // ClearDirty
331 /// Clears the dirty flag on the specified record in the specified database.
333 void Desktop::ClearDirty(unsigned int dbId, unsigned int stateTableIndex)
335 dout("Database ID: " << dbId);
337 DBPacket packet(*this, m_command, m_response);
338 packet.SetRecordFlags(dbId, stateTableIndex, 0);
340 m_socket->Packet(packet);
342 // flush the command sequence
343 while( packet.Command() != SB_COMMAND_DB_DONE )
344 m_socket->NextRecord(m_response);
348 // DeleteRecord
350 /// Deletes the specified record in the specified database.
352 void Desktop::DeleteRecord(unsigned int dbId, unsigned int stateTableIndex)
354 dout("Database ID: " << dbId);
356 DBPacket packet(*this, m_command, m_response);
357 packet.DeleteRecordByIndex(dbId, stateTableIndex);
359 m_socket->Packet(packet);
361 // flush the command sequence
362 while( packet.Command() != SB_COMMAND_DB_DONE )
363 m_socket->NextRecord(m_response);
367 // LoadDatabase
369 /// Retrieve a database from the handheld device, using the given parser
370 /// to parse the resulting data, and optionally store it.
372 /// See the RecordParser<> template to create a parser object. The
373 /// RecordParser<> template allows custom storage based on the type of
374 /// database record retrieved. The database ID and the parser Record
375 /// type must match.
377 /// \param[in] dbId Database Database ID - use GetDBID()
378 /// \param[out] parser Parser object which parses the resulting
379 /// protocol data, and optionally stores it in
380 /// a custom fashion. See the RecordParser<>
381 /// template.
383 /// \exception Barry::Error
384 /// Thrown on protocol error.
386 /// \exception std::logic_error
387 /// Thrown if not in Desktop mode.
389 void Desktop::LoadDatabase(unsigned int dbId, Parser &parser)
391 DBData data;
392 DBLoader loader(*this);
393 bool loading = loader.StartDBLoad(dbId, data);
394 while( loading ) {
395 // manual parser call
396 parser.ParseRecord(data, m_ic);
398 // advance!
399 loading = loader.GetNextRecord(data);
403 void Desktop::ClearDatabase(unsigned int dbId)
405 dout("Database ID: " << dbId);
407 DBPacket packet(*this, m_command, m_response);
408 packet.ClearDatabase(dbId);
410 // wait up to a minute here for old, slower devices with lots of data
411 m_socket->Packet(packet, 60000);
412 if( packet.ReturnCode() != 0 ) {
413 std::ostringstream oss;
414 oss << "Desktop: could not clear database: (command: "
415 << "0x" << std::hex << packet.Command() << ", code: "
416 << "0x" << std::hex << packet.ReturnCode() << ")";
417 throw Error(oss.str());
420 // check response to clear command was successful
421 if( packet.Command() != SB_COMMAND_DB_DONE ) {
422 eeout(m_command, m_response);
423 throw Error("Desktop: error clearing database, bad response");
427 void Desktop::SaveDatabase(unsigned int dbId, Builder &builder)
429 dout("Database ID: " << dbId);
431 // Protocol note: so far in testing, this CLEAR_DATABASE operation is
432 // required, since every record sent via SET_RECORD
433 // is treated like a hypothetical "ADD_RECORD" (perhaps
434 // SET_RECORD should be renamed)... I don't know if
435 // there is a real SET_RECORD... all I know is from
436 // the Windows USB captures, which uses this same
437 // technique.
438 ClearDatabase(dbId);
440 DBPacket packet(*this, m_command, m_response);
442 // loop until builder object has no more data
443 bool first = true;
444 while( packet.SetRecord(dbId, builder, m_ic) ) {
445 dout("Database ID: " << dbId);
447 m_socket->Packet(packet, first ? 60000 : -1);
448 first = false;
450 std::ostringstream oss;
451 // successful packet transfer, so check the network return code
452 if( packet.Command() != SB_COMMAND_DB_DONE ) {
453 oss << "Desktop: device responded with unexpected packet command code: "
454 << "0x" << std::hex << packet.Command();
455 throw Error(oss.str());
458 if( packet.ReturnCode() != 0 ) {
459 oss << "Desktop: device responded with error code (command: "
460 << packet.Command() << ", code: "
461 << packet.ReturnCode() << ")";
462 throw Error(oss.str());
469 //////////////////////////////////////////////////////////////////////////////
470 // DBLoader class
472 struct DBLoaderData
474 DBPacket m_packet;
475 DBLoaderData(Desktop &desktop, Data &command, Data &response)
476 : m_packet(desktop, command, response)
481 DBLoader::DBLoader(Desktop &desktop)
482 : m_desktop(desktop)
483 , m_loading(false)
484 , m_loader(new DBLoaderData(desktop, m_send, m_send))
488 DBLoader::~DBLoader()
490 delete m_loader;
493 bool DBLoader::StartDBLoad(unsigned int dbId, DBData &data)
495 dout("Database ID: " << dbId);
497 m_loading = true;
498 m_desktop.m_dbdb.GetDBName(dbId, m_dbName);
500 DBPacket &packet = m_loader->m_packet;
501 packet.SetNewReceive(data.UseData());
502 packet.GetRecords(dbId);
503 m_desktop.m_socket->Packet(packet);
505 while( packet.Command() != SB_COMMAND_DB_DONE ) {
506 if( packet.Command() == SB_COMMAND_DB_DATA ) {
507 packet.ParseMeta(data);
508 data.SetDBName(m_dbName);
509 return true;
512 // advance! (use the same data block as in packet)
513 m_desktop.m_socket->NextRecord(data.UseData());
516 m_loading = false;
517 return false;
520 bool DBLoader::GetNextRecord(DBData &data)
522 if( !m_loading )
523 return false;
525 DBPacket &packet = m_loader->m_packet;
526 packet.SetNewReceive(data.UseData());
528 do {
529 // advance! (use same data as in packet)
530 m_desktop.m_socket->NextRecord(data.UseData());
532 if( packet.Command() == SB_COMMAND_DB_DATA ) {
533 packet.ParseMeta(data);
534 return true;
536 } while( m_loader->m_packet.Command() != SB_COMMAND_DB_DONE );
538 m_loading = false;
539 return false;
542 } // namespace Barry::Mode
548 //////////////////////////////////////////////////////////////////////////////
549 // DeviceBuilder class
551 DeviceBuilder::DeviceBuilder(Mode::Desktop &desktop)
552 : m_started(false)
553 , m_desktop(desktop)
554 , m_loader(desktop)
556 Restart();
559 // searches the dbdb from the desktop to find the dbId,
560 // returns false if not found, and adds it to the list of
561 // databases to retrieve if found
562 bool DeviceBuilder::Add(const std::string &dbname)
564 try {
565 DBLabel id(m_desktop.GetDBID(dbname), dbname);
566 m_dbIds.push_back(id);
567 return true;
569 catch( Barry::Error & ) {
570 // GetDBID() throws on error...
571 return false;
575 void DeviceBuilder::Add(const Barry::DatabaseDatabase &dbdb)
577 DatabaseDatabase::DatabaseArrayType::const_iterator
578 b = dbdb.Databases.begin(),
579 e = dbdb.Databases.end();
581 for( ; b != e; ++b ) {
582 // hmmm, could optimize this and only add ids
583 // with RecordCount > 0, but let's stick with this
584 // for now... it might flush bugs out of the system
585 DBLabel id(b->Number, b->Name);
586 m_dbIds.push_back(id);
590 bool DeviceBuilder::BuildRecord(DBData &data,
591 size_t &offset,
592 const IConverter *ic)
594 DBData temp;
595 if( !FetchRecord(temp, ic) )
596 return false;
598 // copy the metadata
599 data.SetVersion(temp.GetVersion());
600 data.SetDBName(temp.GetDBName());
601 data.SetIds(temp.GetRecType(), temp.GetUniqueId());
602 data.SetOffset(offset);
604 // copy data from temp into the given offset
605 size_t tempsize = temp.GetData().GetSize() - temp.GetOffset();
606 data.UseData().MemCpy(offset,
607 temp.GetData().GetData() + temp.GetOffset(), tempsize);
608 data.UseData().ReleaseBuffer(offset + tempsize);
609 return true;
612 bool DeviceBuilder::FetchRecord(DBData &data, const IConverter *ic)
614 bool ret;
616 if( !m_dbIds.size() )
617 return false; // nothing to do
619 if( !m_started ) {
620 m_current = m_dbIds.begin();
621 ret = m_loader.StartDBLoad(m_current->id, data);
622 m_started = true;
624 else if( m_loader.IsBusy() ) {
625 ret = m_loader.GetNextRecord(data);
627 else {
628 // don't do anything if we're at the end of our rope
629 if( EndOfFile() )
630 return false;
632 // advance and check again... m_current always points
633 // to our current DB
634 ++m_current;
635 if( EndOfFile() )
636 return false;
638 ret = m_loader.StartDBLoad(m_current->id, data);
641 // fill in the DBname if successful
642 if( ret ) {
643 data.SetDBName(m_current->name);
645 return ret;
648 bool DeviceBuilder::EndOfFile() const
650 return m_current == m_dbIds.end();
655 //////////////////////////////////////////////////////////////////////////////
656 // DeviceParser class
658 DeviceParser::DeviceParser(Mode::Desktop &desktop, WriteMode mode)
659 : m_desktop(desktop)
660 , m_mode(mode)
664 DeviceParser::~DeviceParser()
668 void DeviceParser::StartDB(const DBData &data, const IConverter *ic)
670 // start fresh
671 m_rstate.Clear();
672 m_current_db = data.GetDBName();
673 if( !m_desktop.GetDBDB().GetDBNumber(m_current_db, m_current_dbid) ) {
674 // doh! This database does not exist in this device
675 dout("Database '" << m_current_db << "' does not exist in this device. Dropping record.");
676 m_current_db.clear();
677 m_current_dbid = 0;
678 return;
681 // determine mode
682 WriteMode mode = m_mode;
683 if( mode == DECIDE_BY_CALLBACK )
684 mode = DecideWrite(data);
686 switch( mode )
688 case ERASE_ALL_WRITE_ALL:
689 m_desktop.ClearDatabase(m_current_dbid);
690 WriteNext(data, ic);
691 break;
693 case INDIVIDUAL_OVERWRITE:
694 case ADD_BUT_NO_OVERWRITE:
695 case ADD_WITH_NEW_ID:
696 m_desktop.GetRecordStateTable(m_current_dbid, m_rstate);
697 WriteNext(data, ic);
698 break;
700 case DROP_RECORD:
701 break;
703 case DECIDE_BY_CALLBACK:
704 default:
705 throw std::logic_error("DeviceParser: unknown mode");
709 void DeviceParser::WriteNext(const DBData &data, const IConverter *ic)
711 // determine mode
712 WriteMode mode = m_mode;
713 if( mode == DECIDE_BY_CALLBACK )
714 mode = DecideWrite(data);
716 // create fast copy with our own metadata
717 DBData local(data.GetVersion(), data.GetDBName(),
718 data.GetRecType(), data.GetUniqueId(), data.GetOffset(),
719 data.GetData().GetData(), data.GetData().GetSize());
720 DBDataBuilder dbuild(local);
722 RecordStateTable::IndexType index;
724 switch( mode )
726 case ERASE_ALL_WRITE_ALL:
727 // just do an AddRecord()
728 m_desktop.AddRecord(m_current_dbid, dbuild);
729 break;
731 case INDIVIDUAL_OVERWRITE:
732 // search the state table, overwrite existing, and add new
733 if( m_rstate.GetIndex(local.GetUniqueId(), &index) ) {
734 // found this record ID, use the index
735 m_desktop.SetRecord(m_current_dbid, index, dbuild);
737 else {
738 // new record
739 m_desktop.AddRecord(m_current_dbid, dbuild);
741 break;
743 case ADD_BUT_NO_OVERWRITE:
744 if( !m_rstate.GetIndex(local.GetUniqueId()) ) {
745 // no such record ID, so safe to add as new
746 m_desktop.AddRecord(m_current_dbid, dbuild);
748 // else, drop record
749 break;
751 case ADD_WITH_NEW_ID:
752 // use state table to create new id, and add as new
753 local.SetIds(local.GetRecType(), m_rstate.MakeNewRecordId());
754 m_desktop.AddRecord(m_current_dbid, dbuild);
755 break;
757 case DROP_RECORD:
758 break;
760 case DECIDE_BY_CALLBACK:
761 default:
762 throw std::logic_error("DeviceParser: unknown mode");
766 void DeviceParser::ParseRecord(const DBData &data, const IConverter *ic)
768 if( data.GetDBName() == m_current_db ) {
769 WriteNext(data, ic);
771 else {
772 StartDB(data, ic);
776 } // namespace Barry