3 /// Mode class for the Desktop mode
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"
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 };
66 Data
command(rawCommand
, sizeof(rawCommand
));
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
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
);
94 void Desktop::LoadDBDB()
96 DBPacket
packet(*this, m_command
, m_response
);
99 m_socket
->Packet(packet
);
101 while( packet
.Command() != SB_COMMAND_DB_DONE
) {
102 if( packet
.Command() == SB_COMMAND_DB_DATA
) {
104 m_dbdb
.Parse(m_response
);
108 m_socket
->NextRecord(m_response
);
112 void Desktop::OnOpen()
114 // get command table and database database
119 ///////////////////////////////////////////////////////////////////////////////
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
136 // FIXME - this needs a better error handler...
137 if( !m_dbdb
.GetDBNumber(name
, ID
) ) {
138 throw Error("Desktop: database name not found: " + name
);
146 /// Get database command from command table. Must call Open()
149 unsigned int Desktop::GetDBCommand(CommandType ct
)
151 unsigned int cmd
= 0;
152 const char *cmdName
= "Unknown";
157 cmdName
= "Database Access";
158 cmd
= m_commandTable
.GetCommand(cmdName
);
161 throw std::logic_error("Desktop: unknown command type");
165 std::ostringstream oss
;
166 oss
<< "Desktop: unable to get command code: " << cmdName
;
167 throw Error(oss
.str());
173 void Desktop::SetIConverter(const IConverter
&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
185 void Desktop::GetRecordStateTable(unsigned int dbId
, RecordStateTable
&result
)
187 dout("Database ID: " << dbId
);
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
);
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());
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
,
250 dout("Database ID: " << dbId
);
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();
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
;
279 throw Error(oss
.str());
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
);
293 /// Overwrites a specific record in the device as identified by the
296 void Desktop::SetRecord(unsigned int dbId
, unsigned int stateTableIndex
,
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());
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
);
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
);
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
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<>
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
)
391 DBLoader
loader(*this);
392 bool loading
= loader
.StartDBLoad(dbId
, data
);
394 // manual parser call
395 parser
.ParseRecord(data
, m_ic
);
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
439 DBPacket
packet(*this, m_command
, m_response
);
441 // loop until builder object has no more data
443 while( packet
.SetRecord(dbId
, builder
, m_ic
) ) {
444 dout("Database ID: " << dbId
);
446 m_socket
->Packet(packet
, first
? 60000 : -1);
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 //////////////////////////////////////////////////////////////////////////////
474 DBLoaderData(Desktop
&desktop
, Data
&command
, Data
&response
)
475 : m_packet(desktop
, command
, response
)
480 DBLoader::DBLoader(Desktop
&desktop
)
483 , m_loader(new DBLoaderData(desktop
, m_send
, m_send
))
487 DBLoader::~DBLoader()
492 bool DBLoader::StartDBLoad(unsigned int dbId
, DBData
&data
)
494 dout("Database ID: " << dbId
);
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
);
511 // advance! (use the same data block as in packet)
512 m_desktop
.m_socket
->NextRecord(data
.UseData());
519 bool DBLoader::GetNextRecord(DBData
&data
)
524 DBPacket
&packet
= m_loader
->m_packet
;
525 packet
.SetNewReceive(data
.UseData());
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
);
535 } while( m_loader
->m_packet
.Command() != SB_COMMAND_DB_DONE
);
541 } // namespace Barry::Mode
547 //////////////////////////////////////////////////////////////////////////////
548 // DeviceBuilder class
550 DeviceBuilder::DeviceBuilder(Mode::Desktop
&desktop
)
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
)
564 DBLabel
id(m_desktop
.GetDBID(dbname
), dbname
);
565 m_dbIds
.push_back(id
);
568 catch( Barry::Error
& ) {
569 // GetDBID() throws on error...
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
,
591 const IConverter
*ic
)
594 if( !FetchRecord(temp
, ic
) )
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
);
611 bool DeviceBuilder::FetchRecord(DBData
&data
, const IConverter
*ic
)
615 if( !m_dbIds
.size() )
616 return false; // nothing to do
619 m_current
= m_dbIds
.begin();
620 ret
= m_loader
.StartDBLoad(m_current
->id
, data
);
623 else if( m_loader
.IsBusy() ) {
624 ret
= m_loader
.GetNextRecord(data
);
627 // don't do anything if we're at the end of our rope
631 // advance and check again... m_current always points
637 ret
= m_loader
.StartDBLoad(m_current
->id
, data
);
640 // fill in the DBname if successful
642 data
.SetDBName(m_current
->name
);
647 bool DeviceBuilder::EndOfFile() const
649 return m_current
== m_dbIds
.end();
654 //////////////////////////////////////////////////////////////////////////////
655 // DeviceParser class
657 DeviceParser::DeviceParser(Mode::Desktop
&desktop
, WriteMode mode
)
663 DeviceParser::~DeviceParser()
667 void DeviceParser::StartDB(const DBData
&data
, const IConverter
*ic
)
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();
681 WriteMode mode
= m_mode
;
682 if( mode
== DECIDE_BY_CALLBACK
)
683 mode
= DecideWrite(data
);
687 case ERASE_ALL_WRITE_ALL
:
688 m_desktop
.ClearDatabase(m_current_dbid
);
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
);
702 case DECIDE_BY_CALLBACK
:
704 throw std::logic_error("DeviceParser: unknown mode");
708 void DeviceParser::WriteNext(const DBData
&data
, const IConverter
*ic
)
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
;
725 case ERASE_ALL_WRITE_ALL
:
726 // just do an AddRecord()
727 m_desktop
.AddRecord(m_current_dbid
, dbuild
);
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
);
738 m_desktop
.AddRecord(m_current_dbid
, dbuild
);
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
);
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
);
759 case DECIDE_BY_CALLBACK
:
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
) {