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"
36 #include "parser.h" // FIXME - remove
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 };
66 *((uint16_t*) rawCommand
) = htobs(m_socket
->GetSocket());
68 Data
command(rawCommand
, sizeof(rawCommand
));
71 m_socket
->Packet(command
, m_response
);
73 MAKE_PACKET(rpack
, m_response
);
74 while( rpack
->command
!= SB_COMMAND_DB_DONE
) {
75 m_socket
->NextRecord(m_response
);
77 rpack
= (const Protocol::Packet
*) m_response
.GetData();
78 if( rpack
->command
== SB_COMMAND_DB_DATA
&& btohs(rpack
->size
) > 10 ) {
79 // second packet is generally large, and contains
81 m_commandTable
.Clear();
82 m_commandTable
.Parse(m_response
, 6);
86 ddout(m_commandTable
);
89 catch( Usb::Error
& ) {
90 eout("Desktop: error getting command table");
91 eeout(command
, m_response
);
96 void Desktop::LoadDBDB()
98 DBPacket
packet(*this, m_command
, m_response
);
101 m_socket
->Packet(packet
);
103 while( packet
.Command() != SB_COMMAND_DB_DONE
) {
104 if( packet
.Command() == SB_COMMAND_DB_DATA
) {
106 m_dbdb
.Parse(m_response
);
110 m_socket
->NextRecord(m_response
);
114 void Desktop::OnOpen()
116 // get command table and database database
121 ///////////////////////////////////////////////////////////////////////////////
127 /// Get numeric database ID by name.
129 /// \param[in] name Name of database, which matches one of the
130 /// names listed in GetDBDB()
132 /// \exception Barry::Error
133 /// Thrown if name not found.
135 unsigned int Desktop::GetDBID(const std::string
&name
) const
138 // FIXME - this needs a better error handler...
139 if( !m_dbdb
.GetDBNumber(name
, ID
) ) {
140 throw Error("Desktop: database name not found: " + name
);
148 /// Get database command from command table. Must call Open()
151 unsigned int Desktop::GetDBCommand(CommandType ct
)
153 unsigned int cmd
= 0;
154 const char *cmdName
= "Unknown";
159 cmdName
= "Database Access";
160 cmd
= m_commandTable
.GetCommand(cmdName
);
163 throw std::logic_error("Desktop: unknown command type");
167 std::ostringstream oss
;
168 oss
<< "Desktop: unable to get command code: " << cmdName
;
169 throw Error(oss
.str());
175 void Desktop::SetIConverter(const IConverter
&ic
)
181 // GetRecordStateTable
183 /// Retrieve the record state table from the handheld device, using the given
184 /// database ID. Results will be stored in result, which will be cleared
187 void Desktop::GetRecordStateTable(unsigned int dbId
, RecordStateTable
&result
)
189 dout("Database ID: " << dbId
);
194 DBPacket
packet(*this, m_command
, m_response
);
195 packet
.GetRecordStateTable(dbId
);
197 m_socket
->Packet(packet
);
198 result
.Parse(m_response
);
200 // flush the command sequence
201 while( packet
.Command() != SB_COMMAND_DB_DONE
)
202 m_socket
->NextRecord(m_response
);
208 /// Adds a record to the specified database. RecordId is
209 /// retrieved from build, and duplicate IDs are allowed by the device
210 /// (i.e. you can have two records with the same ID)
211 /// but *not* recommended!
213 void Desktop::AddRecord(unsigned int dbId
, Builder
&build
)
215 dout("Database ID: " << dbId
);
217 DBPacket
packet(*this, m_command
, m_response
);
219 if( packet
.SetRecord(dbId
, build
, m_ic
) ) {
221 std::ostringstream oss
;
223 m_socket
->Packet(packet
);
225 // successful packet transfer, so check the network return code
226 if( packet
.Command() != SB_COMMAND_DB_DONE
) {
227 oss
<< "Desktop: device responded with unexpected packet command code: "
228 << "0x" << std::hex
<< packet
.Command();
229 throw Error(oss
.str());
232 if( packet
.ReturnCode() != 0 ) {
233 oss
<< "Desktop: device responded with error code (command: "
234 << packet
.Command() << ", code: "
235 << packet
.ReturnCode() << ")";
236 throw Error(oss
.str());
244 /// Retrieves a specific record from the specified database.
245 /// The stateTableIndex comes from the GetRecordStateTable()
246 /// function. GetRecord() does not clear the dirty flag.
248 void Desktop::GetRecord(unsigned int dbId
,
249 unsigned int stateTableIndex
,
252 dout("Database ID: " << dbId
);
255 m_dbdb
.GetDBName(dbId
, dbName
);
257 DBPacket
packet(*this, m_command
, m_response
);
258 packet
.GetRecordByIndex(dbId
, stateTableIndex
);
260 m_socket
->Packet(packet
);
262 // perform copious packet checks
263 if( m_response
.GetSize() < SB_PACKET_RESPONSE_HEADER_SIZE
) {
264 eeout(m_command
, m_response
);
266 std::ostringstream oss
;
267 oss
<< "Desktop: invalid response packet size of "
268 << std::dec
<< m_response
.GetSize();
270 throw Error(oss
.str());
272 if( packet
.Command() != SB_COMMAND_DB_DATA
) {
273 eeout(m_command
, m_response
);
275 std::ostringstream oss
;
276 oss
<< "Desktop: unexpected command of 0x"
277 << std::setbase(16) << packet
.Command()
278 << " instead of expected 0x"
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 // loop until builder object has no more 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 Error(oss
.str());
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, data
);
394 bool loading
= loader
.StartDBLoad(dbId
);
396 // manual parser call
397 // FIXME - parser should use DBData
399 parser
.SetIds(data
.GetDBName(),
400 data
.GetRecType(), data
.GetUniqueId());
401 size_t offset
= data
.GetOffset();
402 parser
.ParseHeader(data
.GetData(), offset
);
403 parser
.ParseFields(data
.GetData(), offset
, m_ic
);
407 loading
= loader
.GetNextRecord();
411 void Desktop::ClearDatabase(unsigned int dbId
)
413 dout("Database ID: " << dbId
);
415 DBPacket
packet(*this, m_command
, m_response
);
416 packet
.ClearDatabase(dbId
);
418 // wait up to a minute here for old, slower devices with lots of data
419 m_socket
->Packet(packet
, 60000);
420 if( packet
.ReturnCode() != 0 ) {
421 std::ostringstream oss
;
422 oss
<< "Desktop: could not clear database: (command: "
423 << "0x" << std::hex
<< packet
.Command() << ", code: "
424 << "0x" << std::hex
<< packet
.ReturnCode() << ")";
425 throw Error(oss
.str());
428 // check response to clear command was successful
429 if( packet
.Command() != SB_COMMAND_DB_DONE
) {
430 eeout(m_command
, m_response
);
431 throw Error("Desktop: error clearing database, bad response");
435 void Desktop::SaveDatabase(unsigned int dbId
, Builder
&builder
)
437 dout("Database ID: " << dbId
);
439 // Protocol note: so far in testing, this CLEAR_DATABASE operation is
440 // required, since every record sent via SET_RECORD
441 // is treated like a hypothetical "ADD_RECORD" (perhaps
442 // SET_RECORD should be renamed)... I don't know if
443 // there is a real SET_RECORD... all I know is from
444 // the Windows USB captures, which uses this same
448 DBPacket
packet(*this, m_command
, m_response
);
450 // loop until builder object has no more data
452 while( packet
.SetRecord(dbId
, builder
, m_ic
) ) {
453 dout("Database ID: " << dbId
);
455 m_socket
->Packet(packet
, first
? 60000 : -1);
458 std::ostringstream oss
;
459 // successful packet transfer, so check the network return code
460 if( packet
.Command() != SB_COMMAND_DB_DONE
) {
461 oss
<< "Desktop: device responded with unexpected packet command code: "
462 << "0x" << std::hex
<< packet
.Command();
463 throw Error(oss
.str());
466 if( packet
.ReturnCode() != 0 ) {
467 oss
<< "Desktop: device responded with error code (command: "
468 << packet
.Command() << ", code: "
469 << packet
.ReturnCode() << ")";
470 throw Error(oss
.str());
477 //////////////////////////////////////////////////////////////////////////////
484 DBLoaderData(Desktop
&desktop
, Data
&command
, Data
&response
)
485 : m_packet(desktop
, command
, response
)
490 DBLoader::DBLoader(Desktop
&desktop
, DBData
&data
)
491 : m_loader(new DBLoaderData(desktop
, desktop
.m_command
, data
.UseData()))
498 DBLoader::~DBLoader()
503 bool DBLoader::StartDBLoad(unsigned int dbId
)
505 dout("Database ID: " << dbId
);
508 m_desktop
.m_dbdb
.GetDBName(dbId
, m_dbName
);
510 m_loader
->m_packet
.GetRecords(dbId
);
511 m_desktop
.m_socket
->Packet(m_loader
->m_packet
);
513 while( m_loader
->m_packet
.Command() != SB_COMMAND_DB_DONE
) {
514 if( m_loader
->m_packet
.Command() == SB_COMMAND_DB_DATA
) {
515 // this size is the old header size, since using
517 m_loader
->m_packet
.ParseMeta(m_data
);
518 m_data
.SetDBName(m_dbName
);
523 m_desktop
.m_socket
->NextRecord(m_data
.UseData());
530 bool DBLoader::GetNextRecord()
537 m_desktop
.m_socket
->NextRecord(m_data
.UseData());
539 if( m_loader
->m_packet
.Command() == SB_COMMAND_DB_DATA
) {
540 // this size is the old header size, since using
542 m_loader
->m_packet
.ParseMeta(m_data
);
545 } while( m_loader
->m_packet
.Command() != SB_COMMAND_DB_DONE
);
551 }} // namespace Barry::Mode