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.
23 #include "m_desktop.h"
26 #include "protostructs.h"
31 #include "controller.h"
38 namespace Barry
{ namespace Mode
{
41 ///////////////////////////////////////////////////////////////////////////////
44 Desktop::Desktop(Controller
&con
)
45 : Mode(con
, Controller::Desktop
)
50 Desktop::Desktop(Controller
&con
, const IConverter
&ic
)
51 : Mode(con
, Controller::Desktop
)
60 ///////////////////////////////////////////////////////////////////////////////
63 void Desktop::LoadCommandTable()
65 char rawCommand
[] = { 6, 0, 0x0a, 0, 0x40, 0, 0, 1, 0, 0 };
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
.AddRecord(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 ReturnCodeError(oss
.str(), packet
.Command(), packet
.ReturnCode());
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 ")
276 << "0x" << std::setbase(16) << packet
.Command()
277 << _(" instead of expected ")
279 << std::setbase(16) << (unsigned int)SB_COMMAND_DB_DATA
;
281 throw Error(oss
.str());
285 packet
.Parse(parser
, dbName
, m_ic
);
287 // flush the command sequence
288 while( packet
.Command() != SB_COMMAND_DB_DONE
)
289 m_socket
->NextRecord(m_response
);
295 /// Overwrites a specific record in the device as identified by the
298 void Desktop::SetRecord(unsigned int dbId
, unsigned int stateTableIndex
,
301 dout(_("Database ID: ") << dbId
<< " " << _("Index: ") << stateTableIndex
);
303 DBPacket
packet(*this, m_command
, m_response
);
305 // write only if builder object has data
306 if( !packet
.SetRecordByIndex(dbId
, stateTableIndex
, build
, m_ic
) ) {
307 throw std::logic_error(_("Desktop: no data available in SetRecord"));
310 m_socket
->Packet(packet
);
312 std::ostringstream oss
;
314 // successful packet transfer, so check the network return code
315 if( packet
.Command() != SB_COMMAND_DB_DONE
) {
316 oss
<< _("Desktop: device responded with unexpected packet command code: ")
317 << "0x" << std::hex
<< packet
.Command();
318 throw Error(oss
.str());
321 if( packet
.ReturnCode() != 0 ) {
322 oss
<< _("Desktop: device responded with error code (command: ")
323 << packet
.Command() << ", " << _("code: ")
324 << packet
.ReturnCode() << ")";
325 throw ReturnCodeError(oss
.str(), packet
.Command(), packet
.ReturnCode());
332 /// Clears the dirty flag on the specified record in the specified database.
334 void Desktop::ClearDirty(unsigned int dbId
, unsigned int stateTableIndex
)
336 dout(_("Database ID: ") << dbId
);
338 DBPacket
packet(*this, m_command
, m_response
);
339 packet
.SetRecordFlags(dbId
, stateTableIndex
, 0);
341 m_socket
->Packet(packet
);
343 // flush the command sequence
344 while( packet
.Command() != SB_COMMAND_DB_DONE
)
345 m_socket
->NextRecord(m_response
);
351 /// Deletes the specified record in the specified database.
353 void Desktop::DeleteRecord(unsigned int dbId
, unsigned int stateTableIndex
)
355 dout(_("Database ID: ") << dbId
);
357 DBPacket
packet(*this, m_command
, m_response
);
358 packet
.DeleteRecordByIndex(dbId
, stateTableIndex
);
360 m_socket
->Packet(packet
);
362 // flush the command sequence
363 while( packet
.Command() != SB_COMMAND_DB_DONE
)
364 m_socket
->NextRecord(m_response
);
370 /// Retrieve a database from the handheld device, using the given parser
371 /// to parse the resulting data, and optionally store it.
373 /// See the RecordParser<> template to create a parser object. The
374 /// RecordParser<> template allows custom storage based on the type of
375 /// database record retrieved. The database ID and the parser Record
378 /// \param[in] dbId Database Database ID - use GetDBID()
379 /// \param[out] parser Parser object which parses the resulting
380 /// protocol data, and optionally stores it in
381 /// a custom fashion. See the RecordParser<>
384 /// \exception Barry::Error
385 /// Thrown on protocol error.
387 /// \exception std::logic_error
388 /// Thrown if not in Desktop mode.
390 void Desktop::LoadDatabase(unsigned int dbId
, Parser
&parser
)
393 DBLoader
loader(*this);
394 bool loading
= loader
.StartDBLoad(dbId
, data
);
396 // manual parser call
397 parser
.ParseRecord(data
, m_ic
);
400 loading
= loader
.GetNextRecord(data
);
404 void Desktop::ClearDatabase(unsigned int dbId
)
406 dout(_("Database ID: ") << dbId
);
408 DBPacket
packet(*this, m_command
, m_response
);
409 packet
.ClearDatabase(dbId
);
411 // wait up to a minute here for old, slower devices with lots of data
412 m_socket
->Packet(packet
, 60000);
413 if( packet
.ReturnCode() != 0 ) {
414 std::ostringstream oss
;
415 oss
<< _("Desktop: could not clear database: (command: ")
416 << "0x" << std::hex
<< packet
.Command() << ", "
418 << "0x" << std::hex
<< packet
.ReturnCode() << ")";
419 throw ReturnCodeError(oss
.str(), packet
.Command(), packet
.ReturnCode());
422 // check response to clear command was successful
423 if( packet
.Command() != SB_COMMAND_DB_DONE
) {
424 eeout(m_command
, m_response
);
425 throw Error(_("Desktop: error clearing database, bad response"));
429 void Desktop::SaveDatabase(unsigned int dbId
, Builder
&builder
)
431 dout(_("Database ID: ") << dbId
);
435 DBPacket
packet(*this, m_command
, m_response
);
437 // loop until builder object has no more data
439 while( packet
.AddRecord(dbId
, builder
, m_ic
) ) {
440 dout(_("Database ID: ") << dbId
);
442 m_socket
->Packet(packet
, first
? 60000 : -1);
445 std::ostringstream oss
;
446 // successful packet transfer, so check the network return code
447 if( packet
.Command() != SB_COMMAND_DB_DONE
) {
448 oss
<< _("Desktop: device responded with unexpected packet command code: ")
449 << "0x" << std::hex
<< packet
.Command();
450 throw Error(oss
.str());
453 if( packet
.ReturnCode() != 0 ) {
454 oss
<< _("Desktop: device responded with error code (command: ")
455 << packet
.Command() << ", " << _("code: ")
456 << packet
.ReturnCode() << ")";
457 throw ReturnCodeError(oss
.str(), packet
.Command(), packet
.ReturnCode());
464 //////////////////////////////////////////////////////////////////////////////
470 DBLoaderData(Desktop
&desktop
, Data
&command
, Data
&response
)
471 : m_packet(desktop
, command
, response
)
476 DBLoader::DBLoader(Desktop
&desktop
)
479 , m_loader(new DBLoaderData(desktop
, m_send
, m_send
))
483 DBLoader::~DBLoader()
488 bool DBLoader::StartDBLoad(unsigned int dbId
, DBData
&data
)
490 dout(_("Database ID: ") << dbId
);
493 m_desktop
.m_dbdb
.GetDBName(dbId
, m_dbName
);
495 DBPacket
&packet
= m_loader
->m_packet
;
496 packet
.SetNewReceive(data
.UseData());
497 packet
.GetRecords(dbId
);
498 m_desktop
.m_socket
->Packet(packet
);
500 while( packet
.Command() != SB_COMMAND_DB_DONE
) {
501 if( packet
.Command() == SB_COMMAND_DB_DATA
) {
502 packet
.ParseMeta(data
);
503 data
.SetDBName(m_dbName
);
507 // advance! (use the same data block as in packet)
508 m_desktop
.m_socket
->NextRecord(data
.UseData());
515 bool DBLoader::GetNextRecord(DBData
&data
)
520 DBPacket
&packet
= m_loader
->m_packet
;
521 packet
.SetNewReceive(data
.UseData());
524 // advance! (use same data as in packet)
525 m_desktop
.m_socket
->NextRecord(data
.UseData());
527 if( packet
.Command() == SB_COMMAND_DB_DATA
) {
528 packet
.ParseMeta(data
);
531 } while( m_loader
->m_packet
.Command() != SB_COMMAND_DB_DONE
);
537 } // namespace Barry::Mode
543 //////////////////////////////////////////////////////////////////////////////
544 // DeviceBuilder class
546 DeviceBuilder::DeviceBuilder(Mode::Desktop
&desktop
)
554 // searches the dbdb from the desktop to find the dbId,
555 // returns false if not found, and adds it to the list of
556 // databases to retrieve if found
557 bool DeviceBuilder::Add(const std::string
&dbname
)
560 DBLabel
id(m_desktop
.GetDBID(dbname
), dbname
);
561 m_dbIds
.push_back(id
);
564 catch( Barry::Error
& ) {
565 // GetDBID() throws on error...
570 void DeviceBuilder::Add(const Barry::DatabaseDatabase
&dbdb
)
572 DatabaseDatabase::DatabaseArrayType::const_iterator
573 b
= dbdb
.Databases
.begin(),
574 e
= dbdb
.Databases
.end();
576 for( ; b
!= e
; ++b
) {
577 // hmmm, could optimize this and only add ids
578 // with RecordCount > 0, but let's stick with this
579 // for now... it might flush bugs out of the system
580 DBLabel
id(b
->Number
, b
->Name
);
581 m_dbIds
.push_back(id
);
585 bool DeviceBuilder::BuildRecord(DBData
&data
,
587 const IConverter
*ic
)
590 if( !FetchRecord(temp
, ic
) )
594 data
.SetVersion(temp
.GetVersion());
595 data
.SetDBName(temp
.GetDBName());
596 data
.SetIds(temp
.GetRecType(), temp
.GetUniqueId());
597 data
.SetOffset(offset
);
599 // copy data from temp into the given offset
600 size_t tempsize
= temp
.GetData().GetSize() - temp
.GetOffset();
601 data
.UseData().MemCpy(offset
,
602 temp
.GetData().GetData() + temp
.GetOffset(), tempsize
);
603 data
.UseData().ReleaseBuffer(offset
+ tempsize
);
607 bool DeviceBuilder::FetchRecord(DBData
&data
, const IConverter
*ic
)
611 if( !m_dbIds
.size() )
612 return false; // nothing to do
615 m_current
= m_dbIds
.begin();
616 ret
= m_loader
.StartDBLoad(m_current
->id
, data
);
619 else if( m_loader
.IsBusy() ) {
620 ret
= m_loader
.GetNextRecord(data
);
623 // don't do anything if we're at the end of our rope
627 // advance and check again... m_current always points
633 ret
= m_loader
.StartDBLoad(m_current
->id
, data
);
636 // fill in the DBname if successful
638 data
.SetDBName(m_current
->name
);
643 bool DeviceBuilder::EndOfFile() const
645 return m_current
== m_dbIds
.end();
650 //////////////////////////////////////////////////////////////////////////////
651 // DeviceParser class
653 DeviceParser::DeviceParser(Mode::Desktop
&desktop
, WriteMode mode
)
659 DeviceParser::~DeviceParser()
663 void DeviceParser::StartDB(const DBData
&data
, const IConverter
*ic
)
667 m_current_db
= data
.GetDBName();
668 if( !m_desktop
.GetDBDB().GetDBNumber(m_current_db
, m_current_dbid
) ) {
669 // doh! This database does not exist in this device
670 dout(_("This database does not exist in device: ") << m_current_db
<< ". " << _("Dropping record."));
671 m_current_db
.clear();
677 WriteMode mode
= m_mode
;
678 if( mode
== DECIDE_BY_CALLBACK
)
679 mode
= DecideWrite(data
);
683 case ERASE_ALL_WRITE_ALL
:
684 m_desktop
.ClearDatabase(m_current_dbid
);
688 case INDIVIDUAL_OVERWRITE
:
689 case ADD_BUT_NO_OVERWRITE
:
690 case ADD_WITH_NEW_ID
:
691 m_desktop
.GetRecordStateTable(m_current_dbid
, m_rstate
);
698 case DECIDE_BY_CALLBACK
:
700 throw std::logic_error(_("DeviceParser: unknown mode"));
704 void DeviceParser::WriteNext(const DBData
&data
, const IConverter
*ic
)
707 WriteMode mode
= m_mode
;
708 if( mode
== DECIDE_BY_CALLBACK
)
709 mode
= DecideWrite(data
);
711 // create fast copy with our own metadata
712 DBData
local(data
.GetVersion(), data
.GetDBName(),
713 data
.GetRecType(), data
.GetUniqueId(), data
.GetOffset(),
714 data
.GetData().GetData(), data
.GetData().GetSize());
715 DBDataBuilder
dbuild(local
);
717 RecordStateTable::IndexType index
;
721 case ERASE_ALL_WRITE_ALL
:
722 // just do an AddRecord()
723 m_desktop
.AddRecord(m_current_dbid
, dbuild
);
726 case INDIVIDUAL_OVERWRITE
:
727 // search the state table, overwrite existing, and add new
728 if( m_rstate
.GetIndex(local
.GetUniqueId(), &index
) ) {
729 // found this record ID, use the index
730 m_desktop
.SetRecord(m_current_dbid
, index
, dbuild
);
734 m_desktop
.AddRecord(m_current_dbid
, dbuild
);
738 case ADD_BUT_NO_OVERWRITE
:
739 if( !m_rstate
.GetIndex(local
.GetUniqueId()) ) {
740 // no such record ID, so safe to add as new
741 m_desktop
.AddRecord(m_current_dbid
, dbuild
);
746 case ADD_WITH_NEW_ID
:
747 // use state table to create new id, and add as new
748 local
.SetIds(local
.GetRecType(), m_rstate
.MakeNewRecordId());
749 m_desktop
.AddRecord(m_current_dbid
, dbuild
);
755 case DECIDE_BY_CALLBACK
:
757 throw std::logic_error(_("DeviceParser: unknown mode"));
761 void DeviceParser::ParseRecord(const DBData
&data
, const IConverter
*ic
)
763 if( data
.GetDBName() == m_current_db
) {