She’s got me dancing
One of the funniest videos I have seen so far
Tommy Sparks “She’s Got Me Dancing” from Eric Wareheim on Vimeo.
One of the funniest videos I have seen so far
Tommy Sparks “She’s Got Me Dancing” from Eric Wareheim on Vimeo.
When defining structure in Qt that you want to send via DBus you need to define the operator<< and operator>> to a QDBusArgument, but how do you test it? The problem is that the QDBusArument constructor creates a no writable argument so that you cannot do:
1 2 3 4 5 6 7 | MyStructure structure = MyStructure("first arg", "second arg"); MyStrcuture result; QDBusArgument arg; arg << structure; arg >> result; QCOMPARE(structure, result); |
If you add the above test you will get result as an empty structure because the arg is not writable. A good way to test the serialization is to start a DBus interface and test the serialization and deserialization using the real dbus, for example:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 | /** * * Copyright (c) 2012 mandel * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to * deal in the Software without restriction, including without limitation the * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or * sell copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. */ #ifndef TEST_SECRET_H #define TEST_SECRET_H #include <QObject> #include <QDBusInterface> #include "test_runner.h" #include "keyring/secret.h" namespace tori { namespace keyring { class TypesInterface: public QDBusAbstractAdaptor { Q_OBJECT Q_CLASSINFO("D-Bus Interface", "org.saruneko.tori.keyring.TypesInterface") public: TypesInterface(QObject *parent) : QDBusAbstractAdaptor(parent) { } // make it secret so that we can acess it Secret secret; public slots: Secret retrieveSecret() { return secret; } }; class TestSecret : public QObject { Q_OBJECT public: explicit TestSecret(QObject *parent = 0); private slots: void testSerialize(); private: QDBusInterface *iface; TypesInterface *adaptor; }; DECLARE_TEST(TestSecret) } // keyring } // tori #endif // TEST_SECRET_H |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 | /** * * Copyright (c) 2012 mandel * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to * deal in the Software without restriction, including without limitation the * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or * sell copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. */ #include "test_secret.h" namespace tori { namespace keyring { TestSecret::TestSecret(QObject *parent) : QObject(parent) { adaptor = new TypesInterface(this); QDBusConnection::sessionBus().registerObject("/", this); iface = new QDBusInterface(QDBusConnection::sessionBus().baseService(), "/", "org.saruneko.tori.keyring.TypesInterface", QDBusConnection::sessionBus(), this); } void TestSecret::testSerialize() { QDBusObjectPath session("/path/to/session"); QByteArray params = "the parameters"; QByteArray value = "the value"; QString contentType = "password"; // test serializing and deserializing Secret secret(session, params, value, contentType); adaptor->secret = secret; QDBusReply<Secret> result = iface->call(QDBus::BlockWithGui, "retrieveSecret"); QVERIFY(result.isValid()); QCOMPARE(result.value().getSession(), secret.getSession()); QCOMPARE(result.value().getParameters(), secret.getParameters()); QCOMPARE(result.value().getValue(), secret.getValue()); QCOMPARE(result.value().getContentType(), secret.getContentType()); } } //keyring } // tori |
Where secret is defined as:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 | /** * * Copyright (c) 2012 Manuel de la Pena <mandel@themacaque.com> * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to * deal in the Software without restriction, including without limitation the * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or * sell copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. */ #ifndef SECRET_H #define SECRET_H #include <QtDBus> #include <QByteArray> #include <QHash> #include <QString> #include "dbus/dbus_helper.h" class Secret { Q_PROPERTY(QString session READ getSession) Q_PROPERTY(QByteArray parameters READ getParameters) Q_PROPERTY(QByteArray value READ getValue) Q_PROPERTY(QString contentType READ getContentType) public: Secret(); Secret(QDBusObjectPath session, QByteArray parameters, QByteArray value, QString contentType); Secret(const Secret& other); Secret& operator=(const Secret& other); ~Secret(); friend QDBusArgument &operator<<(QDBusArgument &argument, const Secret& secret); friend const QDBusArgument &operator>>(const QDBusArgument &argument, Secret &secret); //register Secret with the Qt type system static void registerMetaType(); // property getters QString getSession() const; QByteArray getParameters() const; QByteArray getValue() const; QString getContentType() const; private: QDBusObjectPath _session; QByteArray _parameters; QByteArray _value; QString _contentType; }; typedef QHash<QDBusObjectPath, Secret> DBusSecretHash; Q_DECLARE_METATYPE(DBusSecretHash) Q_DECLARE_METATYPE(Secret) #endif // SECRET_H |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 | /** * * Copyright (c) 2012 Manuel de la Pena <mandel@themacaque.com> * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to * deal in the Software without restriction, including without limitation the * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or * sell copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. */ #include "secret.h" Secret::Secret() : _session(), _parameters(), _value(), _contentType() { } Secret::Secret(QDBusObjectPath session, QByteArray parameters, QByteArray value, QString contentType) : _session(session), _parameters(parameters), _value(value), _contentType(contentType) { } Secret::Secret(const Secret& other) : _session(other._session), _parameters(other._parameters), _value(other._value), _contentType(other._contentType) { } Secret& Secret::operator=(const Secret& other) { _session = other._session; _parameters = other._parameters; _value = other._value; _contentType = other._contentType; return *this; } Secret::~Secret() { } void Secret::registerMetaType() { qRegisterMetaType<Secret>("Secret"); qDBusRegisterMetaType<Secret>(); } QDBusArgument &operator<<(QDBusArgument &argument, const Secret& secret) { argument.beginStructure(); argument << secret._session; argument << secret._parameters; argument << secret._value; argument << secret._contentType; argument.endStructure(); return argument; } const QDBusArgument &operator>>(const QDBusArgument &argument, Secret &secret) { argument.beginStructure(); argument >> secret._session; argument >> secret._parameters; argument >> secret._value; argument >> secret._contentType; argument.endStructure(); return argument; } QString Secret::getSession() const { return _session.path(); } QByteArray Secret::getParameters() const { return _parameters; } QByteArray Secret::getValue() const { return _value; } QString Secret::getContentType() const { return _contentType; } |
This will ensure that the serialization and deserialization are correctly implemented without to much effort. Hope it helps!
I have recently been doing some work with Qt and DBus and I got stuck a little on how to correctly send a {sv} over DBus. Either my google-fu is terrible or there are not many examples on how to do this, therefore here is a small static method that will return a {sv} that can be send via DBus without getting a wrong parameters error:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 | #ifndef DBUS_HELPER_H #define DBUS_HELPER_H #include #include #include #include typedef QHash DBusStringHash; class DBusHelper : public QObject { Q_OBJECT public: static int DBUS_STRING_MAP_ID; explicit DBusHelper(QObject *parent = 0); static class _init { public: _init() { // diff actions to init qRegisterMetaType("DBusStringHash"); qDBusRegisterMetaType(); DBUS_STRING_MAP_ID = QMetaType::type("DBusStringHash"); } } _initializer; static QVariant getVariant(DBusStringHash hash); }; Q_DECLARE_METATYPE(DBusStringHash) #endif // DBUS_HELPER_H |
1 2 3 4 5 6 7 8 9 10 11 12 13 | // required for the init int DBusHelper::DBUS_STRING_MAP_ID = 0; DBusHelper::_init DBusHelper::_initializer; DBusHelper::DBusHelper(QObject *parent) : QObject(parent) { } QVariant DBusHelper::getVariant(DBusStringHash hash) { return QVariant(DBUS_STRING_MAP_ID, &hash); } |
I added the init trick so that there is no need to manually register the types. I hope it helps!
When I started this blog I did not have the time to research for which host company I should used, I simply picked godaddy because I had a coupon from the long dead diggnation… One of the propositions for this year was to move away from godaddy for several reasons:
At the moment the blog is running on a EC2 instance hosted in the EU zone witch is more than I need. Please let me know if you see any possible errors in blogs posts (all of the should be there as the used to be) or with the RSS feeds, next step, move away from wordpress to a python/django solution.
Following the small work I’m doing to get better at golang here is another piece of work I have done to add a golang binding to one of the C libraries I’m depending on. The following allows to use the libaccounts-glib library from go:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 | package main /* #cgo pkg-config: libaccounts-glib #include <stdlib.h> #include <glib.h> #include <libaccounts-glib/accounts-glib.h> static inline char* to_charptr(const gchar* s) { return (char*)s; } static inline char* to_charptr_from_ptr(gpointer s) { return (char*)s; } static inline gchar* to_gcharptr(const char* s) { return (gchar*)s; } static inline AgService* to_AgService(void* o) { return (AgService *)o; } static inline AgAccountService* to_AgAccountService(void* o) { return (AgAccountService *)o; } static inline AgApplication* to_AgApplication(void* o) { return (AgApplication *)o; } static inline AgProvider* to_AgProvider(void* o) { return (AgProvider *)o; } static inline AgServiceType* to_AgServiceType(void* o) { return (AgServiceType *)o; } static inline void free_string(char* s) { free(s); } static inline AgAccountId to_AgAccountId(guint i) { return (AgAccountId) i; } static inline guint to_guint(gpointer i) { return (guint)i; } static GError* to_error(void* err) { return (GError*)err; } extern void go_account_notify_cb(void* func, AgAccount* acc, gchar* key); static void account_notify_cb(AgAccount* acc, gchar* key, gpointer func){ go_account_notify_cb((void*)func, acc, key); } static AgAccountWatch watch_dir(AgAccount* acc, gchar* key, void* func) { return ag_account_watch_dir(acc, key, (AgAccountNotifyCb)account_notify_cb, func); } static AgAccountWatch watch_key(AgAccount* acc, gchar* key, void* func) { return ag_account_watch_key(acc, key, (AgAccountNotifyCb)account_notify_cb, func); } */ import "C" import ( "os" "fmt" "unsafe" "github.com/mattn/go-gtk/gtk" ) func GetStringFromGCharPtr(data *C.gchar) string { if data == nil { return "" } return C.GoString(C.to_charptr(data)) } func GetServicesFromList(services *C.GList, length C.guint) ([]*Service) { result := make([]*Service, length) for n := C.guint(0); n < length; n++ { data := C.g_list_nth_data(services, n) pointer := unsafe.Pointer(data) service := &Service{serv:C.to_AgService(pointer)} result[uint(n)] = service } return result } func GetAccountServicesFromList(services *C.GList, length C.guint) ([]*AccountService) { result := make([]*AccountService, length) for n := C.guint(0); n < length; n++ { data := C.g_list_nth_data(services, n) pointer := unsafe.Pointer(data) service := &AccountService{acc:C.to_AgAccountService(pointer)} result[uint(n)] = service } return result } func GetIdsFromList(ids *C.GList, length C.guint) ([]uint32) { result := make([]uint32, length) for n := C.guint(0); n < length; n++ { data := C.g_list_nth_data(ids, n) result[uint(n)] = uint32(C.to_guint(data)) } return result } // =============================== GError ===================================== type Error struct { GError *C.GError } func (v *Error) Error() string { return v.Message() } func (v *Error) Message() string { if unsafe.Pointer(v.GError) == nil || unsafe.Pointer(v.GError.message) == nil { return "" } return C.GoString(C.to_charptr(v.GError.message)) } func ErrorFromNative(err unsafe.Pointer) *Error { return &Error{C.to_error(err)} } // ============================== Application ==================================== type Application struct { app *C.AgApplication } func (app *Application) GetDescription () string { return GetStringFromGCharPtr(C.ag_application_get_description(app.app)) } func (app *Application) GetName () string { return GetStringFromGCharPtr(C.ag_application_get_name(app.app)) } func (app *Application) GetServiceUsage (service *Service) string { return GetStringFromGCharPtr( C.ag_application_get_service_usage(app.app, service.serv)) } func (app *Application) Unref () { C.ag_application_unref(C.to_AgApplication(unsafe.Pointer(app.app))) } // =============================== Provider ====================================== type Provider struct { prov *C.AgProvider } func (prov *Provider) GetDisplayName () string { return GetStringFromGCharPtr(C.ag_provider_get_display_name(prov.prov)) } func (prov *Provider) GetDescription () string { return GetStringFromGCharPtr(C.ag_provider_get_description(prov.prov)) } func (prov *Provider) GetName () string { return GetStringFromGCharPtr(C.ag_provider_get_name(prov.prov)) } func (prov *Provider) Unref () { C.ag_provider_unref(C.to_AgProvider(unsafe.Pointer(prov.prov))) } // ================================ Manager ====================================== type Manager struct { man *C.AgManager } func (man *Manager) CreateAccount (provider_name string) *Account { provider_str := C.CString(provider_name) defer C.free_string(provider_str) acc := C.ag_manager_create_account(man.man, C.to_gcharptr(provider_str)) return &Account{acc:acc} } func (man *Manager) GetAccount (id uint32) *Account { acc := C.ag_manager_get_account(man.man, C.to_AgAccountId(C.guint(id))) return &Account{acc:acc} } func (man *Manager) GetAccountServices () ([]*AccountService, uint) { services := C.ag_manager_get_account_services(man.man) defer C.g_list_free(services) length := C.g_list_length(services) result := GetAccountServicesFromList(services, length) return result, uint(length) } func (man *Manager) GetApplication (app_name string) *Application { app_name_str := C.CString(app_name) defer C.free_string(app_name_str) app:= C.ag_manager_get_application(man.man, C.to_gcharptr(app_name_str)) return &Application{app:app} } func (man *Manager) GetEnabledAccountServices() ([]*AccountService, uint) { services := C.ag_manager_get_enabled_account_services(man.man) defer C.g_list_free(services) length := C.g_list_length(services) result := GetAccountServicesFromList(services, length) return result, uint(length) } func (man *Manager) GetProvider(provider_name string) *Provider { provider_name_str := C.CString(provider_name) defer C.free_string(provider_name_str) provider := C.ag_manager_get_provider(man.man, C.to_gcharptr(provider_name_str)) return &Provider{prov:provider} } func (man *Manager) GetService (service_name string) *Service { service_name_str := C.CString(service_name) defer C.free_string(service_name_str) service := C.ag_manager_get_service(man.man, C.to_gcharptr(service_name_str)) return &Service{serv:service} } func (man *Manager) GetServiceType () string { return GetStringFromGCharPtr(C.ag_manager_get_service_type(man.man)) } func (man *Manager) List() ([]uint32, uint) { account_ids := C.ag_manager_list(man.man) defer C.g_list_free(account_ids) length := C.g_list_length(account_ids) result := GetIdsFromList(account_ids, length) return result, uint(length) } func (man *Manager) ListApplicationsByService ( service *Service) ([]*Application, uint) { apps := C.ag_manager_list_applications_by_service( man.man, service.serv) defer C.g_list_free(apps) length := C.g_list_length(apps) result := make([]*Application, length) for n := C.guint(0); n < length; n++ { data := C.g_list_nth_data(apps, n) pointer := unsafe.Pointer(data) app := &Application{app:C.to_AgApplication(pointer)} result[uint(n)] = app } return result, uint(length) } func (man *Manager) ListByServiceType( service_type string) ([]uint32, uint) { service_type_str := C.CString(service_type) defer C.free_string(service_type_str) service_ids := C.ag_manager_list_by_service_type( man.man, C.to_gcharptr(service_type_str)) defer C.g_list_free(service_ids) length := C.g_list_length(service_ids) result := GetIdsFromList(service_ids, length) return result, uint(length) } func (man *Manager) ListEnabled () ([]uint32, uint) { account_ids := C.ag_manager_list_enabled(man.man) defer C.g_list_free(account_ids) length := C.g_list_length(account_ids) result := GetIdsFromList(account_ids, length) return result, uint(length) } func (man *Manager) ListEnabledByServiceType ( service_type string) ([]uint32, uint) { service_type_str := C.CString(service_type) defer C.free_string(service_type_str) account_ids := C.ag_manager_list_enabled_by_service_type(man.man, C.to_gcharptr(service_type_str)) length := C.g_list_length(account_ids) result := GetIdsFromList(account_ids, length) return result, uint(length) } func (man *Manager) ListProviders () ([]*Provider, uint) { providers := C.ag_manager_list_providers(man.man) defer C.g_list_free(providers) length := C.g_list_length(providers) result := make([]*Provider, length) for n := C.guint(0); n < length; n++ { data := C.g_list_nth_data(providers, n) pointer := unsafe.Pointer(data) app := &Provider{prov:C.to_AgProvider(pointer)} result[uint(n)] = app } return result, uint(length) } func (man *Manager) ListServiceTypes () ([]*ServiceType, uint) { services := C.ag_manager_list_service_types(man.man) defer C.g_list_free(services) length := C.g_list_length(services) result := make([]*ServiceType, length) for n := C.guint(0); n < length; n++ { data := C.g_list_nth_data(services, n) pointer := unsafe.Pointer(data) app := &ServiceType{serv_type:C.to_AgServiceType(pointer)} result[uint(n)] = app } return result, uint(length) } func (man *Manager) ListServices () ([]*Service, uint) { services := C.ag_manager_list_services(man.man) defer C.g_list_free(services) length := C.g_list_length(services) result := make([]*Service, length) for n := C.guint(0); n < length; n++ { data := C.g_list_nth_data(services, n) pointer := unsafe.Pointer(data) app := &Service{serv:C.to_AgService(pointer)} result[uint(n)] = app } return result, uint(length) } func (man *Manager) ListServicesByType ( service_type string) ([]*Service, uint) { service_type_str := C.CString(service_type) defer C.free_string(service_type_str) services := C.ag_manager_list_services_by_type( man.man, C.to_gcharptr(service_type_str)) defer C.g_list_free(services) length := C.g_list_length(services) result := make([]*Service, length) for n := C.guint(0); n < length; n++ { data := C.g_list_nth_data(services, n) pointer := unsafe.Pointer(data) app := &Service{serv:C.to_AgService(pointer)} result[uint(n)] = app } return result, uint(length) } func (man *Manager) LoadAccount (account_id uint32) (*Account, error) { var gerror *C.GError id := C.to_AgAccountId(C.guint(account_id)) account := C.ag_manager_load_account(man.man, id, &gerror) if gerror != nil { err := ErrorFromNative(unsafe.Pointer(gerror)) return nil, err } return &Account{acc:account}, nil } func (man *Manager) LoadServiceType ( service_type string) *ServiceType { service_type_str := C.CString(service_type) defer C.free_string(service_type_str) stype := C.ag_manager_load_service_type(man.man, C.to_gcharptr(service_type_str)) return &ServiceType{serv_type:stype} } func (man *Manager) Unref () { C.g_object_unref(C.gpointer(man.man)) } func NewManager () *Manager { return &Manager{man:C.ag_manager_new()} } func NewManagerForServiceType (service_type string) *Manager { service_type_str := C.CString(service_type) defer C.free_string(service_type_str) return &Manager{man:C.ag_manager_new_for_service_type( C.to_gcharptr(service_type_str))} } // ============================= Account Service ================================= type AccountService struct { acc *C.AgAccountService } func (acc *AccountService) GetAccount () *Account { return &Account{C.ag_account_service_get_account(acc.acc)} } func (acc *AccountService) GetService () *Service { return &Service{C.ag_account_service_get_service(acc.acc)} } func (acc *AccountService) GetEnabled () bool { return C.TRUE == C.ag_account_service_get_enabled(acc.acc) } // ================================ Service ====================================== type Service struct { serv *C.AgService } func (serv *Service) GetDisplayName () string { return GetStringFromGCharPtr(C.ag_service_get_display_name(serv.serv)) } func (serv *Service) GetName () string { return GetStringFromGCharPtr(C.ag_service_get_name(serv.serv)) } func (serv *Service) GetDescription () string { return GetStringFromGCharPtr(C.ag_service_get_description(serv.serv)) } func (serv *Service) GetProvider () string { return GetStringFromGCharPtr(C.ag_service_get_provider(serv.serv)) } func (serv *Service) GetServiceType () string { return GetStringFromGCharPtr(C.ag_service_get_service_type(serv.serv)) } func (serv *Service) GetTags () ([]string, uint) { tags := C.ag_service_get_tags(serv.serv) defer C.g_list_free(tags) length := C.g_list_length(tags) result := make([]string, length) for n := C.guint(0); n < length; n++ { data := C.g_list_nth_data(tags, n) pointer := C.to_charptr_from_ptr(data) result[uint(n)] = C.GoString(pointer) } return result, uint(length) } func (serv *Service) HasTag (tag string) bool { tag_str := C.CString(tag) defer C.free_string(tag_str) has_tag := C.ag_service_has_tag(serv.serv, C.to_gcharptr(tag_str)) return C.TRUE == has_tag } func (serv *Service) Unref () { C.ag_service_unref(C.to_AgService(unsafe.Pointer(serv.serv))) } // ============================== ServiceType ==================================== type ServiceType struct { serv_type *C.AgServiceType } func (serv *ServiceType) GetDisplayName () string { return GetStringFromGCharPtr( C.ag_service_type_get_display_name(serv.serv_type)) } func (serv *ServiceType) GetDescription () string { return GetStringFromGCharPtr( C.ag_service_type_get_description(serv.serv_type)) } func (serv *ServiceType) GetName () string { return GetStringFromGCharPtr(C.ag_service_type_get_name(serv.serv_type)) } func (serv *ServiceType) GetTags () ([]string, uint) { tags := C.ag_service_get_tags(serv.serv_type) defer C.g_list_free(tags) length := C.g_list_length(tags) result := make([]string, length) for n := C.guint(0); n < length; n++ { data := C.g_list_nth_data(tags, n) pointer := C.to_charptr_from_ptr(data) result[uint(n)] = C.GoString(pointer) } return result, uint(length) } func (serv *ServiceType) HasTag (tag string) bool { tag_str := C.CString(tag) defer C.free_string(tag_str) has_tag := C.ag_service_has_tag(serv.serv_type, C.to_gcharptr(tag_str)) return C.TRUE == has_tag } func (serv *ServiceType) Unref () { C.ag_service_type_unref(C.to_AgServiceType( unsafe.Pointer(serv.serv_type))) } // ============================== Account Watch ================================== type AccountWatch struct { watch *C.AgAccountWatch } func (watch *AccountWatch ) Unref () { C.g_object_unref(C.gpointer(watch.watch)) } // ================================ Account ====================================== type AccountAsyncStoreCallback func(acc *Account, success bool, err error) type AccountAsyncWatchDirCallback func(acc *Account, key string) type Account struct { acc *C.AgAccount } //export go_account_notify_cb func go_account_notify_cb(pcallback unsafe.Pointer, gaccount *C.AgAccount, gkey *C.gchar) { account := &Account{acc:gaccount} key := C.GoString(C.to_charptr(gkey)) callback := *(*func(*Account, string))(pcallback) callback(account, key) } func (acc *Account) Delete () { C.ag_account_delete(acc.acc) } func (acc *Account) GetDisplayName () string { return GetStringFromGCharPtr(C.ag_account_get_display_name(acc.acc)) } func (acc *Account) GetEnabled () bool { return C.TRUE == C.ag_account_get_enabled(acc.acc) } func (acc *Account) GetManager () *Manager { return &Manager{man:C.ag_account_get_manager(acc.acc)} } func (acc *Account) GetProviderName () string { return GetStringFromGCharPtr(C.ag_account_get_provider_name(acc.acc)) } func (acc *Account) GetSelectedService () *Service { return &Service{serv:C.ag_account_get_selected_service(acc.acc)} } func (acc *Account) ListEnabledServices () ([]*Service, uint) { services := C.ag_account_list_enabled_services(acc.acc) defer C.g_list_free(services) length := C.g_list_length(services) result := GetServicesFromList(services, length) return result, uint(length) } func (acc *Account) ListServices () ([]*Service, uint) { services := C.ag_account_list_services(acc.acc) defer C.g_list_free(services) length := C.g_list_length(services) result := GetServicesFromList(services, length) return result, uint(length) } func (acc *Account) ListServicesByType (service_type string) ([]*Service, uint) { service_type_str := C.CString(service_type) defer C.free_string(service_type_str) services := C.ag_account_list_services_by_type(acc.acc, C.to_gcharptr(service_type_str)) defer C.g_list_free(services) length := C.g_list_length(services) result := GetServicesFromList(services, length) return result, uint(length) } func (acc *Account) RemoveWatch (watch *AccountWatch) { C.ag_account_remove_watch(acc.acc, *watch.watch) } func (acc *Account) SelectService (service *Service) { C.ag_account_select_service(acc.acc, service.serv) } func (acc *Account) SetDisplayName (name string) { name_str := C.CString(name) defer C.free_string(name_str) C.ag_account_set_display_name(acc.acc, C.to_gcharptr(name_str)) } func (acc *Account) SetEnabled (enabled bool) { if enabled { C.ag_account_set_enabled(acc.acc, C.TRUE) } else { C.ag_account_set_enabled(acc.acc, C.FALSE) } } func (acc *Account) Sign (key string, token string) { key_str := C.CString(key) defer C.free_string(key_str) token_str := C.CString(token) defer C.free_string(token_str) C.ag_account_sign(acc.acc, C.to_gcharptr(key_str), C.to_gcharptr(token_str)) } func (acc *Account) Store (callback AccountAsyncStoreCallback) { go func(){ var gerror *C.GError C.ag_account_store_blocking(acc.acc, &gerror) if gerror != nil { err := ErrorFromNative(unsafe.Pointer(gerror)) callback(acc, false, err) return } callback(acc, true, nil) }() } func (acc *Account) StoreSync () (bool, error){ var gerror *C.GError C.ag_account_store_blocking(acc.acc, &gerror) if gerror != nil { err := ErrorFromNative(unsafe.Pointer(gerror)) return false, err } return true, nil } func (acc *Account) SupportsService (service_type string) bool { service_type_str := C.CString(service_type) defer C.free_string(service_type_str) suported := C.ag_account_supports_service(acc.acc, C.to_gcharptr(service_type_str)) return suported == C.TRUE } func (acc *Account) Verify (key string) (bool, string) { key_str := C.CString(key) defer C.free_string(key_str) var token *C.gchar verified := C.ag_account_verify(acc.acc, C.to_gcharptr(key_str), &token) return verified == C.TRUE, C.GoString(C.to_charptr(token)) } func (acc *Account) VerifyWithTokens (key string, tokens []string) bool { key_str := C.CString(key) defer C.free_string(key_str) tokens_str := make([]*C.gchar, len(tokens)) for n := 0; n < len(tokens); n++ { token := C.CString(tokens[n]) defer C.free_string(token) tokens_str[n] = C.to_gcharptr(token) } verified := C.ag_account_verify_with_tokens(acc.acc, C.to_gcharptr(key_str), &tokens_str[0]) return verified == C.TRUE } func (acc *Account) WatchDir (key string, callback AccountAsyncWatchDirCallback) *AccountWatch { key_str := C.CString(key) defer C.free_string(key_str) watch := C.watch_dir(acc.acc, C.to_gcharptr(key_str), unsafe.Pointer(&callback)) return &AccountWatch{watch:&watch} } func (acc *Account) WatchKey (key string, callback AccountAsyncWatchDirCallback) *AccountWatch { key_str := C.CString(key) defer C.free_string(key_str) watch := C.watch_key(acc.acc, C.to_gcharptr(key_str), unsafe.Pointer(&callback)) return &AccountWatch{watch:&watch} } func (acc *Account) Unref () { C.g_object_unref(C.gpointer(acc.acc)) } func main() { fmt.Println("test") gtk.Init(&os.Args) go func () { manager := NewManagerForServiceType("microblogging") defer manager.Unref() services, length := manager.GetAccountServices() fmt.Printf("Got %d servicesn", length) for n := uint(0); n < length; n++ { s := services[n].GetService() fmt.Printf("Display Name: %sn", s.GetDisplayName()) fmt.Printf("Name: %sn", s.GetName()) fmt.Printf("Description: %sn", s.GetDescription()) fmt.Printf("Provider: %sn", s.GetProvider()) } gtk.MainQuit() }() gtk.Main() } |
By the way I have fixed a small mem leak I had int he goa bindings by adding the Unref methods so that the glib structures can be freed once you are done with them.
I have been playing a little with golang lately and I have decided to do a small desktop application to try an practice more. Because I wanted to integrate with the gnome-online-accounts API I had to do a small go interface for the API. I leave it here in the wild to see if people can spot errors with it or find it useful:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 | package main /* #define GOA_API_IS_SUBJECT_TO_CHANGE 1 #cgo pkg-config: goa-1.0 #include <stdlib.h> #include <glib.h> #include <gio/gio.h> #include <goa/goa.h> static inline gchar* to_gcharptr(const char* s) { return (gchar*)s; } static inline char* to_charptr(const gchar* s) { return (char*)s; } static inline GObject* to_GObject(void* o) { return G_OBJECT(o); } static inline GoaObject* to_GoaObject(void* o) { return GOA_OBJECT(o); } static GError* to_error(void* err) { return (GError*)err; } static inline void free_string(char* s) { free(s); } */ import "C" import ( "os" "fmt" "errors" "unsafe" "github.com/mattn/go-gtk/glib" "github.com/mattn/go-gtk/gtk" ) func InterfaceToVariant (data interface{}) (*C.GVariant, error) { switch data.(type) { case bool: if data.(bool) { return C.g_variant_new_boolean(C.TRUE), nil } return C.g_variant_new_boolean(C.FALSE), nil case byte: return C.g_variant_new_byte(C.guchar(data.(byte))), nil case int16: return C.g_variant_new_int16(C.gint16(data.(int16))), nil case uint16: return C.g_variant_new_uint16(C.guint16(data.(uint16))), nil case int32: return C.g_variant_new_int32(C.gint32(data.(int32))), nil case uint32: return C.g_variant_new_uint32(C.guint32(data.(uint32))), nil case int64: return C.g_variant_new_int64(C.gint64(data.(int64))), nil case uint64: return C.g_variant_new_uint64(C.guint64(data.(uint64))), nil case float32: return C.g_variant_new_double(C.gdouble(data.(float32))), nil case string: str := C.CString(data.(string)) defer C.free_string(str) return C.g_variant_new_string(C.to_gcharptr(str)), nil default: return nil, errors.New("Could not covert interface to gvariant") } return nil, nil } // =============================== GError ===================================== type Error struct { GError *C.GError } func (v *Error) Error() string { return v.Message() } func (v *Error) Message() string { if unsafe.Pointer(v.GError) == nil || unsafe.Pointer(v.GError.message) == nil { return "" } return C.GoString(C.to_charptr(v.GError.message)) } func ErrorFromNative(err unsafe.Pointer) *Error { return &Error{C.to_error(err)} } // ============================= Cancelable =================================== type Cancellable struct { cancellable *C.GCancellable } func NewCancellable () (*Cancellable) { return &Cancellable{cancellable:C.g_cancellable_new()} } func (can *Cancellable) IsCancelled() bool { result := C.g_cancellable_is_cancelled(can.cancellable) if result != 0 { return true } return false } // TODO: somehow pass a gerror //func (can *Cancellable) SetErrorIfCancelled () { //} func (can *Cancellable) GetFd () int32 { return int32(C.g_cancellable_get_fd(can.cancellable)) } func (can *Cancellable) MakePollFd (poll *C.GPollFD) bool { return false } func (can *Cancellable) ReleaseFd () { C.g_cancellable_release_fd(can.cancellable) } func GetCurrent () *Cancellable { current := C.g_cancellable_get_current() return &Cancellable{cancellable:current} } func (can *Cancellable) PopCurrent () { C.g_cancellable_pop_current(can.cancellable) } func (can *Cancellable) PushCurrent () { C.g_cancellable_push_current(can.cancellable) } func (can *Cancellable) Reset () { C.g_cancellable_reset(can.cancellable) } func (can *Cancellable) Cancel () { C.g_cancellable_cancel(can.cancellable) } // TODO //func (can *Cancellable) Connect () { //} //TODO //func (can *Cancellable) Disonnect () { //} // ================================= Manager =============================== type ManagerAsyncAddAccountCallback func(man *Manager, object_path string, err error) type Manager struct { manager *C.GoaManager } func (man *Manager) CallAddAccount (provider string, identity string, presentation_identity string, credentials interface{}, details interface{}, can *Cancellable, callback ManagerAsyncAddAccountCallback) { go func() { var cancellable *C.GCancellable if can != nil { cancellable = can.cancellable } provider_str := C.CString(provider) defer C.free_string(provider_str) identity_str := C.CString(identity) defer C.free_string(identity_str) presentation_identity_str := C.CString(presentation_identity) defer C.free_string(presentation_identity_str) cred, err := InterfaceToVariant(credentials) if err != nil { callback(man, "", err) return } det, err := InterfaceToVariant(details) if err != nil { callback(man, "", err) return } var gerror *C.GError var object_path *C.gchar C.goa_manager_call_add_account_sync(man.manager, C.to_gcharptr(provider_str), C.to_gcharptr(identity_str), C.to_gcharptr(presentation_identity_str), cred, det, &object_path, cancellable, &gerror) if gerror != nil { err := ErrorFromNative(unsafe.Pointer(gerror)) callback(man, "", err) return } callback(man, C.GoString(C.to_charptr(object_path)), nil) }() } func (man *Manager) CallAddAccountSync (provider string, identity string, presentation_identity string, credentials interface{}, details interface{}, can *Cancellable) (string, error) { var cancellable *C.GCancellable if can != nil { cancellable = can.cancellable } provider_str := C.CString(provider) defer C.free_string(provider_str) identity_str := C.CString(identity) defer C.free_string(identity_str) presentation_identity_str := C.CString(presentation_identity) defer C.free_string(presentation_identity_str) cred, err := InterfaceToVariant(credentials) if err != nil { return "", err } det, err := InterfaceToVariant(details) if err != nil { return "", err } var gerror *C.GError var object_path *C.gchar C.goa_manager_call_add_account_sync(man.manager, C.to_gcharptr(provider_str), C.to_gcharptr(identity_str), C.to_gcharptr(presentation_identity_str), cred, det, &object_path, cancellable, &gerror) if gerror != nil { err := ErrorFromNative(unsafe.Pointer(gerror)) return "", err } return C.GoString(C.to_charptr(object_path)), nil } // ================================ Account ================================ type AccountAsyncEnsureCredentialsCallback func(man *Account, expires_in int32, err error) type AccountAsyncRemoveCallback func(man *Account, err error) type Account struct { account *C.GoaAccount } func (acc *Account) GetProviderType () string { provider := C.goa_account_dup_provider_type(acc.account) defer C.free(unsafe.Pointer(provider)) return C.GoString(C.to_charptr(provider)) } func (acc *Account) GetProviderName () string { provider := C.goa_account_dup_provider_name(acc.account) defer C.free(unsafe.Pointer(provider)) return C.GoString(C.to_charptr(provider)) } func (acc *Account) GetProviderIcon () string { provider := C.goa_account_dup_provider_icon(acc.account) defer C.free(unsafe.Pointer(provider)) return C.GoString(C.to_charptr(provider)) } func (acc *Account) GetId () string { id := C.goa_account_dup_id(acc.account) defer C.free(unsafe.Pointer(id)) return C.GoString(C.to_charptr(id)) } func (acc *Account) GetAttentionNeeded () bool { attention_needed := C.goa_account_get_attention_needed(acc.account) return C.TRUE == attention_needed } func (acc *Account) GetIdentity () string { identity := C.goa_account_dup_identity(acc.account) defer C.free(unsafe.Pointer(identity)) return C.GoString(C.to_charptr(identity)) } func (acc *Account) GetPresentationIdentity () string { identity := C.goa_account_dup_presentation_identity(acc.account) defer C.free(unsafe.Pointer(identity)) return C.GoString(C.to_charptr(identity)) } func (acc *Account) GetMailDisabled () bool { disabled := C.goa_account_get_mail_disabled(acc.account) return C.TRUE == disabled } func (acc *Account) GetCalendarDisabled () bool { disabled := C.goa_account_get_calendar_disabled(acc.account) return C.TRUE == disabled } func (acc *Account) GetContactsDisabled () bool { disabled := C.goa_account_get_contacts_disabled(acc.account) return C.TRUE == disabled } func (acc *Account) GetChatDisabled () bool { disabled := C.goa_account_get_chat_disabled(acc.account) return C.TRUE == disabled } func (acc *Account) GetDocumentsDisabled () bool { disabled := C.goa_account_get_documents_disabled(acc.account) return C.TRUE == disabled } func (acc *Account) CallEnsureCredentials (can *Cancellable, callback AccountAsyncEnsureCredentialsCallback) { go func() { var cancellable *C.GCancellable if can != nil { cancellable = can.cancellable } var gerror *C.GError var gint C.gint C.goa_account_call_ensure_credentials_sync(acc.account, &gint, cancellable, &gerror) if gerror != nil { err := ErrorFromNative(unsafe.Pointer(gerror)) callback(acc, 0, err) return } callback(acc, int32(gint), nil) }() } func (acc *Account) CallSyncEnsureCredentials ( can *Cancellable) (int32, error) { var cancellable *C.GCancellable if can != nil { cancellable = can.cancellable } var gerror *C.GError var gint C.gint C.goa_account_call_ensure_credentials_sync(acc.account, &gint, cancellable, &gerror) if gerror != nil { err := ErrorFromNative(unsafe.Pointer(gerror)) return 0, err } return int32(gint), nil } func (acc *Account) Remove (can *Cancellable, callback AccountAsyncRemoveCallback) { go func() { var cancellable *C.GCancellable if can != nil { cancellable = can.cancellable } var gerror *C.GError C.goa_account_call_remove_sync(acc.account, cancellable, &gerror) if gerror != nil { err := ErrorFromNative(unsafe.Pointer(gerror)) callback(acc, err) return } callback(acc, nil) }() } func (acc *Account) SyncRemove (can *Cancellable) error { var cancellable *C.GCancellable if can != nil { cancellable = can.cancellable } var gerror *C.GError C.goa_account_call_remove_sync(acc.account, cancellable, &gerror) if gerror != nil { err := ErrorFromNative(unsafe.Pointer(gerror)) return err } return nil } // =============================== OAuthBased ============================== type OAuthBasedAsyncGetAccessTokenCallback func(man *OAuthBased, access_token string, access_token_secret string, expiration uint32, err error) type OAuthBased struct { auth_base *C.GoaOAuthBased } func (oauth *OAuthBased) GetAccessToken (can *Cancellable, callback OAuthBasedAsyncGetAccessTokenCallback) { go func() { var cancellable *C.GCancellable if can != nil { cancellable = can.cancellable } var gaccess_token *C.gchar var gaccess_token_secret *C.gchar var gexpiration C.gint var gerror *C.GError C.goa_oauth_based_call_get_access_token_sync(oauth.auth_base, &gaccess_token, &gaccess_token_secret, &gexpiration, cancellable, &gerror) if gerror != nil { err := ErrorFromNative(unsafe.Pointer(gerror)) callback(oauth, "", "", 0, err) return } // get the go types access_token := C.GoString(C.to_charptr(gaccess_token)) access_token_secret := C.GoString(C.to_charptr(gaccess_token_secret)) callback(oauth, access_token, access_token_secret, uint32(gexpiration), nil) }() } func (oauth *OAuthBased) GetAccessTokenSync ( can *Cancellable) (string, string, uint32, error){ var cancellable *C.GCancellable if can != nil { cancellable = can.cancellable } var gaccess_token *C.gchar var gaccess_token_secret *C.gchar var gexpiration C.gint var gerror *C.GError C.goa_oauth_based_call_get_access_token_sync(oauth.auth_base, &gaccess_token, &gaccess_token_secret, &gexpiration, cancellable, &gerror) if gerror != nil { err := ErrorFromNative(unsafe.Pointer(gerror)) return "", "", 0, err } // get the go types access_token := C.GoString(C.to_charptr(gaccess_token)) access_token_secret := C.GoString(C.to_charptr(gaccess_token_secret)) return access_token, access_token_secret, uint32(gexpiration), nil } func (oauth *OAuthBased) GetConsumerKey () string { consumer_key := C.goa_oauth_based_dup_consumer_key(oauth.auth_base) defer C.free(unsafe.Pointer(consumer_key)) return C.GoString(C.to_charptr(consumer_key)) } func (oauth *OAuthBased) GetConsumerSecret () string { consumer_secret := C.goa_oauth_based_dup_consumer_secret(oauth.auth_base) defer C.free(unsafe.Pointer(consumer_secret)) return C.GoString(C.to_charptr(consumer_secret)) } // =============================== Auth2Based =============================== type OAuthBased2AsyncGetAccessTokenCallback func(man *OAuth2Based, access_token string, expiration uint32, err error) type OAuth2Based struct { auth_base *C.GoaOAuth2Based } func (oauth *OAuth2Based) GetClientId () string { id := C.goa_oauth2_based_dup_client_id(oauth.auth_base) defer C.free(unsafe.Pointer(id)) return C.GoString(C.to_charptr(id)) } func (oauth *OAuth2Based) GetClientSecret () string { secret := C.goa_oauth2_based_dup_client_secret(oauth.auth_base) defer C.free(unsafe.Pointer(secret)) return C.GoString(C.to_charptr(secret)) } func (oauth *OAuth2Based) CallGetAccessToken (can *Cancellable, callback OAuthBased2AsyncGetAccessTokenCallback) { go func() { var cancellable *C.GCancellable if can != nil { cancellable = can.cancellable } var gaccess_token *C.gchar var gexpiration C.gint var gerror *C.GError C.goa_oauth2_based_call_get_access_token_sync(oauth.auth_base, &gaccess_token, &gexpiration, cancellable, &gerror) if gerror != nil { err := ErrorFromNative(unsafe.Pointer(gerror)) callback(oauth, "", 0, err) return } access_token := C.GoString(C.to_charptr(gaccess_token)) callback(oauth, access_token, uint32(gexpiration), nil) }() } func (oauth *OAuth2Based) CallGetAccessTokenSync ( can *Cancellable) (string, uint32, error) { var cancellable *C.GCancellable if can != nil { cancellable = can.cancellable } var gaccess_token *C.gchar var gexpiration C.gint var gerror *C.GError C.goa_oauth2_based_call_get_access_token_sync(oauth.auth_base, &gaccess_token, &gexpiration, cancellable, &gerror) if gerror != nil { err := ErrorFromNative(unsafe.Pointer(gerror)) return "", 0, err } access_token := C.GoString(C.to_charptr(gaccess_token)) return access_token, uint32(gexpiration), nil } // =================================== Mail ================================= type Mail struct { mail *C.GoaMail } func (mail *Mail) GetEmailAdress () string { address := C.goa_mail_dup_email_address(mail.mail) defer C.free(unsafe.Pointer(address)) return C.GoString(C.to_charptr(address)) } func (mail *Mail) GetImapHost () string { host := C.goa_mail_dup_imap_host(mail.mail) defer C.free(unsafe.Pointer(host)) return C.GoString(C.to_charptr(host)) } func (mail *Mail) GetImapSupported () bool { return C.TRUE == C.goa_mail_get_imap_supported(mail.mail) } func (mail *Mail) GetImapUseTls () bool { return C.TRUE == C.goa_mail_get_imap_use_tls(mail.mail) } func (mail *Mail) GetImapUserName () string { username := C.goa_mail_dup_imap_user_name(mail.mail) defer C.free(unsafe.Pointer(username)) return C.GoString(C.to_charptr(username)) } func (mail *Mail) GetSmtpHost () string { host := C.goa_mail_dup_smtp_host(mail.mail) defer C.free(unsafe.Pointer(host)) return C.GoString(C.to_charptr(host)) } func (mail *Mail) GetSmtpSupported () bool { return C.TRUE == C.goa_mail_get_smtp_supported(mail.mail) } func (mail *Mail) GetSmtpUseTls () bool { return C.TRUE == C.goa_mail_get_smtp_use_tls(mail.mail) } func (mail *Mail) GetSmtpUserName () string { username := C.goa_mail_dup_smtp_user_name(mail.mail) defer C.free(unsafe.Pointer(username)) return C.GoString(C.to_charptr(username)) } // ================================== Calendar ============================== type Calendar struct { calendar *C.GoaCalendar } // =================================== Contacts ============================= type Contacts struct { contacts *C.GoaContacts } // ===================================== Chat =============================== type Chat struct { chat *C.GoaChat } // ===================================== Documents =========================== type Documents struct { docs *C.GoaDocuments } // ====================================== Exchanges ========================== type Exchange struct { exchange *C.GoaExchange } // ================================== Object =============================== type Object struct { object *C.GoaObject } func (obj *Object) GetManager () *Manager { c_data := C.goa_object_get_manager(obj.object) if c_data != nil { return &Manager{manager:c_data} } return nil } func (obj *Object) GetAccount () *Account { c_data := C.goa_object_get_account(obj.object) if c_data != nil { return &Account{account:c_data} } return nil } func (obj *Object) GetOauthBased () *OAuthBased { c_data := C.goa_object_get_oauth_based(obj.object) if c_data != nil { return &OAuthBased{auth_base:c_data} } return nil } func (obj *Object) GetOauth2Based () *OAuth2Based { c_data := C.goa_object_get_oauth2_based(obj.object) if c_data != nil { return &OAuth2Based{auth_base:c_data} } return nil } func (obj *Object) GetMail () *Mail { c_data := C.goa_object_get_mail(obj.object) if c_data != nil { return &Mail{mail:c_data} } return nil } func (obj *Object) GetCalendar () *Calendar { c_data := C.goa_object_get_calendar(obj.object) if c_data != nil { return &Calendar{calendar:c_data} } return nil } func (obj *Object) GetContacts () *Contacts { c_data := C.goa_object_get_contacts(obj.object) if c_data != nil { return &Contacts{contacts:c_data} } return nil } func (obj *Object) GetChat () *Chat { c_data := C.goa_object_get_chat(obj.object) if c_data != nil { return &Chat{chat:c_data} } return nil } func (obj *Object) GetDocuments () *Documents { c_data := C.goa_object_get_documents(obj.object) if c_data != nil { return &Documents{docs:c_data} } return nil } func (obj *Object) GetExchange () *Exchange { c_data := C.goa_object_get_exchange(obj.object) if c_data != nil { return &Exchange{exchange:c_data} } return nil } // ================================== Client =============================== type ClientAsyncReadyCallback func(client *Client, err error) type Client struct { client *C.GoaClient } func NewSyncClient (can *Cancellable) (*Client, error) { var gerror *C.GError var cancellable *C.GCancellable if can != nil { cancellable = can.cancellable } goa_client := &Client{client:C.goa_client_new_sync(cancellable, &gerror)} if gerror != nil { err := ErrorFromNative(unsafe.Pointer(gerror)) return nil, err } return goa_client, nil } func NewClient (can *Cancellable, callback ClientAsyncReadyCallback) { go func(){ var gerror *C.GError var cancellable *C.GCancellable if can != nil { cancellable = can.cancellable } goa_client := &Client{ client:C.goa_client_new_sync(cancellable, &gerror)} if gerror != nil { err := ErrorFromNative(unsafe.Pointer(gerror)) callback(nil, err) return } callback(goa_client, nil) }() } func (client *Client) GetManager () *Manager { return &Manager{manager:C.goa_client_get_manager(client.client)} } func (client *Client) GetAccounts () ([]*Object, uint) { accounts_list := C.goa_client_get_accounts(client.client) // free the glist defer C.g_list_free(accounts_list) length := C.g_list_length(accounts_list) result := make([]*Object, C.g_list_length(accounts_list)) // construct each of the goa objects and add them to the array for n := C.guint(0); n < length; n++ { data:= C.g_list_nth_data(accounts_list, n) pointer := unsafe.Pointer(data) goa_object := &Object{object:C.to_GoaObject(pointer)} result[uint(n)] = goa_object } return result, uint(length) } func (client *Client) LookupById (account_id string) *Object { account_id_str := C.CString(account_id) defer C.free_string(account_id_str) return &Object{object:C.goa_client_lookup_by_id(client.client, C.to_gcharptr(account_id_str))} } func (client *Client) Connect(s string, f interface{}, datas ...interface{}) { glib.ObjectFromNative(unsafe.Pointer( C.to_GObject(unsafe.Pointer(client.client)))).Connect(s, f, datas...) } func main() { fmt.Println("test") gtk.Init(&os.Args) go func () { client, err := NewSyncClient(nil) if err != nil { fmt.Println("Got error getting client") } accounts, length := client.GetAccounts() fmt.Printf("Got %d accounts backn", length) for n := uint(0); n < length; n++ { account := accounts[n].GetAccount() fmt.Printf("Account %sn", account.GetId()) fmt.Println("tAccount info:") fmt.Printf("ttProvider type:tt%sn", account.GetProviderType()) fmt.Printf("ttProvider name:tt%sn", account.GetProviderName()) fmt.Printf("ttIdentity:tt%sn", account.GetIdentity()) fmt.Printf("ttPresentation identity:t%sn", account.GetPresentationIdentity()) mail := accounts[n].GetMail() if mail != nil { fmt.Printf("tEmail info:n") fmt.Printf("ttEmail address:tt%sn", mail.GetEmailAdress()) fmt.Printf("ttImap host:tt%sn", mail.GetImapHost()) fmt.Printf("ttSmtp host:tt%sn", mail.GetSmtpHost()) } } }() gtk.Main() } |
I really hate my voice when I hear myself in videos etc.. but well, it happens to most of us. Here is the small rant ralsina and I had about multi-platform programming with python:
Oh boy I’m getting good with these things… The following is an example of an interesting behavior you will find with python if you use \?
path = 'C:\Users\Mandel\COM1' with open(path, 'w') as fd: fd.write('hello') [Errno 2] Not such file or directory 'C:\Users\Mandel\COM1' |
Lets try with a .txt for testing.
path = 'C:\Users\Mandel\COM1.txt' with open(path, 'w') as fd: fd.write('hello') [Errno 2] Not such file or directory. 'C:\Users\Mandel\COM1.txt' |
Ok, lets bring our awesome friend \? in the game:
path = '\\?\C:\Users\Mandel\COM1' with open(path, 'w') as fd: fd.write('hello') |
Works o/. You may wonder what is going on here… Well, \? allows you to use COM1, yet this approach has a very interesting result:
os.chdir('c:\Users\Mandel') for file in os.listdir('.'): print os.path.abspath(file) C:Usersmandeltest.py C:Usersmandelnotes.txt \.COM1 |
WTF is the least I can say. It turns out that os.path.abspath considers that the abspath of the COM1 file present in the hd is the COM7 device.. cute!. What is even more, if you create a file COM1 and a COM1.txt you will get \.COM1 for both files. As soon as I have some time away from Mac OS X I’ll propose a fix for this in Ubuntu One. Sometime I really wonder if I will ever use this knowledge in any other application.
The following behaviour completely got me out of place:
>>> byte_string = b'Test' >>> byte_string[0] == b'T' False >>> byte_string[0] in b'T' True >>> byte_string[0] 84 >>> b'T' b'T' |
Turns out this is documented (blame me I did not read the docs!!!). The correct way to do it is:
>>> byte_string = b'Test' >>> byte_string[:1] == b'T' True |
I really hope we don’t have any code that does this operations.
So yet again I have been confronted with broken tests in Ubuntu One. As I have already mentioned before I have spent a significant amount of time ensuring that the tests of Ubuntu One (which use twisted a lot) are deterministic and we do not leave a dirty reactor in the way. In order to do that a few week a go I wrote the following code that will help the rest of the team write such tests:
import os import shutil import tempfile from twisted.internet import defer, endpoints, protocol from twisted.spread import pb from ubuntuone.devtools.testcases import BaseTestCase # no init method + twisted common warnings # pylint: disable=W0232, C0103, E1101 def server_protocol_factory(cls): """Factory to create tidy protocols.""" if cls is None: cls = protocol.Protocol class ServerTidyProtocol(cls): """A tidy protocol.""" def connectionLost(self, *args): """Lost the connection.""" cls.connectionLost(self, *args) # lets tell everyone # pylint: disable=W0212 if (self.factory._disconnecting and self.factory.testserver_on_connection_lost is not None and not self.factory.testserver_on_connection_lost.called): self.factory.testserver_on_connection_lost.callback(self) # pylint: enable=W0212 return ServerTidyProtocol def client_protocol_factory(cls): """Factory to create tidy protocols.""" if cls is None: cls = protocol.Protocol class ClientTidyProtocol(cls): """A tidy protocol.""" def connectionLost(self, *a): """Connection list.""" # pylint: disable=W0212 if (self.factory._disconnecting and self.factory.testserver_on_connection_lost is not None and not self.factory.testserver_on_connection_lost.called): self.factory.testserver_on_connection_lost.callback(self) # pylint: enable=W0212 cls.connectionLost(self, *a) return ClientTidyProtocol class TidySocketServer(object): """Ensure that twisted servers are correctly managed in tests. Closing a twisted server is a complicated matter. In order to do so you have to ensure that three different deferreds are fired: 1. The server must stop listening. 2. The client connection must disconnect. 3. The server connection must disconnect. This class allows to create a server and a client that will ensure that the reactor is left clean by following the pattern described at http://mumak.net/stuff/twisted-disconnect.html """ def __init__(self): """Create a new instance.""" self.listener = None self.server_factory = None self.connector = None self.client_factory = None def get_server_endpoint(self): """Return the server endpoint description.""" raise NotImplementedError('To be implemented by child classes.') def get_client_endpoint(self): """Return the client endpoint description.""" raise NotImplementedError('To be implemented by child classes.') @defer.inlineCallbacks def listen_server(self, server_class, *args, **kwargs): """Start a server in a random port.""" from twisted.internet import reactor self.server_factory = server_class(*args, **kwargs) self.server_factory._disconnecting = False self.server_factory.testserver_on_connection_lost = defer.Deferred() self.server_factory.protocol = server_protocol_factory( self.server_factory.protocol) endpoint = endpoints.serverFromString(reactor, self.get_server_endpoint()) self.listener = yield endpoint.listen(self.server_factory) defer.returnValue(self.server_factory) @defer.inlineCallbacks def connect_client(self, client_class, *args, **kwargs): """Conect a client to a given server.""" from twisted.internet import reactor if self.server_factory is None: raise ValueError('Server Factory was not provided.') if self.listener is None: raise ValueError('%s has not started listening.', self.server_factory) self.client_factory = client_class(*args, **kwargs) self.client_factory._disconnecting = False self.client_factory.protocol = client_protocol_factory( self.client_factory.protocol) self.client_factory.testserver_on_connection_lost = defer.Deferred() endpoint = endpoints.clientFromString(reactor, self.get_client_endpoint()) self.connector = yield endpoint.connect(self.client_factory) defer.returnValue(self.client_factory) def clean_up(self): """Action to be performed for clean up.""" if self.server_factory is None or self.listener is None: # nothing to clean return defer.succeed(None) if self.listener and self.connector: # clean client and server self.server_factory._disconnecting = True self.client_factory._disconnecting = True self.connector.transport.loseConnection() d = defer.maybeDeferred(self.listener.stopListening) return defer.gatherResults([d, self.client_factory.testserver_on_connection_lost, self.server_factory.testserver_on_connection_lost]) if self.listener: # just clean the server since there is no client self.server_factory._disconnecting = True return defer.maybeDeferred(self.listener.stopListening) class TidyTCPServer(TidySocketServer): """A tidy tcp domain sockets server.""" client_endpoint_pattern = 'tcp:host=127.0.0.1:port=%s' server_endpoint_pattern = 'tcp:0:interface=127.0.0.1' def get_server_endpoint(self): """Return the server endpoint description.""" return self.server_endpoint_pattern def get_client_endpoint(self): """Return the client endpoint description.""" if self.server_factory is None: raise ValueError('Server Factory was not provided.') if self.listener is None: raise ValueError('%s has not started listening.', self.server_factory) return self.client_endpoint_pattern % self.listener.getHost().port class TidyUnixServer(TidySocketServer): """A tidy unix domain sockets server.""" client_endpoint_pattern = 'unix:path=%s' server_endpoint_pattern = 'unix:%s' def __init__(self): """Create a new instance.""" super(TidyUnixServer, self).__init__() self.temp_dir = tempfile.mkdtemp() self.path = os.path.join(self.temp_dir, 'tidy_unix_server') def get_server_endpoint(self): """Return the server endpoint description.""" return self.server_endpoint_pattern % self.path def get_client_endpoint(self): """Return the client endpoint description.""" return self.client_endpoint_pattern % self.path def clean_up(self): """Action to be performed for clean up.""" result = super(TidyUnixServer, self).clean_up() # remove the dir once we are disconnected result.addCallback(lambda _: shutil.rmtree(self.temp_dir)) return result class ServerTestCase(BaseTestCase): """Base test case for tidy servers.""" @defer.inlineCallbacks def setUp(self): """Set the diff tests.""" yield super(ServerTestCase, self).setUp() try: self.server_runner = self.get_server() except NotImplementedError: self.server_runner = None self.server_factory = None self.client_factory = None self.server_disconnected = None self.client_connected = None self.client_disconnected = None self.listener = None self.connector = None self.addCleanup(self.tear_down_server_client) def get_server(self): """Return the server to be used to run the tests.""" raise NotImplementedError('To be implemented by child classes.') @defer.inlineCallbacks def listen_server(self, server_class, *args, **kwargs): """Listen a server. The method takes the server class and the arguments that should be passed to the server constructor. """ self.server_factory = yield self.server_runner.listen_server( server_class, *args, **kwargs) self.server_disconnected = self.server_factory.testserver_on_connection_lost self.listener = self.server_runner.listener @defer.inlineCallbacks def connect_client(self, client_class, *args, **kwargs): """Connect the client. The method takes the client factory class and the arguments that should be passed to the client constructor. """ self.client_factory = yield self.server_runner.connect_client( client_class, *args, **kwargs) self.client_disconnected = self.client_factory.testserver_on_connection_lost self.connector = self.server_runner.connector def tear_down_server_client(self): """Clean the server and client.""" if self.server_runner: return self.server_runner.clean_up() class TCPServerTestCase(ServerTestCase): """Test that uses a single twisted server.""" def get_server(self): """Return the server to be used to run the tests.""" return TidyTCPServer() class UnixServerTestCase(ServerTestCase): """Test that uses a single twisted server.""" def get_server(self): """Return the server to be used to run the tests.""" return TidyUnixServer() class PbServerTestCase(ServerTestCase): """Test a pb server.""" def get_server(self): """Return the server to be used to run the tests.""" raise NotImplementedError('To be implemented by child classes.') @defer.inlineCallbacks def listen_server(self, *args, **kwargs): """Listen a pb server.""" yield super(PbServerTestCase, self).listen_server(pb.PBServerFactory, *args, **kwargs) @defer.inlineCallbacks def connect_client(self, *args, **kwargs): """Connect a pb client.""" yield super(PbServerTestCase, self).connect_client(pb.PBClientFactory, *args, **kwargs) class TCPPbServerTestCase(PbServerTestCase): """Test a pb server over TCP.""" def get_server(self): """Return the server to be used to run the tests.""" return TidyTCPServer() class UnixPbServerTestCase(PbServerTestCase): """Test a pb server over Unix domain sockets.""" def get_server(self): """Return the server to be used to run the tests.""" return TidyUnixServer() |
The idea of the code is that developers do not need to worry about how to stop listening ports in their tests and just write tests like the following:
class TCPMultipleServersTestCase(TestCase): """Ensure that several servers can be ran.""" timeout = 2 @defer.inlineCallbacks def setUp(self): """Set the diff tests.""" yield super(TCPMultipleServersTestCase, self).setUp() self.first_tcp_server = self.get_server() self.second_tcp_server = self.get_server() self.adder = Adder() self.calculator = Calculator(self.adder) self.echoer = Echoer() def get_server(self): """Return the server to be used to run the tests.""" return TidyTCPServer() @defer.inlineCallbacks def test_single_server(self): """Test setting a single server.""" first_number = 1 second_number = 2 yield self.first_tcp_server.listen_server(pb.PBServerFactory, self.calculator) self.addCleanup(self.first_tcp_server.clean_up) calculator_c = yield self.first_tcp_server.connect_client( pb.PBClientFactory) calculator = yield calculator_c.getRootObject() adder = yield calculator.callRemote('get_adder') result = yield adder.callRemote('add', first_number, second_number) self.assertEqual(first_number + second_number, result) @defer.inlineCallbacks def test_multiple_server(self): """Test setting multiple server.""" first_number = 1 second_number = 2 # first server yield self.first_tcp_server.listen_server(pb.PBServerFactory, self.calculator) self.addCleanup(self.first_tcp_server.clean_up) # second server yield self.second_tcp_server.listen_server(pb.PBServerFactory, self.echoer) self.addCleanup(self.second_tcp_server.clean_up) # connect the diff clients calculator_c = yield self.first_tcp_server.connect_client( pb.PBClientFactory) echoer_c = yield self.second_tcp_server.connect_client( pb.PBClientFactory) calculator = yield calculator_c.getRootObject() adder = yield calculator.callRemote('get_adder') result = yield adder.callRemote('add', first_number, second_number) self.assertEqual(first_number + second_number, result) echoer = yield echoer_c.getRootObject() echo = yield echoer.callRemote('say', 'hello') self.assertEqual(self.echoer.remote_say('hello'), echo) |
As you can see those tests do not give a rats ass about ensuring that the clients lose connection or we stop listening ports… Or so I though because the following code made such approach break in Mac OS X (although I suspect it was broken on Linux and Windows but we never experienced it):
class NullProtocol(protocol.Protocol): """A protocol that drops the connection.""" def connectionMade(self): """Just drop the connection.""" self.transport.loseConnection() class PortDetectFactory(protocol.ClientFactory): """Will detect if something is listening in a given port.""" protocol = NullProtocol def __init__(self): """Initialize this instance.""" self.d = defer.Deferred() def is_listening(self): """A deferred that will become True if something is listening.""" return self.d def buildProtocol(self, addr): """Connected.""" p = protocol.ClientFactory.buildProtocol(self, addr) if not self.d.called: self.d.callback(True) return p def clientConnectionLost(self, connector, reason): """The connection was lost.""" if not self.d.called: self.d.callback(False) def clientConnectionFailed(self, connector, reason): """The connection failed.""" if not self.d.called: self.d.callback(False) |
The code used to test the above was written as:
@defer.inlineCallbacks def test_is_already_running(self): """The is_already_running method returns True if already started.""" server = self.get_server() self.addCleanup(server.clean_up) class TestConnect(object): @defer.inlineCallbacks def connect(my_self, factory): connected_factory = yield server.connect_client(PortDetectFactory) self.patch(factory, 'is_listening', lambda: connected_factory.is_listening()) defer.returnValue(connected_factory) self.patch(tcpactivation, 'clientFromString', lambda *args: TestConnect()) yield server.listen_server(protocol.ServerFactory) # pylint: disable=E1101 ad = ActivationDetector(self.config) result = yield ad.is_already_running() self.assertTrue(result, "It should be already running.") |
While in all the other platforms the tests passed with no problems on Mac OS X the tests would block in the clean_up method from the server because the deferred that was called in the connectionLost from the ServerTidyProtocol was never fired… Interesting.. After digging in the code I realized that the main issue with the approach of the clean_up code was wrong. The problem relies on the way in which the NullProtocol works. As you can see in the code the protocol loses its connections as soon as it made. This results in to possible things:
When running the tests on Windows and Linux we were always facing the first scenario, buildProtocol was called which meant that connectionLost in the server protocol would be called. On the other hand, on Mac OS X, 1 out of 10 runs of the tests would block in the clean up because we would be in the second scenario, that is, no protocol would be build in the ServerFactory which results in the connectionLost never being called because it was no needed. The work around this issue is quite simple once you understand what is going on. The ServerFactory has to be modified to set the deferred when buildProtocol is called and not before ensuring that when we cleanup we check if the deferred is None and if it is not we wait for it to be fired. The fixed version of the helper code is the following:
import os import shutil import tempfile from twisted.internet import defer, endpoints, protocol from twisted.spread import pb from ubuntuone.devtools.testcases import BaseTestCase # no init method + twisted common warnings # pylint: disable=W0232, C0103, E1101 def server_protocol_factory(cls): """Factory to create tidy protocols.""" if cls is None: cls = protocol.Protocol class ServerTidyProtocol(cls): """A tidy protocol.""" def connectionLost(self, *args): """Lost the connection.""" cls.connectionLost(self, *args) # lets tell everyone # pylint: disable=W0212 if (self.factory._disconnecting and self.factory.testserver_on_connection_lost is not None and not self.factory.testserver_on_connection_lost.called): self.factory.testserver_on_connection_lost.callback(self) # pylint: enable=W0212 return ServerTidyProtocol def server_factory_factory(cls): """Factory that creates special types of factories for tests.""" if cls is None: cls = protocol.ServerFactory class TidyServerFactory(cls): """A tidy factory.""" testserver_on_connection_lost = None def buildProtocol(self, addr): prot = cls.buildProtocol(self, addr) self.testserver_on_connection_lost = defer.Deferred() return prot return TidyServerFactory def client_protocol_factory(cls): """Factory to create tidy protocols.""" if cls is None: cls = protocol.Protocol class ClientTidyProtocol(cls): """A tidy protocol.""" def connectionLost(self, *a): """Connection list.""" cls.connectionLost(self, *a) # pylint: disable=W0212 if (self.factory._disconnecting and self.factory.testserver_on_connection_lost is not None and not self.factory.testserver_on_connection_lost.called): self.factory.testserver_on_connection_lost.callback(self) # pylint: enable=W0212 return ClientTidyProtocol class TidySocketServer(object): """Ensure that twisted servers are correctly managed in tests. Closing a twisted server is a complicated matter. In order to do so you have to ensure that three different deferreds are fired: 1. The server must stop listening. 2. The client connection must disconnect. 3. The server connection must disconnect. This class allows to create a server and a client that will ensure that the reactor is left clean by following the pattern described at http://mumak.net/stuff/twisted-disconnect.html """ def __init__(self): """Create a new instance.""" self.listener = None self.server_factory = None self.connector = None self.client_factory = None def get_server_endpoint(self): """Return the server endpoint description.""" raise NotImplementedError('To be implemented by child classes.') def get_client_endpoint(self): """Return the client endpoint description.""" raise NotImplementedError('To be implemented by child classes.') @defer.inlineCallbacks def listen_server(self, server_class, *args, **kwargs): """Start a server in a random port.""" from twisted.internet import reactor tidy_class = server_factory_factory(server_class) self.server_factory = tidy_class(*args, **kwargs) self.server_factory._disconnecting = False self.server_factory.protocol = server_protocol_factory( self.server_factory.protocol) endpoint = endpoints.serverFromString(reactor, self.get_server_endpoint()) self.listener = yield endpoint.listen(self.server_factory) defer.returnValue(self.server_factory) @defer.inlineCallbacks def connect_client(self, client_class, *args, **kwargs): """Conect a client to a given server.""" from twisted.internet import reactor if self.server_factory is None: raise ValueError('Server Factory was not provided.') if self.listener is None: raise ValueError('%s has not started listening.', self.server_factory) self.client_factory = client_class(*args, **kwargs) self.client_factory._disconnecting = False self.client_factory.protocol = client_protocol_factory( self.client_factory.protocol) self.client_factory.testserver_on_connection_lost = defer.Deferred() endpoint = endpoints.clientFromString(reactor, self.get_client_endpoint()) self.connector = yield endpoint.connect(self.client_factory) defer.returnValue(self.client_factory) def clean_up(self): """Action to be performed for clean up.""" if self.server_factory is None or self.listener is None: # nothing to clean return defer.succeed(None) if self.listener and self.connector: # clean client and server self.server_factory._disconnecting = True self.client_factory._disconnecting = True d = defer.maybeDeferred(self.listener.stopListening) self.connector.transport.loseConnection() if self.server_factory.testserver_on_connection_lost: return defer.gatherResults([d, self.client_factory.testserver_on_connection_lost, self.server_factory.testserver_on_connection_lost]) else: return defer.gatherResults([d, self.client_factory.testserver_on_connection_lost]) if self.listener: # just clean the server since there is no client # pylint: disable=W0201 self.server_factory._disconnecting = True return defer.maybeDeferred(self.listener.stopListening) # pylint: enable=W0201 class TidyTCPServer(TidySocketServer): """A tidy tcp domain sockets server.""" client_endpoint_pattern = 'tcp:host=127.0.0.1:port=%s' server_endpoint_pattern = 'tcp:0:interface=127.0.0.1' def get_server_endpoint(self): """Return the server endpoint description.""" return self.server_endpoint_pattern def get_client_endpoint(self): """Return the client endpoint description.""" if self.server_factory is None: raise ValueError('Server Factory was not provided.') if self.listener is None: raise ValueError('%s has not started listening.', self.server_factory) return self.client_endpoint_pattern % self.listener.getHost().port class TidyUnixServer(TidySocketServer): """A tidy unix domain sockets server.""" client_endpoint_pattern = 'unix:path=%s' server_endpoint_pattern = 'unix:%s' def __init__(self): """Create a new instance.""" super(TidyUnixServer, self).__init__() self.temp_dir = tempfile.mkdtemp() self.path = os.path.join(self.temp_dir, 'tidy_unix_server') def get_server_endpoint(self): """Return the server endpoint description.""" return self.server_endpoint_pattern % self.path def get_client_endpoint(self): """Return the client endpoint description.""" return self.client_endpoint_pattern % self.path def clean_up(self): """Action to be performed for clean up.""" result = super(TidyUnixServer, self).clean_up() # remove the dir once we are disconnected result.addCallback(lambda _: shutil.rmtree(self.temp_dir)) return result class ServerTestCase(BaseTestCase): """Base test case for tidy servers.""" @defer.inlineCallbacks def setUp(self): """Set the diff tests.""" yield super(ServerTestCase, self).setUp() try: self.server_runner = self.get_server() except NotImplementedError: self.server_runner = None self.server_factory = None self.client_factory = None self.server_disconnected = None self.client_connected = None self.client_disconnected = None self.listener = None self.connector = None self.addCleanup(self.tear_down_server_client) def get_server(self): """Return the server to be used to run the tests.""" raise NotImplementedError('To be implemented by child classes.') @defer.inlineCallbacks def listen_server(self, server_class, *args, **kwargs): """Listen a server. The method takes the server class and the arguments that should be passed to the server constructor. """ self.server_factory = yield self.server_runner.listen_server( server_class, *args, **kwargs) self.server_disconnected = self.server_factory.testserver_on_connection_lost self.listener = self.server_runner.listener @defer.inlineCallbacks def connect_client(self, client_class, *args, **kwargs): """Connect the client. The method takes the client factory class and the arguments that should be passed to the client constructor. """ self.client_factory = yield self.server_runner.connect_client( client_class, *args, **kwargs) self.client_disconnected = self.client_factory.testserver_on_connection_lost self.connector = self.server_runner.connector def tear_down_server_client(self): """Clean the server and client.""" if self.server_runner: return self.server_runner.clean_up() class TCPServerTestCase(ServerTestCase): """Test that uses a single twisted server.""" def get_server(self): """Return the server to be used to run the tests.""" return TidyTCPServer() class UnixServerTestCase(ServerTestCase): """Test that uses a single twisted server.""" def get_server(self): """Return the server to be used to run the tests.""" return TidyUnixServer() class PbServerTestCase(ServerTestCase): """Test a pb server.""" def get_server(self): """Return the server to be used to run the tests.""" raise NotImplementedError('To be implemented by child classes.') @defer.inlineCallbacks def listen_server(self, *args, **kwargs): """Listen a pb server.""" yield super(PbServerTestCase, self).listen_server(pb.PBServerFactory, *args, **kwargs) @defer.inlineCallbacks def connect_client(self, *args, **kwargs): """Connect a pb client.""" yield super(PbServerTestCase, self).connect_client(pb.PBClientFactory, *args, **kwargs) class TCPPbServerTestCase(PbServerTestCase): """Test a pb server over TCP.""" def get_server(self): """Return the server to be used to run the tests.""" return TidyTCPServer() class UnixPbServerTestCase(PbServerTestCase): """Test a pb server over Unix domain sockets.""" def get_server(self): """Return the server to be used to run the tests.""" return TidyUnixServer() |
I wonder if at some point I should share this code for the people out there… any opinions?