3 /// Mode class for the Desktop mode
7 Copyright (C) 2005-2012, 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
.AddRecord(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 ReturnCodeError(oss
.str(), packet
.Command(), packet
.ReturnCode());
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 ReturnCodeError(oss
.str(), packet
.Command(), packet
.ReturnCode());
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 ReturnCodeError(oss
.str(), packet
.Command(), packet
.ReturnCode());
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
);
432 DBPacket
packet(*this, m_command
, m_response
);
434 // loop until builder object has no more data
436 while( packet
.AddRecord(dbId
, builder
, m_ic
) ) {
437 dout("Database ID: " << dbId
);
439 m_socket
->Packet(packet
, first
? 60000 : -1);
442 std::ostringstream oss
;
443 // successful packet transfer, so check the network return code
444 if( packet
.Command() != SB_COMMAND_DB_DONE
) {
445 oss
<< "Desktop: device responded with unexpected packet command code: "
446 << "0x" << std::hex
<< packet
.Command();
447 throw Error(oss
.str());
450 if( packet
.ReturnCode() != 0 ) {
451 oss
<< "Desktop: device responded with error code (command: "
452 << packet
.Command() << ", code: "
453 << packet
.ReturnCode() << ")";
454 throw ReturnCodeError(oss
.str(), packet
.Command(), packet
.ReturnCode());
461 //////////////////////////////////////////////////////////////////////////////
467 DBLoaderData(Desktop
&desktop
, Data
&command
, Data
&response
)
468 : m_packet(desktop
, command
, response
)
473 DBLoader::DBLoader(Desktop
&desktop
)
476 , m_loader(new DBLoaderData(desktop
, m_send
, m_send
))
480 DBLoader::~DBLoader()
485 bool DBLoader::StartDBLoad(unsigned int dbId
, DBData
&data
)
487 dout("Database ID: " << dbId
);
490 m_desktop
.m_dbdb
.GetDBName(dbId
, m_dbName
);
492 DBPacket
&packet
= m_loader
->m_packet
;
493 packet
.SetNewReceive(data
.UseData());
494 packet
.GetRecords(dbId
);
495 m_desktop
.m_socket
->Packet(packet
);
497 while( packet
.Command() != SB_COMMAND_DB_DONE
) {
498 if( packet
.Command() == SB_COMMAND_DB_DATA
) {
499 packet
.ParseMeta(data
);
500 data
.SetDBName(m_dbName
);
504 // advance! (use the same data block as in packet)
505 m_desktop
.m_socket
->NextRecord(data
.UseData());
512 bool DBLoader::GetNextRecord(DBData
&data
)
517 DBPacket
&packet
= m_loader
->m_packet
;
518 packet
.SetNewReceive(data
.UseData());
521 // advance! (use same data as in packet)
522 m_desktop
.m_socket
->NextRecord(data
.UseData());
524 if( packet
.Command() == SB_COMMAND_DB_DATA
) {
525 packet
.ParseMeta(data
);
528 } while( m_loader
->m_packet
.Command() != SB_COMMAND_DB_DONE
);
534 } // namespace Barry::Mode
540 //////////////////////////////////////////////////////////////////////////////
541 // DeviceBuilder class
543 DeviceBuilder::DeviceBuilder(Mode::Desktop
&desktop
)
551 // searches the dbdb from the desktop to find the dbId,
552 // returns false if not found, and adds it to the list of
553 // databases to retrieve if found
554 bool DeviceBuilder::Add(const std::string
&dbname
)
557 DBLabel
id(m_desktop
.GetDBID(dbname
), dbname
);
558 m_dbIds
.push_back(id
);
561 catch( Barry::Error
& ) {
562 // GetDBID() throws on error...
567 void DeviceBuilder::Add(const Barry::DatabaseDatabase
&dbdb
)
569 DatabaseDatabase::DatabaseArrayType::const_iterator
570 b
= dbdb
.Databases
.begin(),
571 e
= dbdb
.Databases
.end();
573 for( ; b
!= e
; ++b
) {
574 // hmmm, could optimize this and only add ids
575 // with RecordCount > 0, but let's stick with this
576 // for now... it might flush bugs out of the system
577 DBLabel
id(b
->Number
, b
->Name
);
578 m_dbIds
.push_back(id
);
582 bool DeviceBuilder::BuildRecord(DBData
&data
,
584 const IConverter
*ic
)
587 if( !FetchRecord(temp
, ic
) )
591 data
.SetVersion(temp
.GetVersion());
592 data
.SetDBName(temp
.GetDBName());
593 data
.SetIds(temp
.GetRecType(), temp
.GetUniqueId());
594 data
.SetOffset(offset
);
596 // copy data from temp into the given offset
597 size_t tempsize
= temp
.GetData().GetSize() - temp
.GetOffset();
598 data
.UseData().MemCpy(offset
,
599 temp
.GetData().GetData() + temp
.GetOffset(), tempsize
);
600 data
.UseData().ReleaseBuffer(offset
+ tempsize
);
604 bool DeviceBuilder::FetchRecord(DBData
&data
, const IConverter
*ic
)
608 if( !m_dbIds
.size() )
609 return false; // nothing to do
612 m_current
= m_dbIds
.begin();
613 ret
= m_loader
.StartDBLoad(m_current
->id
, data
);
616 else if( m_loader
.IsBusy() ) {
617 ret
= m_loader
.GetNextRecord(data
);
620 // don't do anything if we're at the end of our rope
624 // advance and check again... m_current always points
630 ret
= m_loader
.StartDBLoad(m_current
->id
, data
);
633 // fill in the DBname if successful
635 data
.SetDBName(m_current
->name
);
640 bool DeviceBuilder::EndOfFile() const
642 return m_current
== m_dbIds
.end();
647 //////////////////////////////////////////////////////////////////////////////
648 // DeviceParser class
650 DeviceParser::DeviceParser(Mode::Desktop
&desktop
, WriteMode mode
)
656 DeviceParser::~DeviceParser()
660 void DeviceParser::StartDB(const DBData
&data
, const IConverter
*ic
)
664 m_current_db
= data
.GetDBName();
665 if( !m_desktop
.GetDBDB().GetDBNumber(m_current_db
, m_current_dbid
) ) {
666 // doh! This database does not exist in this device
667 dout("Database '" << m_current_db
<< "' does not exist in this device. Dropping record.");
668 m_current_db
.clear();
674 WriteMode mode
= m_mode
;
675 if( mode
== DECIDE_BY_CALLBACK
)
676 mode
= DecideWrite(data
);
680 case ERASE_ALL_WRITE_ALL
:
681 m_desktop
.ClearDatabase(m_current_dbid
);
685 case INDIVIDUAL_OVERWRITE
:
686 case ADD_BUT_NO_OVERWRITE
:
687 case ADD_WITH_NEW_ID
:
688 m_desktop
.GetRecordStateTable(m_current_dbid
, m_rstate
);
695 case DECIDE_BY_CALLBACK
:
697 throw std::logic_error("DeviceParser: unknown mode");
701 void DeviceParser::WriteNext(const DBData
&data
, const IConverter
*ic
)
704 WriteMode mode
= m_mode
;
705 if( mode
== DECIDE_BY_CALLBACK
)
706 mode
= DecideWrite(data
);
708 // create fast copy with our own metadata
709 DBData
local(data
.GetVersion(), data
.GetDBName(),
710 data
.GetRecType(), data
.GetUniqueId(), data
.GetOffset(),
711 data
.GetData().GetData(), data
.GetData().GetSize());
712 DBDataBuilder
dbuild(local
);
714 RecordStateTable::IndexType index
;
718 case ERASE_ALL_WRITE_ALL
:
719 // just do an AddRecord()
720 m_desktop
.AddRecord(m_current_dbid
, dbuild
);
723 case INDIVIDUAL_OVERWRITE
:
724 // search the state table, overwrite existing, and add new
725 if( m_rstate
.GetIndex(local
.GetUniqueId(), &index
) ) {
726 // found this record ID, use the index
727 m_desktop
.SetRecord(m_current_dbid
, index
, dbuild
);
731 m_desktop
.AddRecord(m_current_dbid
, dbuild
);
735 case ADD_BUT_NO_OVERWRITE
:
736 if( !m_rstate
.GetIndex(local
.GetUniqueId()) ) {
737 // no such record ID, so safe to add as new
738 m_desktop
.AddRecord(m_current_dbid
, dbuild
);
743 case ADD_WITH_NEW_ID
:
744 // use state table to create new id, and add as new
745 local
.SetIds(local
.GetRecType(), m_rstate
.MakeNewRecordId());
746 m_desktop
.AddRecord(m_current_dbid
, dbuild
);
752 case DECIDE_BY_CALLBACK
:
754 throw std::logic_error("DeviceParser: unknown mode");
758 void DeviceParser::ParseRecord(const DBData
&data
, const IConverter
*ic
)
760 if( data
.GetDBName() == m_current_db
) {