desktop: fixed recur end date validation error
[barry.git] / src / m_desktop.cc
blobcdeb3b1dbb9c0554994b94af9070469173f44fed
1 ///
2 /// \file m_desktop.cc
3 /// Mode class for the Desktop mode
4 ///
6 /*
7 Copyright (C) 2005-2012, 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 "parser.h"
32 #include <stdexcept>
33 #include <sstream>
35 #include "debug.h"
37 namespace Barry { namespace Mode {
40 ///////////////////////////////////////////////////////////////////////////////
41 // Desktop Mode class
43 Desktop::Desktop(Controller &con)
44 : Mode(con, Controller::Desktop)
45 , m_ic(0)
49 Desktop::Desktop(Controller &con, const IConverter &ic)
50 : Mode(con, Controller::Desktop)
51 , m_ic(&ic)
55 Desktop::~Desktop()
59 ///////////////////////////////////////////////////////////////////////////////
60 // protected members
62 void Desktop::LoadCommandTable()
64 char rawCommand[] = { 6, 0, 0x0a, 0, 0x40, 0, 0, 1, 0, 0 };
66 Data command(rawCommand, sizeof(rawCommand));
68 try {
69 m_socket->Packet(command, m_response);
71 MAKE_PACKET(rpack, m_response);
72 while( rpack->command != SB_COMMAND_DB_DONE ) {
73 m_socket->NextRecord(m_response);
75 rpack = (const Protocol::Packet *) m_response.GetData();
76 if( rpack->command == SB_COMMAND_DB_DATA && btohs(rpack->size) > 10 ) {
77 // second packet is generally large, and contains
78 // the command table
79 m_commandTable.Clear();
80 m_commandTable.Parse(m_response, 6);
84 ddout(m_commandTable);
87 catch( Usb::Error & ) {
88 eout("Desktop: error getting command table");
89 eeout(command, m_response);
90 throw;
94 void Desktop::LoadDBDB()
96 DBPacket packet(*this, m_command, m_response);
97 packet.GetDBDB();
99 m_socket->Packet(packet);
101 while( packet.Command() != SB_COMMAND_DB_DONE ) {
102 if( packet.Command() == SB_COMMAND_DB_DATA ) {
103 m_dbdb.Clear();
104 m_dbdb.Parse(m_response);
107 // advance!
108 m_socket->NextRecord(m_response);
112 void Desktop::OnOpen()
114 // get command table and database database
115 LoadCommandTable();
116 LoadDBDB();
119 ///////////////////////////////////////////////////////////////////////////////
120 // public API
123 // GetDBID
125 /// Get numeric database ID by name.
127 /// \param[in] name Name of database, which matches one of the
128 /// names listed in GetDBDB()
130 /// \exception Barry::Error
131 /// Thrown if name not found.
133 unsigned int Desktop::GetDBID(const std::string &name) const
135 unsigned int ID = 0;
136 // FIXME - this needs a better error handler...
137 if( !m_dbdb.GetDBNumber(name, ID) ) {
138 throw Error("Desktop: database name not found: " + name);
140 return ID;
144 // GetDBCommand
146 /// Get database command from command table. Must call Open()
147 /// before this.
149 unsigned int Desktop::GetDBCommand(CommandType ct)
151 unsigned int cmd = 0;
152 const char *cmdName = "Unknown";
154 switch( ct )
156 case DatabaseAccess:
157 cmdName = "Database Access";
158 cmd = m_commandTable.GetCommand(cmdName);
159 break;
160 default:
161 throw std::logic_error("Desktop: unknown command type");
164 if( cmd == 0 ) {
165 std::ostringstream oss;
166 oss << "Desktop: unable to get command code: " << cmdName;
167 throw Error(oss.str());
170 return cmd;
173 void Desktop::SetIConverter(const IConverter &ic)
175 m_ic = &ic;
179 // GetRecordStateTable
181 /// Retrieve the record state table from the handheld device, using the given
182 /// database ID. Results will be stored in result, which will be cleared
183 /// before adding.
185 void Desktop::GetRecordStateTable(unsigned int dbId, RecordStateTable &result)
187 dout("Database ID: " << dbId);
189 // start fresh
190 result.Clear();
192 DBPacket packet(*this, m_command, m_response);
193 packet.GetRecordStateTable(dbId);
195 m_socket->Packet(packet);
196 result.Parse(m_response);
198 // flush the command sequence
199 while( packet.Command() != SB_COMMAND_DB_DONE )
200 m_socket->NextRecord(m_response);
204 // AddRecord
206 /// Adds a record to the specified database. RecordId is
207 /// retrieved from build, and duplicate IDs are allowed by the device
208 /// (i.e. you can have two records with the same ID)
209 /// but *not* recommended!
211 void Desktop::AddRecord(unsigned int dbId, Builder &build)
213 dout("Database ID: " << dbId);
215 DBPacket packet(*this, m_command, m_response);
217 if( packet.AddRecord(dbId, build, m_ic) ) {
219 std::ostringstream oss;
221 m_socket->Packet(packet);
223 // successful packet transfer, so check the network return code
224 if( packet.Command() != SB_COMMAND_DB_DONE ) {
225 oss << "Desktop: device responded with unexpected packet command code: "
226 << "0x" << std::hex << packet.Command();
227 throw Error(oss.str());
230 if( packet.ReturnCode() != 0 ) {
231 oss << "Desktop: device responded with error code (command: "
232 << packet.Command() << ", code: "
233 << packet.ReturnCode() << ")";
234 throw ReturnCodeError(oss.str(), packet.Command(), packet.ReturnCode());
240 // GetRecord
242 /// Retrieves a specific record from the specified database.
243 /// The stateTableIndex comes from the GetRecordStateTable()
244 /// function. GetRecord() does not clear the dirty flag.
246 void Desktop::GetRecord(unsigned int dbId,
247 unsigned int stateTableIndex,
248 Parser &parser)
250 dout("Database ID: " << dbId);
252 std::string dbName;
253 m_dbdb.GetDBName(dbId, dbName);
255 DBPacket packet(*this, m_command, m_response);
256 packet.GetRecordByIndex(dbId, stateTableIndex);
258 m_socket->Packet(packet);
260 // perform copious packet checks
261 if( m_response.GetSize() < SB_PACKET_RESPONSE_HEADER_SIZE ) {
262 eeout(m_command, m_response);
264 std::ostringstream oss;
265 oss << "Desktop: invalid response packet size of "
266 << std::dec << m_response.GetSize();
267 eout(oss.str());
268 throw Error(oss.str());
270 if( packet.Command() != SB_COMMAND_DB_DATA ) {
271 eeout(m_command, m_response);
273 std::ostringstream oss;
274 oss << "Desktop: unexpected command of 0x"
275 << std::setbase(16) << packet.Command()
276 << " instead of expected 0x"
277 << std::setbase(16) << (unsigned int)SB_COMMAND_DB_DATA;
278 eout(oss.str());
279 throw Error(oss.str());
282 // grab that data
283 packet.Parse(parser, dbName, m_ic);
285 // flush the command sequence
286 while( packet.Command() != SB_COMMAND_DB_DONE )
287 m_socket->NextRecord(m_response);
291 // SetRecord
293 /// Overwrites a specific record in the device as identified by the
294 /// stateTableIndex.
296 void Desktop::SetRecord(unsigned int dbId, unsigned int stateTableIndex,
297 Builder &build)
299 dout("Database ID: " << dbId << " Index: " << stateTableIndex);
301 DBPacket packet(*this, m_command, m_response);
303 // write only if builder object has data
304 if( !packet.SetRecordByIndex(dbId, stateTableIndex, build, m_ic) ) {
305 throw std::logic_error("Desktop: no data available in SetRecord");
308 m_socket->Packet(packet);
310 std::ostringstream oss;
312 // successful packet transfer, so check the network return code
313 if( packet.Command() != SB_COMMAND_DB_DONE ) {
314 oss << "Desktop: device responded with unexpected packet command code: "
315 << "0x" << std::hex << packet.Command();
316 throw Error(oss.str());
319 if( packet.ReturnCode() != 0 ) {
320 oss << "Desktop: device responded with error code (command: "
321 << packet.Command() << ", code: "
322 << packet.ReturnCode() << ")";
323 throw ReturnCodeError(oss.str(), packet.Command(), packet.ReturnCode());
328 // ClearDirty
330 /// Clears the dirty flag on the specified record in the specified database.
332 void Desktop::ClearDirty(unsigned int dbId, unsigned int stateTableIndex)
334 dout("Database ID: " << dbId);
336 DBPacket packet(*this, m_command, m_response);
337 packet.SetRecordFlags(dbId, stateTableIndex, 0);
339 m_socket->Packet(packet);
341 // flush the command sequence
342 while( packet.Command() != SB_COMMAND_DB_DONE )
343 m_socket->NextRecord(m_response);
347 // DeleteRecord
349 /// Deletes the specified record in the specified database.
351 void Desktop::DeleteRecord(unsigned int dbId, unsigned int stateTableIndex)
353 dout("Database ID: " << dbId);
355 DBPacket packet(*this, m_command, m_response);
356 packet.DeleteRecordByIndex(dbId, stateTableIndex);
358 m_socket->Packet(packet);
360 // flush the command sequence
361 while( packet.Command() != SB_COMMAND_DB_DONE )
362 m_socket->NextRecord(m_response);
366 // LoadDatabase
368 /// Retrieve a database from the handheld device, using the given parser
369 /// to parse the resulting data, and optionally store it.
371 /// See the RecordParser<> template to create a parser object. The
372 /// RecordParser<> template allows custom storage based on the type of
373 /// database record retrieved. The database ID and the parser Record
374 /// type must match.
376 /// \param[in] dbId Database Database ID - use GetDBID()
377 /// \param[out] parser Parser object which parses the resulting
378 /// protocol data, and optionally stores it in
379 /// a custom fashion. See the RecordParser<>
380 /// template.
382 /// \exception Barry::Error
383 /// Thrown on protocol error.
385 /// \exception std::logic_error
386 /// Thrown if not in Desktop mode.
388 void Desktop::LoadDatabase(unsigned int dbId, Parser &parser)
390 DBData data;
391 DBLoader loader(*this);
392 bool loading = loader.StartDBLoad(dbId, data);
393 while( loading ) {
394 // manual parser call
395 parser.ParseRecord(data, m_ic);
397 // advance!
398 loading = loader.GetNextRecord(data);
402 void Desktop::ClearDatabase(unsigned int dbId)
404 dout("Database ID: " << dbId);
406 DBPacket packet(*this, m_command, m_response);
407 packet.ClearDatabase(dbId);
409 // wait up to a minute here for old, slower devices with lots of data
410 m_socket->Packet(packet, 60000);
411 if( packet.ReturnCode() != 0 ) {
412 std::ostringstream oss;
413 oss << "Desktop: could not clear database: (command: "
414 << "0x" << std::hex << packet.Command() << ", code: "
415 << "0x" << std::hex << packet.ReturnCode() << ")";
416 throw ReturnCodeError(oss.str(), packet.Command(), packet.ReturnCode());
419 // check response to clear command was successful
420 if( packet.Command() != SB_COMMAND_DB_DONE ) {
421 eeout(m_command, m_response);
422 throw Error("Desktop: error clearing database, bad response");
426 void Desktop::SaveDatabase(unsigned int dbId, Builder &builder)
428 dout("Database ID: " << dbId);
430 ClearDatabase(dbId);
432 DBPacket packet(*this, m_command, m_response);
434 // loop until builder object has no more data
435 bool first = true;
436 while( packet.AddRecord(dbId, builder, m_ic) ) {
437 dout("Database ID: " << dbId);
439 m_socket->Packet(packet, first ? 60000 : -1);
440 first = false;
442 std::ostringstream oss;
443 // successful packet transfer, so check the network return code
444 if( packet.Command() != SB_COMMAND_DB_DONE ) {
445 oss << "Desktop: device responded with unexpected packet command code: "
446 << "0x" << std::hex << packet.Command();
447 throw Error(oss.str());
450 if( packet.ReturnCode() != 0 ) {
451 oss << "Desktop: device responded with error code (command: "
452 << packet.Command() << ", code: "
453 << packet.ReturnCode() << ")";
454 throw ReturnCodeError(oss.str(), packet.Command(), packet.ReturnCode());
461 //////////////////////////////////////////////////////////////////////////////
462 // DBLoader class
464 struct DBLoaderData
466 DBPacket m_packet;
467 DBLoaderData(Desktop &desktop, Data &command, Data &response)
468 : m_packet(desktop, command, response)
473 DBLoader::DBLoader(Desktop &desktop)
474 : m_desktop(desktop)
475 , m_loading(false)
476 , m_loader(new DBLoaderData(desktop, m_send, m_send))
480 DBLoader::~DBLoader()
482 delete m_loader;
485 bool DBLoader::StartDBLoad(unsigned int dbId, DBData &data)
487 dout("Database ID: " << dbId);
489 m_loading = true;
490 m_desktop.m_dbdb.GetDBName(dbId, m_dbName);
492 DBPacket &packet = m_loader->m_packet;
493 packet.SetNewReceive(data.UseData());
494 packet.GetRecords(dbId);
495 m_desktop.m_socket->Packet(packet);
497 while( packet.Command() != SB_COMMAND_DB_DONE ) {
498 if( packet.Command() == SB_COMMAND_DB_DATA ) {
499 packet.ParseMeta(data);
500 data.SetDBName(m_dbName);
501 return true;
504 // advance! (use the same data block as in packet)
505 m_desktop.m_socket->NextRecord(data.UseData());
508 m_loading = false;
509 return false;
512 bool DBLoader::GetNextRecord(DBData &data)
514 if( !m_loading )
515 return false;
517 DBPacket &packet = m_loader->m_packet;
518 packet.SetNewReceive(data.UseData());
520 do {
521 // advance! (use same data as in packet)
522 m_desktop.m_socket->NextRecord(data.UseData());
524 if( packet.Command() == SB_COMMAND_DB_DATA ) {
525 packet.ParseMeta(data);
526 return true;
528 } while( m_loader->m_packet.Command() != SB_COMMAND_DB_DONE );
530 m_loading = false;
531 return false;
534 } // namespace Barry::Mode
540 //////////////////////////////////////////////////////////////////////////////
541 // DeviceBuilder class
543 DeviceBuilder::DeviceBuilder(Mode::Desktop &desktop)
544 : m_started(false)
545 , m_desktop(desktop)
546 , m_loader(desktop)
548 Restart();
551 // searches the dbdb from the desktop to find the dbId,
552 // returns false if not found, and adds it to the list of
553 // databases to retrieve if found
554 bool DeviceBuilder::Add(const std::string &dbname)
556 try {
557 DBLabel id(m_desktop.GetDBID(dbname), dbname);
558 m_dbIds.push_back(id);
559 return true;
561 catch( Barry::Error & ) {
562 // GetDBID() throws on error...
563 return false;
567 void DeviceBuilder::Add(const Barry::DatabaseDatabase &dbdb)
569 DatabaseDatabase::DatabaseArrayType::const_iterator
570 b = dbdb.Databases.begin(),
571 e = dbdb.Databases.end();
573 for( ; b != e; ++b ) {
574 // hmmm, could optimize this and only add ids
575 // with RecordCount > 0, but let's stick with this
576 // for now... it might flush bugs out of the system
577 DBLabel id(b->Number, b->Name);
578 m_dbIds.push_back(id);
582 bool DeviceBuilder::BuildRecord(DBData &data,
583 size_t &offset,
584 const IConverter *ic)
586 DBData temp;
587 if( !FetchRecord(temp, ic) )
588 return false;
590 // copy the metadata
591 data.SetVersion(temp.GetVersion());
592 data.SetDBName(temp.GetDBName());
593 data.SetIds(temp.GetRecType(), temp.GetUniqueId());
594 data.SetOffset(offset);
596 // copy data from temp into the given offset
597 size_t tempsize = temp.GetData().GetSize() - temp.GetOffset();
598 data.UseData().MemCpy(offset,
599 temp.GetData().GetData() + temp.GetOffset(), tempsize);
600 data.UseData().ReleaseBuffer(offset + tempsize);
601 return true;
604 bool DeviceBuilder::FetchRecord(DBData &data, const IConverter *ic)
606 bool ret;
608 if( !m_dbIds.size() )
609 return false; // nothing to do
611 if( !m_started ) {
612 m_current = m_dbIds.begin();
613 ret = m_loader.StartDBLoad(m_current->id, data);
614 m_started = true;
616 else if( m_loader.IsBusy() ) {
617 ret = m_loader.GetNextRecord(data);
619 else {
620 // don't do anything if we're at the end of our rope
621 if( EndOfFile() )
622 return false;
624 // advance and check again... m_current always points
625 // to our current DB
626 ++m_current;
627 if( EndOfFile() )
628 return false;
630 ret = m_loader.StartDBLoad(m_current->id, data);
633 // fill in the DBname if successful
634 if( ret ) {
635 data.SetDBName(m_current->name);
637 return ret;
640 bool DeviceBuilder::EndOfFile() const
642 return m_current == m_dbIds.end();
647 //////////////////////////////////////////////////////////////////////////////
648 // DeviceParser class
650 DeviceParser::DeviceParser(Mode::Desktop &desktop, WriteMode mode)
651 : m_desktop(desktop)
652 , m_mode(mode)
656 DeviceParser::~DeviceParser()
660 void DeviceParser::StartDB(const DBData &data, const IConverter *ic)
662 // start fresh
663 m_rstate.Clear();
664 m_current_db = data.GetDBName();
665 if( !m_desktop.GetDBDB().GetDBNumber(m_current_db, m_current_dbid) ) {
666 // doh! This database does not exist in this device
667 dout("Database '" << m_current_db << "' does not exist in this device. Dropping record.");
668 m_current_db.clear();
669 m_current_dbid = 0;
670 return;
673 // determine mode
674 WriteMode mode = m_mode;
675 if( mode == DECIDE_BY_CALLBACK )
676 mode = DecideWrite(data);
678 switch( mode )
680 case ERASE_ALL_WRITE_ALL:
681 m_desktop.ClearDatabase(m_current_dbid);
682 WriteNext(data, ic);
683 break;
685 case INDIVIDUAL_OVERWRITE:
686 case ADD_BUT_NO_OVERWRITE:
687 case ADD_WITH_NEW_ID:
688 m_desktop.GetRecordStateTable(m_current_dbid, m_rstate);
689 WriteNext(data, ic);
690 break;
692 case DROP_RECORD:
693 break;
695 case DECIDE_BY_CALLBACK:
696 default:
697 throw std::logic_error("DeviceParser: unknown mode");
701 void DeviceParser::WriteNext(const DBData &data, const IConverter *ic)
703 // determine mode
704 WriteMode mode = m_mode;
705 if( mode == DECIDE_BY_CALLBACK )
706 mode = DecideWrite(data);
708 // create fast copy with our own metadata
709 DBData local(data.GetVersion(), data.GetDBName(),
710 data.GetRecType(), data.GetUniqueId(), data.GetOffset(),
711 data.GetData().GetData(), data.GetData().GetSize());
712 DBDataBuilder dbuild(local);
714 RecordStateTable::IndexType index;
716 switch( mode )
718 case ERASE_ALL_WRITE_ALL:
719 // just do an AddRecord()
720 m_desktop.AddRecord(m_current_dbid, dbuild);
721 break;
723 case INDIVIDUAL_OVERWRITE:
724 // search the state table, overwrite existing, and add new
725 if( m_rstate.GetIndex(local.GetUniqueId(), &index) ) {
726 // found this record ID, use the index
727 m_desktop.SetRecord(m_current_dbid, index, dbuild);
729 else {
730 // new record
731 m_desktop.AddRecord(m_current_dbid, dbuild);
733 break;
735 case ADD_BUT_NO_OVERWRITE:
736 if( !m_rstate.GetIndex(local.GetUniqueId()) ) {
737 // no such record ID, so safe to add as new
738 m_desktop.AddRecord(m_current_dbid, dbuild);
740 // else, drop record
741 break;
743 case ADD_WITH_NEW_ID:
744 // use state table to create new id, and add as new
745 local.SetIds(local.GetRecType(), m_rstate.MakeNewRecordId());
746 m_desktop.AddRecord(m_current_dbid, dbuild);
747 break;
749 case DROP_RECORD:
750 break;
752 case DECIDE_BY_CALLBACK:
753 default:
754 throw std::logic_error("DeviceParser: unknown mode");
758 void DeviceParser::ParseRecord(const DBData &data, const IConverter *ic)
760 if( data.GetDBName() == m_current_db ) {
761 WriteNext(data, ic);
763 else {
764 StartDB(data, ic);
768 } // namespace Barry