menu: added new Keywords tag to .desktop files
[barry.git] / src / m_desktop.cc
blobaae7709f5fb3cf53c3da363b4760f29e8a8401a9
1 ///
2 /// \file m_desktop.cc
3 /// Mode class for the Desktop mode
4 ///
6 /*
7 Copyright (C) 2005-2013, 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 "i18n.h"
23 #include "m_desktop.h"
24 #include "data.h"
25 #include "protocol.h"
26 #include "protostructs.h"
27 #include "packet.h"
28 #include "endian.h"
29 #include "error.h"
30 #include "usbwrap.h"
31 #include "controller.h"
32 #include "parser.h"
33 #include <stdexcept>
34 #include <sstream>
36 #include "debug.h"
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 };
67 Data command(rawCommand, sizeof(rawCommand));
69 try {
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
79 // the command table
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);
91 throw;
95 void Desktop::LoadDBDB()
97 DBPacket packet(*this, m_command, m_response);
98 packet.GetDBDB();
100 m_socket->Packet(packet);
102 while( packet.Command() != SB_COMMAND_DB_DONE ) {
103 if( packet.Command() == SB_COMMAND_DB_DATA ) {
104 m_dbdb.Clear();
105 m_dbdb.Parse(m_response);
108 // advance!
109 m_socket->NextRecord(m_response);
113 void Desktop::OnOpen()
115 // get command table and database database
116 LoadCommandTable();
117 LoadDBDB();
120 ///////////////////////////////////////////////////////////////////////////////
121 // public API
124 // GetDBID
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
136 unsigned int ID = 0;
137 // FIXME - this needs a better error handler...
138 if( !m_dbdb.GetDBNumber(name, ID) ) {
139 throw Error(_("Desktop: database name not found: ") + name);
141 return ID;
145 // GetDBCommand
147 /// Get database command from command table. Must call Open()
148 /// before this.
150 unsigned int Desktop::GetDBCommand(CommandType ct)
152 unsigned int cmd = 0;
153 const char *cmdName = "Unknown";
155 switch( ct )
157 case DatabaseAccess:
158 cmdName = "Database Access";
159 cmd = m_commandTable.GetCommand(cmdName);
160 break;
161 default:
162 throw std::logic_error(_("Desktop: unknown command type"));
165 if( cmd == 0 ) {
166 std::ostringstream oss;
167 oss << _("Desktop: unable to get command code: ") << cmdName;
168 throw Error(oss.str());
171 return cmd;
174 void Desktop::SetIConverter(const IConverter &ic)
176 m_ic = &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
184 /// before adding.
186 void Desktop::GetRecordStateTable(unsigned int dbId, RecordStateTable &result)
188 dout(_("Database ID: ") << dbId);
190 // start fresh
191 result.Clear();
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);
205 // AddRecord
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());
241 // GetRecord
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,
249 Parser &parser)
251 dout(_("Database ID: ") << dbId);
253 std::string dbName;
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();
268 eout(oss.str());
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 ")
278 << "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 // 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());
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);
394 bool loading = loader.StartDBLoad(dbId, data);
395 while( loading ) {
396 // manual parser call
397 parser.ParseRecord(data, m_ic);
399 // advance!
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() << ", "
417 << _("code: ")
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);
433 ClearDatabase(dbId);
435 DBPacket packet(*this, m_command, m_response);
437 // loop until builder object has no more data
438 bool first = true;
439 while( packet.AddRecord(dbId, builder, m_ic) ) {
440 dout(_("Database ID: ") << dbId);
442 m_socket->Packet(packet, first ? 60000 : -1);
443 first = false;
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 //////////////////////////////////////////////////////////////////////////////
465 // DBLoader class
467 struct DBLoaderData
469 DBPacket m_packet;
470 DBLoaderData(Desktop &desktop, Data &command, Data &response)
471 : m_packet(desktop, command, response)
476 DBLoader::DBLoader(Desktop &desktop)
477 : m_desktop(desktop)
478 , m_loading(false)
479 , m_loader(new DBLoaderData(desktop, m_send, m_send))
483 DBLoader::~DBLoader()
485 delete m_loader;
488 bool DBLoader::StartDBLoad(unsigned int dbId, DBData &data)
490 dout(_("Database ID: ") << dbId);
492 m_loading = true;
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);
504 return true;
507 // advance! (use the same data block as in packet)
508 m_desktop.m_socket->NextRecord(data.UseData());
511 m_loading = false;
512 return false;
515 bool DBLoader::GetNextRecord(DBData &data)
517 if( !m_loading )
518 return false;
520 DBPacket &packet = m_loader->m_packet;
521 packet.SetNewReceive(data.UseData());
523 do {
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);
529 return true;
531 } while( m_loader->m_packet.Command() != SB_COMMAND_DB_DONE );
533 m_loading = false;
534 return false;
537 } // namespace Barry::Mode
543 //////////////////////////////////////////////////////////////////////////////
544 // DeviceBuilder class
546 DeviceBuilder::DeviceBuilder(Mode::Desktop &desktop)
547 : m_started(false)
548 , m_desktop(desktop)
549 , m_loader(desktop)
551 Restart();
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)
559 try {
560 DBLabel id(m_desktop.GetDBID(dbname), dbname);
561 m_dbIds.push_back(id);
562 return true;
564 catch( Barry::Error & ) {
565 // GetDBID() throws on error...
566 return false;
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,
586 size_t &offset,
587 const IConverter *ic)
589 DBData temp;
590 if( !FetchRecord(temp, ic) )
591 return false;
593 // copy the metadata
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);
604 return true;
607 bool DeviceBuilder::FetchRecord(DBData &data, const IConverter *ic)
609 bool ret;
611 if( !m_dbIds.size() )
612 return false; // nothing to do
614 if( !m_started ) {
615 m_current = m_dbIds.begin();
616 ret = m_loader.StartDBLoad(m_current->id, data);
617 m_started = true;
619 else if( m_loader.IsBusy() ) {
620 ret = m_loader.GetNextRecord(data);
622 else {
623 // don't do anything if we're at the end of our rope
624 if( EndOfFile() )
625 return false;
627 // advance and check again... m_current always points
628 // to our current DB
629 ++m_current;
630 if( EndOfFile() )
631 return false;
633 ret = m_loader.StartDBLoad(m_current->id, data);
636 // fill in the DBname if successful
637 if( ret ) {
638 data.SetDBName(m_current->name);
640 return ret;
643 bool DeviceBuilder::EndOfFile() const
645 return m_current == m_dbIds.end();
650 //////////////////////////////////////////////////////////////////////////////
651 // DeviceParser class
653 DeviceParser::DeviceParser(Mode::Desktop &desktop, WriteMode mode)
654 : m_desktop(desktop)
655 , m_mode(mode)
659 DeviceParser::~DeviceParser()
663 void DeviceParser::StartDB(const DBData &data, const IConverter *ic)
665 // start fresh
666 m_rstate.Clear();
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();
672 m_current_dbid = 0;
673 return;
676 // determine mode
677 WriteMode mode = m_mode;
678 if( mode == DECIDE_BY_CALLBACK )
679 mode = DecideWrite(data);
681 switch( mode )
683 case ERASE_ALL_WRITE_ALL:
684 m_desktop.ClearDatabase(m_current_dbid);
685 WriteNext(data, ic);
686 break;
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);
692 WriteNext(data, ic);
693 break;
695 case DROP_RECORD:
696 break;
698 case DECIDE_BY_CALLBACK:
699 default:
700 throw std::logic_error(_("DeviceParser: unknown mode"));
704 void DeviceParser::WriteNext(const DBData &data, const IConverter *ic)
706 // determine mode
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;
719 switch( mode )
721 case ERASE_ALL_WRITE_ALL:
722 // just do an AddRecord()
723 m_desktop.AddRecord(m_current_dbid, dbuild);
724 break;
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);
732 else {
733 // new record
734 m_desktop.AddRecord(m_current_dbid, dbuild);
736 break;
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);
743 // else, drop record
744 break;
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);
750 break;
752 case DROP_RECORD:
753 break;
755 case DECIDE_BY_CALLBACK:
756 default:
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 ) {
764 WriteNext(data, ic);
766 else {
767 StartDB(data, ic);
771 } // namespace Barry