lib: changed Usb::Error exception to deal in libusb_errcode instead of system
[barry/progweb.git] / src / m_desktop.cc
blob21dc6d55290f9c65d0be265abe7b28d0424c1079
1 ///
2 /// \file m_desktop.cc
3 /// Mode class for the Desktop mode
4 ///
6 /*
7 Copyright (C) 2005-2011, 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 };
66 Data command(rawCommand, sizeof(rawCommand));
68 try {
69 m_socket->Packet(command, m_response);
71 MAKE_PACKET(rpack, m_response);
72 while( rpack->command != SB_COMMAND_DB_DONE ) {
73 m_socket->NextRecord(m_response);
75 rpack = (const Protocol::Packet *) m_response.GetData();
76 if( rpack->command == SB_COMMAND_DB_DATA && btohs(rpack->size) > 10 ) {
77 // second packet is generally large, and contains
78 // the command table
79 m_commandTable.Clear();
80 m_commandTable.Parse(m_response, 6);
84 ddout(m_commandTable);
87 catch( Usb::Error & ) {
88 eout("Desktop: error getting command table");
89 eeout(command, m_response);
90 throw;
94 void Desktop::LoadDBDB()
96 DBPacket packet(*this, m_command, m_response);
97 packet.GetDBDB();
99 m_socket->Packet(packet);
101 while( packet.Command() != SB_COMMAND_DB_DONE ) {
102 if( packet.Command() == SB_COMMAND_DB_DATA ) {
103 m_dbdb.Clear();
104 m_dbdb.Parse(m_response);
107 // advance!
108 m_socket->NextRecord(m_response);
112 void Desktop::OnOpen()
114 // get command table and database database
115 LoadCommandTable();
116 LoadDBDB();
119 ///////////////////////////////////////////////////////////////////////////////
120 // public API
123 // GetDBID
125 /// Get numeric database ID by name.
127 /// \param[in] name Name of database, which matches one of the
128 /// names listed in GetDBDB()
130 /// \exception Barry::Error
131 /// Thrown if name not found.
133 unsigned int Desktop::GetDBID(const std::string &name) const
135 unsigned int ID = 0;
136 // FIXME - this needs a better error handler...
137 if( !m_dbdb.GetDBNumber(name, ID) ) {
138 throw Error("Desktop: database name not found: " + name);
140 return ID;
144 // GetDBCommand
146 /// Get database command from command table. Must call Open()
147 /// before this.
149 unsigned int Desktop::GetDBCommand(CommandType ct)
151 unsigned int cmd = 0;
152 const char *cmdName = "Unknown";
154 switch( ct )
156 case DatabaseAccess:
157 cmdName = "Database Access";
158 cmd = m_commandTable.GetCommand(cmdName);
159 break;
160 default:
161 throw std::logic_error("Desktop: unknown command type");
164 if( cmd == 0 ) {
165 std::ostringstream oss;
166 oss << "Desktop: unable to get command code: " << cmdName;
167 throw Error(oss.str());
170 return cmd;
173 void Desktop::SetIConverter(const IConverter &ic)
175 m_ic = &ic;
179 // GetRecordStateTable
181 /// Retrieve the record state table from the handheld device, using the given
182 /// database ID. Results will be stored in result, which will be cleared
183 /// before adding.
185 void Desktop::GetRecordStateTable(unsigned int dbId, RecordStateTable &result)
187 dout("Database ID: " << dbId);
189 // start fresh
190 result.Clear();
192 DBPacket packet(*this, m_command, m_response);
193 packet.GetRecordStateTable(dbId);
195 m_socket->Packet(packet);
196 result.Parse(m_response);
198 // flush the command sequence
199 while( packet.Command() != SB_COMMAND_DB_DONE )
200 m_socket->NextRecord(m_response);
204 // AddRecord
206 /// Adds a record to the specified database. RecordId is
207 /// retrieved from build, and duplicate IDs are allowed by the device
208 /// (i.e. you can have two records with the same ID)
209 /// but *not* recommended!
211 void Desktop::AddRecord(unsigned int dbId, Builder &build)
213 dout("Database ID: " << dbId);
215 DBPacket packet(*this, m_command, m_response);
217 if( packet.SetRecord(dbId, build, m_ic) ) {
219 std::ostringstream oss;
221 m_socket->Packet(packet);
223 // successful packet transfer, so check the network return code
224 if( packet.Command() != SB_COMMAND_DB_DONE ) {
225 oss << "Desktop: device responded with unexpected packet command code: "
226 << "0x" << std::hex << packet.Command();
227 throw Error(oss.str());
230 if( packet.ReturnCode() != 0 ) {
231 oss << "Desktop: device responded with error code (command: "
232 << packet.Command() << ", code: "
233 << packet.ReturnCode() << ")";
234 throw Error(oss.str());
240 // GetRecord
242 /// Retrieves a specific record from the specified database.
243 /// The stateTableIndex comes from the GetRecordStateTable()
244 /// function. GetRecord() does not clear the dirty flag.
246 void Desktop::GetRecord(unsigned int dbId,
247 unsigned int stateTableIndex,
248 Parser &parser)
250 dout("Database ID: " << dbId);
252 std::string dbName;
253 m_dbdb.GetDBName(dbId, dbName);
255 DBPacket packet(*this, m_command, m_response);
256 packet.GetRecordByIndex(dbId, stateTableIndex);
258 m_socket->Packet(packet);
260 // perform copious packet checks
261 if( m_response.GetSize() < SB_PACKET_RESPONSE_HEADER_SIZE ) {
262 eeout(m_command, m_response);
264 std::ostringstream oss;
265 oss << "Desktop: invalid response packet size of "
266 << std::dec << m_response.GetSize();
267 eout(oss.str());
268 throw Error(oss.str());
270 if( packet.Command() != SB_COMMAND_DB_DATA ) {
271 eeout(m_command, m_response);
273 std::ostringstream oss;
274 oss << "Desktop: unexpected command of 0x"
275 << std::setbase(16) << packet.Command()
276 << " instead of expected 0x"
277 << std::setbase(16) << (unsigned int)SB_COMMAND_DB_DATA;
278 eout(oss.str());
279 throw Error(oss.str());
282 // grab that data
283 packet.Parse(parser, dbName, m_ic);
285 // flush the command sequence
286 while( packet.Command() != SB_COMMAND_DB_DONE )
287 m_socket->NextRecord(m_response);
291 // SetRecord
293 /// Overwrites a specific record in the device as identified by the
294 /// stateTableIndex.
296 void Desktop::SetRecord(unsigned int dbId, unsigned int stateTableIndex,
297 Builder &build)
299 dout("Database ID: " << dbId << " Index: " << stateTableIndex);
301 DBPacket packet(*this, m_command, m_response);
303 // write only if builder object has data
304 if( !packet.SetRecordByIndex(dbId, stateTableIndex, build, m_ic) ) {
305 throw std::logic_error("Desktop: no data available in SetRecord");
308 m_socket->Packet(packet);
310 std::ostringstream oss;
312 // successful packet transfer, so check the network return code
313 if( packet.Command() != SB_COMMAND_DB_DONE ) {
314 oss << "Desktop: device responded with unexpected packet command code: "
315 << "0x" << std::hex << packet.Command();
316 throw Error(oss.str());
319 if( packet.ReturnCode() != 0 ) {
320 oss << "Desktop: device responded with error code (command: "
321 << packet.Command() << ", code: "
322 << packet.ReturnCode() << ")";
323 throw Error(oss.str());
328 // ClearDirty
330 /// Clears the dirty flag on the specified record in the specified database.
332 void Desktop::ClearDirty(unsigned int dbId, unsigned int stateTableIndex)
334 dout("Database ID: " << dbId);
336 DBPacket packet(*this, m_command, m_response);
337 packet.SetRecordFlags(dbId, stateTableIndex, 0);
339 m_socket->Packet(packet);
341 // flush the command sequence
342 while( packet.Command() != SB_COMMAND_DB_DONE )
343 m_socket->NextRecord(m_response);
347 // DeleteRecord
349 /// Deletes the specified record in the specified database.
351 void Desktop::DeleteRecord(unsigned int dbId, unsigned int stateTableIndex)
353 dout("Database ID: " << dbId);
355 DBPacket packet(*this, m_command, m_response);
356 packet.DeleteRecordByIndex(dbId, stateTableIndex);
358 m_socket->Packet(packet);
360 // flush the command sequence
361 while( packet.Command() != SB_COMMAND_DB_DONE )
362 m_socket->NextRecord(m_response);
366 // LoadDatabase
368 /// Retrieve a database from the handheld device, using the given parser
369 /// to parse the resulting data, and optionally store it.
371 /// See the RecordParser<> template to create a parser object. The
372 /// RecordParser<> template allows custom storage based on the type of
373 /// database record retrieved. The database ID and the parser Record
374 /// type must match.
376 /// \param[in] dbId Database Database ID - use GetDBID()
377 /// \param[out] parser Parser object which parses the resulting
378 /// protocol data, and optionally stores it in
379 /// a custom fashion. See the RecordParser<>
380 /// template.
382 /// \exception Barry::Error
383 /// Thrown on protocol error.
385 /// \exception std::logic_error
386 /// Thrown if not in Desktop mode.
388 void Desktop::LoadDatabase(unsigned int dbId, Parser &parser)
390 DBData data;
391 DBLoader loader(*this);
392 bool loading = loader.StartDBLoad(dbId, data);
393 while( loading ) {
394 // manual parser call
395 parser.ParseRecord(data, m_ic);
397 // advance!
398 loading = loader.GetNextRecord(data);
402 void Desktop::ClearDatabase(unsigned int dbId)
404 dout("Database ID: " << dbId);
406 DBPacket packet(*this, m_command, m_response);
407 packet.ClearDatabase(dbId);
409 // wait up to a minute here for old, slower devices with lots of data
410 m_socket->Packet(packet, 60000);
411 if( packet.ReturnCode() != 0 ) {
412 std::ostringstream oss;
413 oss << "Desktop: could not clear database: (command: "
414 << "0x" << std::hex << packet.Command() << ", code: "
415 << "0x" << std::hex << packet.ReturnCode() << ")";
416 throw Error(oss.str());
419 // check response to clear command was successful
420 if( packet.Command() != SB_COMMAND_DB_DONE ) {
421 eeout(m_command, m_response);
422 throw Error("Desktop: error clearing database, bad response");
426 void Desktop::SaveDatabase(unsigned int dbId, Builder &builder)
428 dout("Database ID: " << dbId);
430 // Protocol note: so far in testing, this CLEAR_DATABASE operation is
431 // required, since every record sent via SET_RECORD
432 // is treated like a hypothetical "ADD_RECORD" (perhaps
433 // SET_RECORD should be renamed)... I don't know if
434 // there is a real SET_RECORD... all I know is from
435 // the Windows USB captures, which uses this same
436 // technique.
437 ClearDatabase(dbId);
439 DBPacket packet(*this, m_command, m_response);
441 // loop until builder object has no more data
442 bool first = true;
443 while( packet.SetRecord(dbId, builder, m_ic) ) {
444 dout("Database ID: " << dbId);
446 m_socket->Packet(packet, first ? 60000 : -1);
447 first = false;
449 std::ostringstream oss;
450 // successful packet transfer, so check the network return code
451 if( packet.Command() != SB_COMMAND_DB_DONE ) {
452 oss << "Desktop: device responded with unexpected packet command code: "
453 << "0x" << std::hex << packet.Command();
454 throw Error(oss.str());
457 if( packet.ReturnCode() != 0 ) {
458 oss << "Desktop: device responded with error code (command: "
459 << packet.Command() << ", code: "
460 << packet.ReturnCode() << ")";
461 throw Error(oss.str());
468 //////////////////////////////////////////////////////////////////////////////
469 // DBLoader class
471 struct DBLoaderData
473 DBPacket m_packet;
474 DBLoaderData(Desktop &desktop, Data &command, Data &response)
475 : m_packet(desktop, command, response)
480 DBLoader::DBLoader(Desktop &desktop)
481 : m_desktop(desktop)
482 , m_loading(false)
483 , m_loader(new DBLoaderData(desktop, m_send, m_send))
487 DBLoader::~DBLoader()
489 delete m_loader;
492 bool DBLoader::StartDBLoad(unsigned int dbId, DBData &data)
494 dout("Database ID: " << dbId);
496 m_loading = true;
497 m_desktop.m_dbdb.GetDBName(dbId, m_dbName);
499 DBPacket &packet = m_loader->m_packet;
500 packet.SetNewReceive(data.UseData());
501 packet.GetRecords(dbId);
502 m_desktop.m_socket->Packet(packet);
504 while( packet.Command() != SB_COMMAND_DB_DONE ) {
505 if( packet.Command() == SB_COMMAND_DB_DATA ) {
506 packet.ParseMeta(data);
507 data.SetDBName(m_dbName);
508 return true;
511 // advance! (use the same data block as in packet)
512 m_desktop.m_socket->NextRecord(data.UseData());
515 m_loading = false;
516 return false;
519 bool DBLoader::GetNextRecord(DBData &data)
521 if( !m_loading )
522 return false;
524 DBPacket &packet = m_loader->m_packet;
525 packet.SetNewReceive(data.UseData());
527 do {
528 // advance! (use same data as in packet)
529 m_desktop.m_socket->NextRecord(data.UseData());
531 if( packet.Command() == SB_COMMAND_DB_DATA ) {
532 packet.ParseMeta(data);
533 return true;
535 } while( m_loader->m_packet.Command() != SB_COMMAND_DB_DONE );
537 m_loading = false;
538 return false;
541 } // namespace Barry::Mode
547 //////////////////////////////////////////////////////////////////////////////
548 // DeviceBuilder class
550 DeviceBuilder::DeviceBuilder(Mode::Desktop &desktop)
551 : m_started(false)
552 , m_desktop(desktop)
553 , m_loader(desktop)
555 Restart();
558 // searches the dbdb from the desktop to find the dbId,
559 // returns false if not found, and adds it to the list of
560 // databases to retrieve if found
561 bool DeviceBuilder::Add(const std::string &dbname)
563 try {
564 DBLabel id(m_desktop.GetDBID(dbname), dbname);
565 m_dbIds.push_back(id);
566 return true;
568 catch( Barry::Error & ) {
569 // GetDBID() throws on error...
570 return false;
574 void DeviceBuilder::Add(const Barry::DatabaseDatabase &dbdb)
576 DatabaseDatabase::DatabaseArrayType::const_iterator
577 b = dbdb.Databases.begin(),
578 e = dbdb.Databases.end();
580 for( ; b != e; ++b ) {
581 // hmmm, could optimize this and only add ids
582 // with RecordCount > 0, but let's stick with this
583 // for now... it might flush bugs out of the system
584 DBLabel id(b->Number, b->Name);
585 m_dbIds.push_back(id);
589 bool DeviceBuilder::BuildRecord(DBData &data,
590 size_t &offset,
591 const IConverter *ic)
593 DBData temp;
594 if( !FetchRecord(temp, ic) )
595 return false;
597 // copy the metadata
598 data.SetVersion(temp.GetVersion());
599 data.SetDBName(temp.GetDBName());
600 data.SetIds(temp.GetRecType(), temp.GetUniqueId());
601 data.SetOffset(offset);
603 // copy data from temp into the given offset
604 size_t tempsize = temp.GetData().GetSize() - temp.GetOffset();
605 data.UseData().MemCpy(offset,
606 temp.GetData().GetData() + temp.GetOffset(), tempsize);
607 data.UseData().ReleaseBuffer(offset + tempsize);
608 return true;
611 bool DeviceBuilder::FetchRecord(DBData &data, const IConverter *ic)
613 bool ret;
615 if( !m_dbIds.size() )
616 return false; // nothing to do
618 if( !m_started ) {
619 m_current = m_dbIds.begin();
620 ret = m_loader.StartDBLoad(m_current->id, data);
621 m_started = true;
623 else if( m_loader.IsBusy() ) {
624 ret = m_loader.GetNextRecord(data);
626 else {
627 // don't do anything if we're at the end of our rope
628 if( EndOfFile() )
629 return false;
631 // advance and check again... m_current always points
632 // to our current DB
633 ++m_current;
634 if( EndOfFile() )
635 return false;
637 ret = m_loader.StartDBLoad(m_current->id, data);
640 // fill in the DBname if successful
641 if( ret ) {
642 data.SetDBName(m_current->name);
644 return ret;
647 bool DeviceBuilder::EndOfFile() const
649 return m_current == m_dbIds.end();
654 //////////////////////////////////////////////////////////////////////////////
655 // DeviceParser class
657 DeviceParser::DeviceParser(Mode::Desktop &desktop, WriteMode mode)
658 : m_desktop(desktop)
659 , m_mode(mode)
663 DeviceParser::~DeviceParser()
667 void DeviceParser::StartDB(const DBData &data, const IConverter *ic)
669 // start fresh
670 m_rstate.Clear();
671 m_current_db = data.GetDBName();
672 if( !m_desktop.GetDBDB().GetDBNumber(m_current_db, m_current_dbid) ) {
673 // doh! This database does not exist in this device
674 dout("Database '" << m_current_db << "' does not exist in this device. Dropping record.");
675 m_current_db.clear();
676 m_current_dbid = 0;
677 return;
680 // determine mode
681 WriteMode mode = m_mode;
682 if( mode == DECIDE_BY_CALLBACK )
683 mode = DecideWrite(data);
685 switch( mode )
687 case ERASE_ALL_WRITE_ALL:
688 m_desktop.ClearDatabase(m_current_dbid);
689 WriteNext(data, ic);
690 break;
692 case INDIVIDUAL_OVERWRITE:
693 case ADD_BUT_NO_OVERWRITE:
694 case ADD_WITH_NEW_ID:
695 m_desktop.GetRecordStateTable(m_current_dbid, m_rstate);
696 WriteNext(data, ic);
697 break;
699 case DROP_RECORD:
700 break;
702 case DECIDE_BY_CALLBACK:
703 default:
704 throw std::logic_error("DeviceParser: unknown mode");
708 void DeviceParser::WriteNext(const DBData &data, const IConverter *ic)
710 // determine mode
711 WriteMode mode = m_mode;
712 if( mode == DECIDE_BY_CALLBACK )
713 mode = DecideWrite(data);
715 // create fast copy with our own metadata
716 DBData local(data.GetVersion(), data.GetDBName(),
717 data.GetRecType(), data.GetUniqueId(), data.GetOffset(),
718 data.GetData().GetData(), data.GetData().GetSize());
719 DBDataBuilder dbuild(local);
721 RecordStateTable::IndexType index;
723 switch( mode )
725 case ERASE_ALL_WRITE_ALL:
726 // just do an AddRecord()
727 m_desktop.AddRecord(m_current_dbid, dbuild);
728 break;
730 case INDIVIDUAL_OVERWRITE:
731 // search the state table, overwrite existing, and add new
732 if( m_rstate.GetIndex(local.GetUniqueId(), &index) ) {
733 // found this record ID, use the index
734 m_desktop.SetRecord(m_current_dbid, index, dbuild);
736 else {
737 // new record
738 m_desktop.AddRecord(m_current_dbid, dbuild);
740 break;
742 case ADD_BUT_NO_OVERWRITE:
743 if( !m_rstate.GetIndex(local.GetUniqueId()) ) {
744 // no such record ID, so safe to add as new
745 m_desktop.AddRecord(m_current_dbid, dbuild);
747 // else, drop record
748 break;
750 case ADD_WITH_NEW_ID:
751 // use state table to create new id, and add as new
752 local.SetIds(local.GetRecType(), m_rstate.MakeNewRecordId());
753 m_desktop.AddRecord(m_current_dbid, dbuild);
754 break;
756 case DROP_RECORD:
757 break;
759 case DECIDE_BY_CALLBACK:
760 default:
761 throw std::logic_error("DeviceParser: unknown mode");
765 void DeviceParser::ParseRecord(const DBData &data, const IConverter *ic)
767 if( data.GetDBName() == m_current_db ) {
768 WriteNext(data, ic);
770 else {
771 StartDB(data, ic);
775 } // namespace Barry