i18n work in progress... desperately needs some housecleaning.
[barry.git] / src / m_desktop.cc
blobd3e3fae63b8e9e90b3a4b971e0e67f3d7798fe77
1 ///
2 /// \file m_desktop.cc
3 /// Mode class for the Desktop mode
4 ///
6 /*
7 Copyright (C) 2005-2007, 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 : m_con(con)
44 , m_ModeSocket(0)
48 Desktop::~Desktop()
52 ///////////////////////////////////////////////////////////////////////////////
53 // protected members
55 void Desktop::LoadCommandTable()
57 char rawCommand[] = { 6, 0, 0x0a, 0, 0x40, 0, 0, 1, 0, 0 };
58 *((uint16_t*) rawCommand) = htobs(m_socket->GetSocket());
60 Data command(rawCommand, sizeof(rawCommand));
61 Data response;
63 try {
64 m_socket->Packet(command, response);
66 MAKE_PACKET(rpack, response);
67 while( rpack->command != SB_COMMAND_DB_DONE ) {
68 m_socket->NextRecord(response);
70 rpack = (const Protocol::Packet *) response.GetData();
71 if( rpack->command == SB_COMMAND_DB_DATA && btohs(rpack->size) > 10 ) {
72 // second packet is generally large, and contains
73 // the command table
74 m_commandTable.Clear();
75 m_commandTable.Parse(response, 6);
79 ddout(m_commandTable);
82 catch( Usb::Error & ) {
83 eout("Desktop: error getting command table");
84 eeout(command, response);
85 throw;
89 void Desktop::LoadDBDB()
91 Data command, response;
92 DBPacket packet(*this, command, response);
93 packet.GetDBDB();
95 m_socket->Packet(packet);
97 while( packet.Command() != SB_COMMAND_DB_DONE ) {
98 if( packet.Command() == SB_COMMAND_DB_DATA ) {
99 m_dbdb.Clear();
100 m_dbdb.Parse(response);
103 // advance!
104 m_socket->NextRecord(response);
110 ///////////////////////////////////////////////////////////////////////////////
111 // public API
114 // Open
116 /// Select device mode. This is required before using any other mode-based
117 /// operations, such as GetDBDB() and LoadDatabase().
119 /// This function opens a socket to the device for communicating in Desktop
120 /// mode. If the device requires it, specify the password with a const char*
121 /// string in password. The password will not be stored in memory
122 /// inside this class, only a hash will be generated from it. After
123 /// using the hash, the hash memory will be set to 0. The application
124 /// is responsible for safely handling the raw password data.
126 /// You can retry the password by catching Barry::BadPassword and
127 /// calling RetryPassword() with the new password.
129 /// \exception Barry::Error
130 /// Thrown on protocol error.
132 /// \exception std::logic_error()
133 /// Thrown if unsupported mode is requested, or if socket
134 /// already open.
136 /// \exception Barry::BadPassword
137 /// Thrown when password is invalid or if not enough retries
138 /// left in the device.
140 void Desktop::Open(const char *password)
142 if( m_ModeSocket ) {
143 m_socket->Close();
144 m_socket.reset();
145 m_ModeSocket = 0;
148 m_ModeSocket = m_con.SelectMode(Controller::Desktop);
149 RetryPassword(password);
153 // RetryPassword
155 /// Retry a failed password attempt from the first call to Open().
156 /// Only call this function in response to Barry::BadPassword exceptions
157 /// that are thrown from Open().
159 /// \exception Barry::Error
160 /// Thrown on protocol error.
162 /// \exception std::logic_error()
163 /// Thrown if in unsupported mode, or if socket already open.
165 /// \exception Barry::BadPassword
166 /// Thrown when password is invalid or if not enough retries
167 /// left in the device.
169 void Desktop::RetryPassword(const char *password)
171 if( m_socket.get() != 0 )
172 throw std::logic_error("Socket alreay open in RetryPassword");
174 m_socket = m_con.m_zero.Open(m_ModeSocket, password);
176 // get command table and database database
177 LoadCommandTable();
178 LoadDBDB();
182 // GetDBID
184 /// Get numeric database ID by name.
186 /// \param[in] name Name of database, which matches one of the
187 /// names listed in GetDBDB()
189 /// \exception Barry::Error
190 /// Thrown if name not found.
192 unsigned int Desktop::GetDBID(const std::string &name) const
194 unsigned int ID = 0;
195 // FIXME - this needs a better error handler...
196 if( !m_dbdb.GetDBNumber(name, ID) ) {
197 throw Error("Desktop: database name not found: " + name);
199 return ID;
203 // GetDBCommand
205 /// Get database command from command table. Must call Open()
206 /// before this.
208 unsigned int Desktop::GetDBCommand(CommandType ct)
210 unsigned int cmd = 0;
211 const char *cmdName = "Unknown";
213 switch( ct )
215 case DatabaseAccess:
216 cmdName = "Database Access";
217 cmd = m_commandTable.GetCommand(cmdName);
218 break;
219 default:
220 throw std::logic_error("Desktop: unknown command type");
223 if( cmd == 0 ) {
224 std::ostringstream oss;
225 oss << "Desktop: unable to get command code: " << cmdName;
226 throw Error(oss.str());
229 return cmd;
233 // GetRecordStateTable
235 /// Retrieve the record state table from the handheld device, using the given
236 /// database ID. Results will be stored in result, which will be cleared
237 /// before adding.
239 void Desktop::GetRecordStateTable(unsigned int dbId, RecordStateTable &result)
241 dout("Database ID: " << dbId);
243 // start fresh
244 result.Clear();
246 Data command, response;
247 DBPacket packet(*this, command, response);
248 packet.GetRecordStateTable(dbId);
250 m_socket->Packet(packet);
251 result.Parse(response);
253 // flush the command sequence
254 while( packet.Command() != SB_COMMAND_DB_DONE )
255 m_socket->NextRecord(response);
259 // AddRecord
261 /// Adds a record to the specified database. RecordId is
262 /// retrieved from build, and duplicate IDs are allowed by the device
263 /// (i.e. you can have two records with the same ID)
264 /// but *not* recommended!
266 void Desktop::AddRecord(unsigned int dbId, Builder &build)
268 dout("Database ID: " << dbId);
270 Data command, response;
271 DBPacket packet(*this, command, response);
273 if( packet.SetRecord(dbId, build) ) {
275 std::ostringstream oss;
277 m_socket->Packet(packet);
279 // successful packet transfer, so check the network return code
280 if( packet.Command() != SB_COMMAND_DB_DONE ) {
281 oss << "Desktop: device responded with unexpected packet command code: "
282 << "0x" << std::hex << packet.Command();
283 throw Error(oss.str());
286 if( packet.ReturnCode() != 0 ) {
287 oss << "Desktop: device responded with error code (command: "
288 << packet.Command() << ", code: "
289 << packet.ReturnCode() << ")";
290 throw Error(oss.str());
296 // GetRecord
298 /// Retrieves a specific record from the specified database.
299 /// The stateTableIndex comes from the GetRecordStateTable()
300 /// function. GetRecord() does not clear the dirty flag.
302 void Desktop::GetRecord(unsigned int dbId,
303 unsigned int stateTableIndex,
304 Parser &parser)
306 dout("Database ID: " << dbId);
308 Data command, response;
309 DBPacket packet(*this, command, response);
310 packet.GetRecordByIndex(dbId, stateTableIndex);
312 m_socket->Packet(packet);
314 // perform copious packet checks
315 if( response.GetSize() < SB_PACKET_RESPONSE_HEADER_SIZE ) {
316 eeout(command, response);
318 std::ostringstream oss;
319 oss << "Desktop: invalid response packet size of "
320 << std::dec << response.GetSize();
321 eout(oss.str());
322 throw Error(oss.str());
324 if( packet.Command() != SB_COMMAND_DB_DATA ) {
325 eeout(command, response);
327 std::ostringstream oss;
328 oss << "Desktop: unexpected command of 0x"
329 << std::setbase(16) << packet.Command()
330 << " instead of expected 0x"
331 << std::setbase(16) << (unsigned int)SB_COMMAND_DB_DATA;
332 eout(oss.str());
333 throw Error(oss.str());
336 // grab that data
337 packet.Parse(parser);
339 // flush the command sequence
340 while( packet.Command() != SB_COMMAND_DB_DONE )
341 m_socket->NextRecord(response);
345 // SetRecord
347 /// Overwrites a specific record in the device as identified by the
348 /// stateTableIndex.
350 void Desktop::SetRecord(unsigned int dbId, unsigned int stateTableIndex,
351 Builder &build)
353 dout("Database ID: " << dbId << " Index: " << stateTableIndex);
355 Data command, response;
356 DBPacket packet(*this, command, response);
358 // loop until builder object has no more data
359 if( !packet.SetRecordByIndex(dbId, stateTableIndex, build) ) {
360 throw std::logic_error("Desktop: no data available in SetRecord");
363 m_socket->Packet(packet);
365 std::ostringstream oss;
367 // successful packet transfer, so check the network return code
368 if( packet.Command() != SB_COMMAND_DB_DONE ) {
369 oss << "Desktop: device responded with unexpected packet command code: "
370 << "0x" << std::hex << packet.Command();
371 throw Error(oss.str());
374 if( packet.ReturnCode() != 0 ) {
375 oss << "Desktop: device responded with error code (command: "
376 << packet.Command() << ", code: "
377 << packet.ReturnCode() << ")";
378 throw Error(oss.str());
383 // ClearDirty
385 /// Clears the dirty flag on the specified record in the specified database.
387 void Desktop::ClearDirty(unsigned int dbId, unsigned int stateTableIndex)
389 dout("Database ID: " << dbId);
391 Data command, response;
392 DBPacket packet(*this, command, response);
393 packet.SetRecordFlags(dbId, stateTableIndex, 0);
395 m_socket->Packet(packet);
397 // flush the command sequence
398 while( packet.Command() != SB_COMMAND_DB_DONE )
399 m_socket->NextRecord(response);
403 // DeleteRecord
405 /// Deletes the specified record in the specified database.
407 void Desktop::DeleteRecord(unsigned int dbId, unsigned int stateTableIndex)
409 dout("Database ID: " << dbId);
411 Data command, response;
412 DBPacket packet(*this, command, response);
413 packet.DeleteRecordByIndex(dbId, stateTableIndex);
415 m_socket->Packet(packet);
417 // flush the command sequence
418 while( packet.Command() != SB_COMMAND_DB_DONE )
419 m_socket->NextRecord(response);
423 // LoadDatabase
425 /// Retrieve a database from the handheld device, using the given parser
426 /// to parse the resulting data, and optionally store it.
428 /// See the RecordParser<> template to create a parser object. The
429 /// RecordParser<> template allows custom storage based on the type of
430 /// database record retrieved. The database ID and the parser Record
431 /// type must match.
433 /// \param[in] dbId Database Database ID - use GetDBID()
434 /// \param[out] parser Parser object which parses the resulting
435 /// protocol data, and optionally stores it in
436 /// a custom fashion. See the RecordParser<>
437 /// template.
439 /// \exception Barry::Error
440 /// Thrown on protocol error.
442 /// \exception std::logic_error
443 /// Thrown if not in Desktop mode.
445 void Desktop::LoadDatabase(unsigned int dbId, Parser &parser)
447 dout("Database ID: " << dbId);
449 Data command, response;
450 DBPacket packet(*this, command, response);
451 packet.GetRecords(dbId);
453 m_socket->Packet(packet);
455 while( packet.Command() != SB_COMMAND_DB_DONE ) {
456 if( packet.Command() == SB_COMMAND_DB_DATA ) {
457 // this size is the old header size, since using
458 // old command above
459 packet.Parse(parser);
462 // advance!
463 m_socket->NextRecord(response);
467 void Desktop::SaveDatabase(unsigned int dbId, Builder &builder)
469 dout("Database ID: " << dbId);
471 // Protocol note: so far in testing, this CLEAR_DATABASE operation is
472 // required, since every record sent via SET_RECORD
473 // is treated like a hypothetical "ADD_RECORD" (perhaps
474 // SET_RECORD should be renamed)... I don't know if
475 // there is a real SET_RECORD... all I know is from
476 // the Windows USB captures, which uses this same
477 // technique.
478 Data command, response;
479 DBPacket packet(*this, command, response);
480 packet.ClearDatabase(dbId);
482 // wait up to a minute here for old, slower devices with lots of data
483 m_socket->Packet(packet, 60000);
484 if( packet.ReturnCode() != 0 ) {
485 std::ostringstream oss;
486 oss << "Desktop: could not clear database: (command: "
487 << "0x" << std::hex << packet.Command() << ", code: "
488 << "0x" << std::hex << packet.ReturnCode() << ")";
489 throw Error(oss.str());
492 // check response to clear command was successful
493 if( packet.Command() != SB_COMMAND_DB_DONE ) {
494 eeout(command, response);
495 throw Error("Desktop: error clearing database, bad response");
498 // loop until builder object has no more data
499 bool first = true;
500 while( packet.SetRecord(dbId, builder) ) {
501 dout("Database ID: " << dbId);
503 m_socket->Packet(packet, first ? 60000 : -1);
504 first = false;
506 std::ostringstream oss;
507 // successful packet transfer, so check the network return code
508 if( packet.Command() != SB_COMMAND_DB_DONE ) {
509 oss << "Desktop: device responded with unexpected packet command code: "
510 << "0x" << std::hex << packet.Command();
511 throw Error(oss.str());
514 if( packet.ReturnCode() != 0 ) {
515 oss << "Desktop: device responded with error code (command: "
516 << packet.Command() << ", code: "
517 << packet.ReturnCode() << ")";
518 throw Error(oss.str());
523 }} // namespace Barry::Mode