lib: added DBData and DBLoader
[barry.git] / src / m_desktop.cc
blob9204e1f7081c610aab16e52510e6ab432aed337d
1 ///
2 /// \file m_desktop.cc
3 /// Mode class for the Desktop mode
4 ///
6 /*
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"
23 #include "data.h"
24 #include "protocol.h"
25 #include "protostructs.h"
26 #include "packet.h"
27 #include "endian.h"
28 #include "error.h"
29 #include "usbwrap.h"
30 #include "controller.h"
31 #include <stdexcept>
32 #include <sstream>
34 #include "debug.h"
36 #include "parser.h" // FIXME - remove
38 namespace Barry { namespace Mode {
41 ///////////////////////////////////////////////////////////////////////////////
42 // Desktop Mode class
44 Desktop::Desktop(Controller &con)
45 : Mode(con, Controller::Desktop)
46 , m_ic(0)
50 Desktop::Desktop(Controller &con, const IConverter &ic)
51 : Mode(con, Controller::Desktop)
52 , m_ic(&ic)
56 Desktop::~Desktop()
60 ///////////////////////////////////////////////////////////////////////////////
61 // protected members
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));
70 try {
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
80 // the command table
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);
92 throw;
96 void Desktop::LoadDBDB()
98 DBPacket packet(*this, m_command, m_response);
99 packet.GetDBDB();
101 m_socket->Packet(packet);
103 while( packet.Command() != SB_COMMAND_DB_DONE ) {
104 if( packet.Command() == SB_COMMAND_DB_DATA ) {
105 m_dbdb.Clear();
106 m_dbdb.Parse(m_response);
109 // advance!
110 m_socket->NextRecord(m_response);
114 void Desktop::OnOpen()
116 // get command table and database database
117 LoadCommandTable();
118 LoadDBDB();
121 ///////////////////////////////////////////////////////////////////////////////
122 // public API
125 // GetDBID
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
137 unsigned int ID = 0;
138 // FIXME - this needs a better error handler...
139 if( !m_dbdb.GetDBNumber(name, ID) ) {
140 throw Error("Desktop: database name not found: " + name);
142 return ID;
146 // GetDBCommand
148 /// Get database command from command table. Must call Open()
149 /// before this.
151 unsigned int Desktop::GetDBCommand(CommandType ct)
153 unsigned int cmd = 0;
154 const char *cmdName = "Unknown";
156 switch( ct )
158 case DatabaseAccess:
159 cmdName = "Database Access";
160 cmd = m_commandTable.GetCommand(cmdName);
161 break;
162 default:
163 throw std::logic_error("Desktop: unknown command type");
166 if( cmd == 0 ) {
167 std::ostringstream oss;
168 oss << "Desktop: unable to get command code: " << cmdName;
169 throw Error(oss.str());
172 return cmd;
175 void Desktop::SetIConverter(const IConverter &ic)
177 m_ic = &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
185 /// before adding.
187 void Desktop::GetRecordStateTable(unsigned int dbId, RecordStateTable &result)
189 dout("Database ID: " << dbId);
191 // start fresh
192 result.Clear();
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);
206 // AddRecord
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());
242 // GetRecord
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,
250 Parser &parser)
252 dout("Database ID: " << dbId);
254 std::string dbName;
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();
269 eout(oss.str());
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;
280 eout(oss.str());
281 throw Error(oss.str());
284 // grab that data
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);
293 // SetRecord
295 /// Overwrites a specific record in the device as identified by the
296 /// stateTableIndex.
298 void Desktop::SetRecord(unsigned int dbId, unsigned int stateTableIndex,
299 Builder &build)
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());
330 // ClearDirty
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);
349 // DeleteRecord
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);
368 // LoadDatabase
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
376 /// type must match.
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<>
382 /// template.
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)
392 DBData data;
393 DBLoader loader(*this, data);
394 bool loading = loader.StartDBLoad(dbId);
395 while( loading ) {
396 // manual parser call
397 // FIXME - parser should use DBData
398 parser.Clear();
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);
404 parser.Store();
406 // advance!
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
445 // technique.
446 ClearDatabase(dbId);
448 DBPacket packet(*this, m_command, m_response);
450 // loop until builder object has no more data
451 bool first = true;
452 while( packet.SetRecord(dbId, builder, m_ic) ) {
453 dout("Database ID: " << dbId);
455 m_socket->Packet(packet, first ? 60000 : -1);
456 first = false;
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 //////////////////////////////////////////////////////////////////////////////
478 // DBLoader class
481 struct DBLoaderData
483 DBPacket m_packet;
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()))
492 , m_desktop(desktop)
493 , m_data(data)
494 , m_loading(false)
498 DBLoader::~DBLoader()
500 delete m_loader;
503 bool DBLoader::StartDBLoad(unsigned int dbId)
505 dout("Database ID: " << dbId);
507 m_loading = true;
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
516 // old command above
517 m_loader->m_packet.ParseMeta(m_data);
518 m_data.SetDBName(m_dbName);
519 return true;
522 // advance!
523 m_desktop.m_socket->NextRecord(m_data.UseData());
526 m_loading = false;
527 return false;
530 bool DBLoader::GetNextRecord()
532 if( !m_loading )
533 return false;
535 do {
536 // advance!
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
541 // old command above
542 m_loader->m_packet.ParseMeta(m_data);
543 return true;
545 } while( m_loader->m_packet.Command() != SB_COMMAND_DB_DONE );
547 m_loading = false;
548 return false;
551 }} // namespace Barry::Mode