lib: added Backup parser and Restore builder classes
[barry.git] / src / m_desktop.cc
blobc265443644f2862318a8600f8b6a1be9b65b270e
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 namespace Barry { namespace Mode {
39 ///////////////////////////////////////////////////////////////////////////////
40 // Desktop Mode class
42 Desktop::Desktop(Controller &con)
43 : Mode(con, Controller::Desktop)
44 , m_ic(0)
48 Desktop::Desktop(Controller &con, const IConverter &ic)
49 : Mode(con, Controller::Desktop)
50 , m_ic(&ic)
54 Desktop::~Desktop()
58 ///////////////////////////////////////////////////////////////////////////////
59 // protected members
61 void Desktop::LoadCommandTable()
63 char rawCommand[] = { 6, 0, 0x0a, 0, 0x40, 0, 0, 1, 0, 0 };
64 *((uint16_t*) rawCommand) = htobs(m_socket->GetSocket());
66 Data command(rawCommand, sizeof(rawCommand));
67 Data response;
69 try {
70 m_socket->Packet(command, response);
72 MAKE_PACKET(rpack, response);
73 while( rpack->command != SB_COMMAND_DB_DONE ) {
74 m_socket->NextRecord(response);
76 rpack = (const Protocol::Packet *) response.GetData();
77 if( rpack->command == SB_COMMAND_DB_DATA && btohs(rpack->size) > 10 ) {
78 // second packet is generally large, and contains
79 // the command table
80 m_commandTable.Clear();
81 m_commandTable.Parse(response, 6);
85 ddout(m_commandTable);
88 catch( Usb::Error & ) {
89 eout("Desktop: error getting command table");
90 eeout(command, response);
91 throw;
95 void Desktop::LoadDBDB()
97 Data command, response;
98 DBPacket packet(*this, command, 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(response);
109 // advance!
110 m_socket->NextRecord(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 Data command, response;
195 DBPacket packet(*this, command, response);
196 packet.GetRecordStateTable(dbId);
198 m_socket->Packet(packet);
199 result.Parse(response);
201 // flush the command sequence
202 while( packet.Command() != SB_COMMAND_DB_DONE )
203 m_socket->NextRecord(response);
207 // AddRecord
209 /// Adds a record to the specified database. RecordId is
210 /// retrieved from build, and duplicate IDs are allowed by the device
211 /// (i.e. you can have two records with the same ID)
212 /// but *not* recommended!
214 void Desktop::AddRecord(unsigned int dbId, Builder &build)
216 dout("Database ID: " << dbId);
218 Data command, response;
219 DBPacket packet(*this, command, response);
221 if( packet.SetRecord(dbId, build, m_ic) ) {
223 std::ostringstream oss;
225 m_socket->Packet(packet);
227 // successful packet transfer, so check the network return code
228 if( packet.Command() != SB_COMMAND_DB_DONE ) {
229 oss << "Desktop: device responded with unexpected packet command code: "
230 << "0x" << std::hex << packet.Command();
231 throw Error(oss.str());
234 if( packet.ReturnCode() != 0 ) {
235 oss << "Desktop: device responded with error code (command: "
236 << packet.Command() << ", code: "
237 << packet.ReturnCode() << ")";
238 throw Error(oss.str());
244 // GetRecord
246 /// Retrieves a specific record from the specified database.
247 /// The stateTableIndex comes from the GetRecordStateTable()
248 /// function. GetRecord() does not clear the dirty flag.
250 void Desktop::GetRecord(unsigned int dbId,
251 unsigned int stateTableIndex,
252 Parser &parser)
254 dout("Database ID: " << dbId);
256 std::string dbName;
257 m_dbdb.GetDBName(dbId, dbName);
259 Data command, response;
260 DBPacket packet(*this, command, response);
261 packet.GetRecordByIndex(dbId, stateTableIndex);
263 m_socket->Packet(packet);
265 // perform copious packet checks
266 if( response.GetSize() < SB_PACKET_RESPONSE_HEADER_SIZE ) {
267 eeout(command, response);
269 std::ostringstream oss;
270 oss << "Desktop: invalid response packet size of "
271 << std::dec << response.GetSize();
272 eout(oss.str());
273 throw Error(oss.str());
275 if( packet.Command() != SB_COMMAND_DB_DATA ) {
276 eeout(command, response);
278 std::ostringstream oss;
279 oss << "Desktop: unexpected command of 0x"
280 << std::setbase(16) << packet.Command()
281 << " instead of expected 0x"
282 << std::setbase(16) << (unsigned int)SB_COMMAND_DB_DATA;
283 eout(oss.str());
284 throw Error(oss.str());
287 // grab that data
288 packet.Parse(parser, dbName, m_ic);
290 // flush the command sequence
291 while( packet.Command() != SB_COMMAND_DB_DONE )
292 m_socket->NextRecord(response);
296 // SetRecord
298 /// Overwrites a specific record in the device as identified by the
299 /// stateTableIndex.
301 void Desktop::SetRecord(unsigned int dbId, unsigned int stateTableIndex,
302 Builder &build)
304 dout("Database ID: " << dbId << " Index: " << stateTableIndex);
306 Data command, response;
307 DBPacket packet(*this, command, response);
309 // loop until builder object has no more data
310 if( !packet.SetRecordByIndex(dbId, stateTableIndex, build, m_ic) ) {
311 throw std::logic_error("Desktop: no data available in SetRecord");
314 m_socket->Packet(packet);
316 std::ostringstream oss;
318 // successful packet transfer, so check the network return code
319 if( packet.Command() != SB_COMMAND_DB_DONE ) {
320 oss << "Desktop: device responded with unexpected packet command code: "
321 << "0x" << std::hex << packet.Command();
322 throw Error(oss.str());
325 if( packet.ReturnCode() != 0 ) {
326 oss << "Desktop: device responded with error code (command: "
327 << packet.Command() << ", code: "
328 << packet.ReturnCode() << ")";
329 throw Error(oss.str());
334 // ClearDirty
336 /// Clears the dirty flag on the specified record in the specified database.
338 void Desktop::ClearDirty(unsigned int dbId, unsigned int stateTableIndex)
340 dout("Database ID: " << dbId);
342 Data command, response;
343 DBPacket packet(*this, command, response);
344 packet.SetRecordFlags(dbId, stateTableIndex, 0);
346 m_socket->Packet(packet);
348 // flush the command sequence
349 while( packet.Command() != SB_COMMAND_DB_DONE )
350 m_socket->NextRecord(response);
354 // DeleteRecord
356 /// Deletes the specified record in the specified database.
358 void Desktop::DeleteRecord(unsigned int dbId, unsigned int stateTableIndex)
360 dout("Database ID: " << dbId);
362 Data command, response;
363 DBPacket packet(*this, command, response);
364 packet.DeleteRecordByIndex(dbId, stateTableIndex);
366 m_socket->Packet(packet);
368 // flush the command sequence
369 while( packet.Command() != SB_COMMAND_DB_DONE )
370 m_socket->NextRecord(response);
374 // LoadDatabase
376 /// Retrieve a database from the handheld device, using the given parser
377 /// to parse the resulting data, and optionally store it.
379 /// See the RecordParser<> template to create a parser object. The
380 /// RecordParser<> template allows custom storage based on the type of
381 /// database record retrieved. The database ID and the parser Record
382 /// type must match.
384 /// \param[in] dbId Database Database ID - use GetDBID()
385 /// \param[out] parser Parser object which parses the resulting
386 /// protocol data, and optionally stores it in
387 /// a custom fashion. See the RecordParser<>
388 /// template.
390 /// \exception Barry::Error
391 /// Thrown on protocol error.
393 /// \exception std::logic_error
394 /// Thrown if not in Desktop mode.
396 void Desktop::LoadDatabase(unsigned int dbId, Parser &parser)
398 dout("Database ID: " << dbId);
400 std::string dbName;
401 m_dbdb.GetDBName(dbId, dbName);
403 Data command, response;
404 DBPacket packet(*this, command, response);
405 packet.GetRecords(dbId);
407 m_socket->Packet(packet);
409 while( packet.Command() != SB_COMMAND_DB_DONE ) {
410 if( packet.Command() == SB_COMMAND_DB_DATA ) {
411 // this size is the old header size, since using
412 // old command above
413 packet.Parse(parser, dbName, m_ic);
416 // advance!
417 m_socket->NextRecord(response);
421 void Desktop::ClearDatabase(unsigned int dbId)
423 dout("Database ID: " << dbId);
425 Data command, response;
426 DBPacket packet(*this, command, response);
427 packet.ClearDatabase(dbId);
429 // wait up to a minute here for old, slower devices with lots of data
430 m_socket->Packet(packet, 60000);
431 if( packet.ReturnCode() != 0 ) {
432 std::ostringstream oss;
433 oss << "Desktop: could not clear database: (command: "
434 << "0x" << std::hex << packet.Command() << ", code: "
435 << "0x" << std::hex << packet.ReturnCode() << ")";
436 throw Error(oss.str());
439 // check response to clear command was successful
440 if( packet.Command() != SB_COMMAND_DB_DONE ) {
441 eeout(command, response);
442 throw Error("Desktop: error clearing database, bad response");
446 void Desktop::SaveDatabase(unsigned int dbId, Builder &builder)
448 dout("Database ID: " << dbId);
450 // Protocol note: so far in testing, this CLEAR_DATABASE operation is
451 // required, since every record sent via SET_RECORD
452 // is treated like a hypothetical "ADD_RECORD" (perhaps
453 // SET_RECORD should be renamed)... I don't know if
454 // there is a real SET_RECORD... all I know is from
455 // the Windows USB captures, which uses this same
456 // technique.
457 ClearDatabase(dbId);
459 Data command, response;
460 DBPacket packet(*this, command, response);
462 // loop until builder object has no more data
463 bool first = true;
464 while( packet.SetRecord(dbId, builder, m_ic) ) {
465 dout("Database ID: " << dbId);
467 m_socket->Packet(packet, first ? 60000 : -1);
468 first = false;
470 std::ostringstream oss;
471 // successful packet transfer, so check the network return code
472 if( packet.Command() != SB_COMMAND_DB_DONE ) {
473 oss << "Desktop: device responded with unexpected packet command code: "
474 << "0x" << std::hex << packet.Command();
475 throw Error(oss.str());
478 if( packet.ReturnCode() != 0 ) {
479 oss << "Desktop: device responded with error code (command: "
480 << packet.Command() << ", code: "
481 << packet.ReturnCode() << ")";
482 throw Error(oss.str());
487 }} // namespace Barry::Mode