3 /// Mode class for the Desktop mode
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"
25 #include "protostructs.h"
30 #include "controller.h"
37 namespace Barry
{ namespace Mode
{
40 ///////////////////////////////////////////////////////////////////////////////
43 Desktop::Desktop(Controller
&con
)
44 : Mode(con
, Controller::Desktop
)
49 Desktop::Desktop(Controller
&con
, const IConverter
&ic
)
50 : Mode(con
, Controller::Desktop
)
59 ///////////////////////////////////////////////////////////////////////////////
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
));
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
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
);
95 void Desktop::LoadDBDB()
97 DBPacket
packet(*this, m_command
, m_response
);
100 m_socket
->Packet(packet
);
102 while( packet
.Command() != SB_COMMAND_DB_DONE
) {
103 if( packet
.Command() == SB_COMMAND_DB_DATA
) {
105 m_dbdb
.Parse(m_response
);
109 m_socket
->NextRecord(m_response
);
113 void Desktop::OnOpen()
115 // get command table and database database
120 ///////////////////////////////////////////////////////////////////////////////
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
137 // FIXME - this needs a better error handler...
138 if( !m_dbdb
.GetDBNumber(name
, ID
) ) {
139 throw Error("Desktop: database name not found: " + name
);
147 /// Get database command from command table. Must call Open()
150 unsigned int Desktop::GetDBCommand(CommandType ct
)
152 unsigned int cmd
= 0;
153 const char *cmdName
= "Unknown";
158 cmdName
= "Database Access";
159 cmd
= m_commandTable
.GetCommand(cmdName
);
162 throw std::logic_error("Desktop: unknown command type");
166 std::ostringstream oss
;
167 oss
<< "Desktop: unable to get command code: " << cmdName
;
168 throw Error(oss
.str());
174 void Desktop::SetIConverter(const IConverter
&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
186 void Desktop::GetRecordStateTable(unsigned int dbId
, RecordStateTable
&result
)
188 dout("Database ID: " << dbId
);
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
);
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());
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
,
251 dout("Database ID: " << dbId
);
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();
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
;
280 throw Error(oss
.str());
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
);
294 /// Overwrites a specific record in the device as identified by the
297 void Desktop::SetRecord(unsigned int dbId
, unsigned int stateTableIndex
,
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());
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
);
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
);
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
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<>
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
)
392 DBLoader
loader(*this);
393 bool loading
= loader
.StartDBLoad(dbId
, data
);
395 // manual parser call
396 parser
.ParseRecord(data
, m_ic
);
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
440 DBPacket
packet(*this, m_command
, m_response
);
442 // loop until builder object has no more data
444 while( packet
.SetRecord(dbId
, builder
, m_ic
) ) {
445 dout("Database ID: " << dbId
);
447 m_socket
->Packet(packet
, first
? 60000 : -1);
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 //////////////////////////////////////////////////////////////////////////////
475 DBLoaderData(Desktop
&desktop
, Data
&command
, Data
&response
)
476 : m_packet(desktop
, command
, response
)
481 DBLoader::DBLoader(Desktop
&desktop
)
484 , m_loader(new DBLoaderData(desktop
, m_send
, m_send
))
488 DBLoader::~DBLoader()
493 bool DBLoader::StartDBLoad(unsigned int dbId
, DBData
&data
)
495 dout("Database ID: " << dbId
);
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
);
512 // advance! (use the same data block as in packet)
513 m_desktop
.m_socket
->NextRecord(data
.UseData());
520 bool DBLoader::GetNextRecord(DBData
&data
)
525 DBPacket
&packet
= m_loader
->m_packet
;
526 packet
.SetNewReceive(data
.UseData());
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
);
536 } while( m_loader
->m_packet
.Command() != SB_COMMAND_DB_DONE
);
542 } // namespace Barry::Mode
548 //////////////////////////////////////////////////////////////////////////////
549 // DeviceBuilder class
551 DeviceBuilder::DeviceBuilder(Mode::Desktop
&desktop
)
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
)
565 DBLabel
id(m_desktop
.GetDBID(dbname
), dbname
);
566 m_dbIds
.push_back(id
);
569 catch( Barry::Error
& ) {
570 // GetDBID() throws on error...
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
,
592 const IConverter
*ic
)
595 if( !FetchRecord(temp
, ic
) )
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
);
612 bool DeviceBuilder::FetchRecord(DBData
&data
, const IConverter
*ic
)
616 if( !m_dbIds
.size() )
617 return false; // nothing to do
620 m_current
= m_dbIds
.begin();
621 ret
= m_loader
.StartDBLoad(m_current
->id
, data
);
624 else if( m_loader
.IsBusy() ) {
625 ret
= m_loader
.GetNextRecord(data
);
628 // don't do anything if we're at the end of our rope
632 // advance and check again... m_current always points
638 ret
= m_loader
.StartDBLoad(m_current
->id
, data
);
641 // fill in the DBname if successful
643 data
.SetDBName(m_current
->name
);
648 bool DeviceBuilder::EndOfFile() const
650 return m_current
== m_dbIds
.end();
655 //////////////////////////////////////////////////////////////////////////////
656 // DeviceParser class
658 DeviceParser::DeviceParser(Mode::Desktop
&desktop
, WriteMode mode
)
664 DeviceParser::~DeviceParser()
668 void DeviceParser::StartDB(const DBData
&data
, const IConverter
*ic
)
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();
682 WriteMode mode
= m_mode
;
683 if( mode
== DECIDE_BY_CALLBACK
)
684 mode
= DecideWrite(data
);
688 case ERASE_ALL_WRITE_ALL
:
689 m_desktop
.ClearDatabase(m_current_dbid
);
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
);
703 case DECIDE_BY_CALLBACK
:
705 throw std::logic_error("DeviceParser: unknown mode");
709 void DeviceParser::WriteNext(const DBData
&data
, const IConverter
*ic
)
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
;
726 case ERASE_ALL_WRITE_ALL
:
727 // just do an AddRecord()
728 m_desktop
.AddRecord(m_current_dbid
, dbuild
);
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
);
739 m_desktop
.AddRecord(m_current_dbid
, dbuild
);
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
);
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
);
760 case DECIDE_BY_CALLBACK
:
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
) {