diff --git a/.github/banner/venom.svg b/.github/banner/venom.svg new file mode 100644 index 0000000..6e27f9c --- /dev/null +++ b/.github/banner/venom.svg @@ -0,0 +1 @@ +Venom \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index 18d4357..573d4e6 100644 --- a/Dockerfile +++ b/Dockerfile @@ -11,7 +11,7 @@ RUN \ libopus-dev \ libsodium-dev \ libsoup2.4-dev \ - libsqlite3-dev \ + libsqlcipher-dev \ libvpx-dev \ libgee-0.8-dev \ libgspell-1-dev \ diff --git a/README.md b/README.md index 5583eaf..662d881 100644 --- a/README.md +++ b/README.md @@ -1,13 +1,20 @@ -Venom +![Venom](.github/banner/venom.svg) ===== -[![Build Status](https://travis-ci.org/naxuroqa/Venom.png?branch=develop)](https://travis-ci.org/naxuroqa/Venom) [![tip for next commit](http://tip4commit.com/projects/634.svg)](http://tip4commit.com/projects/634) [![codecov](https://codecov.io/gh/naxuroqa/Venom/branch/develop/graph/badge.svg)](https://codecov.io/gh/naxuroqa/Venom) +[![Build Status](https://travis-ci.org/naxuroqa/Venom.png?branch=develop)](https://travis-ci.org/naxuroqa/Venom) +![GitHub](https://img.shields.io/github/license/naxuroqa/venom.svg) +![GitHub release](https://img.shields.io/github/release/naxuroqa/venom.svg) +![GitHub last commit](https://img.shields.io/github/last-commit/naxuroqa/venom.svg) +[![tip for next commit](http://tip4commit.com/projects/634.svg)](http://tip4commit.com/projects/634) +[![codecov](https://codecov.io/gh/naxuroqa/Venom/branch/develop/graph/badge.svg)](https://codecov.io/gh/naxuroqa/Venom) + ###### a modern [Tox](https://github.com/TokTok/c-toxcore) client for the GNU/Linux desktop Features -------- +* Encrypted profiles * Secure, private messaging * Read receipts * Contact aliases @@ -19,6 +26,7 @@ Features * Socks5 Proxy support * Spell checking * Sound notifications +* [Faux offline messaging](https://wiki.tox.chat/users/offline_messaging) Roadmap ------- @@ -41,7 +49,7 @@ Dependencies * `libgee >= 0.20` * `libsoup-2.4` * `gspell >= 1.8` -* `sqlite3` +* `sqlcipher` * `toxcore >= 0.2` Build-Dependencies diff --git a/com.github.naxuroqa.venom.yml b/com.github.naxuroqa.venom.yml index 7275606..2ff6b81 100644 --- a/com.github.naxuroqa.venom.yml +++ b/com.github.naxuroqa.venom.yml @@ -15,20 +15,31 @@ finish-args: - --talk-name=org.freedesktop.Notifications - --filesystem=xdg-data/pixmaps/faces:ro - --filesystem=xdg-download +cleanup-commands: + - rm -rf /app/bin/{DHT_bootstrap,gspell-app1,enchant*} + - rm -rf /app/include + - rm -rf /app/lib/{*.a,*.la,girepository-1.0,pkgconfig} + - rm -rf /app/lib/enchant-2/{*.a,*.la} + - rm -rf /app/share/{vala,man,gir-1.0} build-options: - cflags: -O3 + cflags: -O3 -DSQLITE_HAS_CODEC cxxflags: -O3 modules: - - name: libgee - build-options: - env: - PKG_CONFIG_GOBJECT_INTROSPECTION_1_0_GIRDIR: /app/share/gir-1.0 - PKG_CONFIG_GOBJECT_INTROSPECTION_1_0_TYPELIBDIR: /app/lib/girepository-1.0 + - name: sqlcipher + rm-configure: true + config-opts: + - --enable-tempstore=yes + - --disable-tcl sources: - type: git - url: https://gitlab.gnome.org/GNOME/libgee - tag: 0.20.1 - commit: 57e4c8a08d61ab77bbec310a3a1621e6bf3111cb + url: https://github.com/sqlcipher/sqlcipher + tag: v3.4.2 + commit: c6f709fca81c910ba133aaf6330c28e01ccfe5f8 + disable-fsckobjects: true + - type: script + dest-filename: autogen.sh + commands: + - AUTOMAKE="automake --foreign" autoreconf -vfi - name: libsodium sources: - type: git @@ -45,6 +56,16 @@ modules: url: https://github.com/toktok/c-toxcore tag: v0.2.8 commit: 3f35a84968f100e1e6d3c9df467fd3c82a9ebb13 + - name: libgee + build-options: + env: + PKG_CONFIG_GOBJECT_INTROSPECTION_1_0_GIRDIR: /app/share/gir-1.0 + PKG_CONFIG_GOBJECT_INTROSPECTION_1_0_TYPELIBDIR: /app/lib/girepository-1.0 + sources: + - type: git + url: https://gitlab.gnome.org/GNOME/libgee + tag: 0.20.1 + commit: 57e4c8a08d61ab77bbec310a3a1621e6bf3111cb - name: enchant sources: - type: archive diff --git a/meson.build b/meson.build index b6a964f..80f038f 100644 --- a/meson.build +++ b/meson.build @@ -1,6 +1,6 @@ project('venom', ['vala', 'c'], license: 'GPL3+', - version: '0.4.2' + version: '0.5.0' ) i18n = import('i18n') diff --git a/po/LINGUAS b/po/LINGUAS index a69a902..08fbb27 100644 --- a/po/LINGUAS +++ b/po/LINGUAS @@ -4,6 +4,6 @@ fr it pl pt -pt_br +pt_BR ru zh_CN diff --git a/po/POTFILES b/po/POTFILES index 51805d0..3cd519c 100644 --- a/po/POTFILES +++ b/po/POTFILES @@ -3,7 +3,6 @@ src/undo/UndoCommand.vala src/undo/UndoStack.vala src/undo/TextBufferUndoBinding.vala src/undo/SimpleUndoStack.vala -src/core/Interfaces.vala src/core/NotificationListener.vala src/core/UserInfo.vala src/core/TimeStamp.vala @@ -11,10 +10,12 @@ src/core/FileTransfer.vala src/core/R.vala src/core/Logger.vala src/core/Message.vala +src/core/GlobalSettings.vala src/core/Contact.vala src/core/FileIO.vala src/core/WidgetFactory.vala src/core/Tools.vala +src/core/Profile.vala src/core/WindowState.vala src/core/Application.vala src/core/Identicon.vala @@ -29,28 +30,32 @@ src/viewmodel/MessageViewModel.vala src/viewmodel/FileTransferEntryViewModel.vala src/viewmodel/CreateGroupchatViewModel.vala src/viewmodel/ConferenceInfoViewModel.vala -src/tox/ToxSessionIO.vala -src/tox/DhtNodeDatabase.vala +src/tox/ToxMessage.vala src/tox/ToxAdapterFriendListener.vala -src/tox/ContactDatabase.vala +src/tox/JsonWebDhtNodeUpdater.vala src/tox/ToxAdapterFiletransferListener.vala +src/tox/SqliteNospamRepository.vala src/tox/Conference.vala src/tox/FriendRequest.vala src/tox/ToxSessionThread.vala +src/tox/SqliteDhtNodeRepository.vala +src/tox/SqliteFriendRequestRepository.vala src/tox/ToxContact.vala src/tox/ToxSession.vala -src/tox/JsonWebDhtNodeDatabase.vala src/tox/DhtNode.vala -src/tox/SqliteDhtNodeDatabase.vala +src/tox/SqliteContactRepository.vala src/tox/ToxAdapterSelfListener.vala src/tox/ToxAdapterConferenceListener.vala -src/tox/MessageDatabase.vala +src/tox/SqliteMessageRepository.vala src/tox/ConferenceMessage.vala +src/tox/StaticDhtNodeUpdater.vala src/plugin/Plugin.vala src/plugin/Pluginregistrar.vala src/view/MessageWidget.vala src/view/ConferenceInfoWidget.vala src/view/ApplicationWindow.vala +src/view/LoginWidget.vala +src/view/InAppNotification.vala src/view/ConferenceWindow.vala src/view/NodeWidget.vala src/view/AboutDialog.vala @@ -68,11 +73,13 @@ src/view/AddContactWidget.vala src/view/ConversationWindow.vala src/view/PeerEntry.vala src/view/FileTransferEntry.vala +src/view/NospamEntry.vala src/view/ConferenceInviteEntry.vala src/view/UserInfoWidget.vala src/view/FriendInfoWidget.vala src/view/ContextStyleBinding.vala src/ui/file_transfer_widget.ui +src/ui/login_widget.ui src/ui/contact_list_entry_compact.ui src/ui/application_window.ui src/ui/file_transfer_entry.ui @@ -95,6 +102,7 @@ src/ui/user_info_widget.ui src/ui/contact_list_entry.ui src/ui/file_transfer_entry_inline.ui src/ui/settings_widget.ui +src/ui/nospam_entry.ui src/ui/friend_info_widget.ui src/ui/error_widget.ui src/db/DatabaseInterfaces.vala diff --git a/po/de.po b/po/de.po index be09e9e..50ebef9 100644 --- a/po/de.po +++ b/po/de.po @@ -8,15 +8,15 @@ msgstr "" "Language: de\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" -#: src/core/Application.vala:36 +#: src/core/Application.vala:37 msgid "Set level of messages to log" msgstr "Setze das Niveau für Protokollmeldungen" -#: src/core/Application.vala:36 +#: src/core/Application.vala:37 msgid "" msgstr "" -#: src/core/Application.vala:37 +#: src/core/Application.vala:38 msgid "Display version number" msgstr "Zeige Versionsnummer" @@ -44,39 +44,39 @@ msgstr "Spixi , naxuroqa " msgid "Please let me add you to my contact list. 😁" msgstr "Bitte lass mich dich meiner Kontaktliste hinzufügen. 😁" -#: src/ui/add_contact_widget.ui:132 +#: src/ui/add_contact_widget.ui:133 msgid "Send a friend request" msgstr "Sende eine Freundschaftsanfrage" -#: src/ui/add_contact_widget.ui:166 +#: src/ui/add_contact_widget.ui:167 msgid "_ID:" msgstr "_ID:" -#: src/ui/add_contact_widget.ui:183 +#: src/ui/add_contact_widget.ui:184 msgid "Enter a Tox ID or URI here" msgstr "Gib eine Tox-ID oder eine Tox-URI ein" -#: src/ui/create_groupchat_widget.ui:122 src/ui/add_contact_widget.ui:187 +#: src/ui/create_groupchat_widget.ui:122 src/ui/add_contact_widget.ui:188 msgid "paste from clipboard" msgstr "Zwischenablage einfügen" -#: src/ui/add_contact_widget.ui:221 +#: src/ui/add_contact_widget.ui:222 msgid "Enter your friends Tox ID" msgstr "Gib die Tox ID deines Freunds / deiner Freundin ein" -#: src/ui/add_contact_widget.ui:253 +#: src/ui/add_contact_widget.ui:254 msgid "_Message:" msgstr "_Nachricht:" -#: src/ui/add_contact_widget.ui:279 +#: src/ui/add_contact_widget.ui:280 msgid "Send a custom message to be displayed to the friend you are adding" msgstr "Sende eine benutzerdefinierte Mitteilung, die dem Freund / der Freundin, den / die du hinzufügen möchtest, angezeigt wird" -#: src/ui/add_contact_widget.ui:301 +#: src/ui/add_contact_widget.ui:302 msgid "Send your friend a short message" msgstr "Sende deinem Freund / deiner Freundin eine kurze Nachricht" -#: src/ui/add_contact_widget.ui:332 +#: src/ui/add_contact_widget.ui:333 msgid "Send" msgstr "Abschicken" @@ -84,11 +84,11 @@ msgstr "Abschicken" msgid "_Preferences" msgstr "_Einstellungen" -#: src/ui/app_menu.ui:34 +#: src/view/ApplicationWindow.vala:204 src/ui/app_menu.ui:34 msgid "About" msgstr "Info" -#: src/ui/app_menu.ui:38 +#: src/ui/app_menu.ui:42 msgid "_Quit" msgstr "_Beenden" @@ -100,7 +100,7 @@ msgstr "Konferenz" msgid "Title:" msgstr "Titel:" -#: src/ui/conference_info_widget.ui:266 src/ui/user_info_widget.ui:443 +#: src/ui/conference_info_widget.ui:266 src/ui/user_info_widget.ui:624 #: src/ui/friend_info_widget.ui:634 msgid "Apply" msgstr "Anwenden" @@ -146,7 +146,7 @@ msgstr "_Titel:" msgid "T_ype:" msgstr "T_yp:" -#: src/ui/create_groupchat_widget.ui:282 +#: src/ui/login_widget.ui:570 src/ui/create_groupchat_widget.ui:282 msgid "Create" msgstr "Erstellen" @@ -174,15 +174,15 @@ msgstr "Setze einen Alias um deine Freunde schnell zu finden" msgid "Tox" msgstr "Tox" -#: src/ui/user_info_widget.ui:349 +#: src/ui/user_info_widget.ui:381 msgid "ID:" msgstr "ID:" -#: src/viewmodel/MessageViewModel.vala:79 +#: src/viewmodel/MessageViewModel.vala:90 msgid "Message sent ✓" msgstr "Nachricht gesendet ✓" -#: src/viewmodel/MessageViewModel.vala:79 +#: src/viewmodel/MessageViewModel.vala:93 msgid "Message received ✓" msgstr "Nachricht empfangen ✓" @@ -194,48 +194,35 @@ msgstr "Dieses Mitglied ist auch in deiner Kontaktliste" msgid "General" msgstr "Generell" -#: src/ui/settings_widget.ui:251 +#: src/ui/settings_widget.ui:291 msgid "Appearance" msgstr "Erscheinungsbild" -#: src/ui/settings_widget.ui:295 +#: src/ui/settings_widget.ui:335 msgid "Dark Theme" msgstr "Dunkles Thema" -#: src/ui/settings_widget.ui:983 +#: src/ui/settings_widget.ui:1025 msgid "Privacy" msgstr "Privatsphäre" -#: src/ui/settings_widget.ui:1122 -msgid "History" -msgstr "Verlauf" - -#: src/ui/settings_widget.ui:1254 -msgid "days" -msgstr "Tagen" - -#: src/ui/settings_widget.ui:1285 -msgid "Delete all previous conversations" -msgstr "Lösche alle bisherigen Konversationen" - -#: src/ui/settings_widget.ui:1706 +#: src/ui/settings_widget.ui:1681 msgid "Update bootstrap nodes" msgstr "Erneuere bootstrap Knoten" -#: src/ui/user_info_widget.ui:391 +#: src/ui/user_info_widget.ui:349 msgid "Copy to clipboard" msgstr "Kopiere in die Zwischenablage" -#: src/view/WelcomeWidget.vala:39 +#: src/view/WelcomeWidget.vala:137 msgid "Chat with your friends and family without anyone else listening in." msgstr "Plaudere mit deinen Freunden und Familie ohne dass jemand mithört." -#: src/ui/welcome_widget.ui:127 +#: src/ui/welcome_widget.ui:123 msgid "Learn more" msgstr "Erfahre mehr" -#: src/core/Message.vala:80 src/viewmodel/MessageViewModel.vala:80 -#: src/tox/ConferenceMessage.vala:60 +#: src/tox/ToxMessage.vala:53 src/tox/ConferenceMessage.vala:57 msgid "me" msgstr "Ich" @@ -243,14 +230,6 @@ msgstr "Ich" msgid "Choose an avatar" msgstr "Wähle einen Avatar aus" -#: src/ui/friend_request_widget.ui:156 -msgid "Reject request" -msgstr "Anfrage ablehnen" - -#: src/ui/friend_request_widget.ui:132 -msgid "Accept request" -msgstr "Anfrage annehmen" - #: src/core/NotificationListener.vala:139 msgid "New file from %s" msgstr "Neue Datei von %s" @@ -264,17 +243,17 @@ msgid "Offline" msgstr "Offline" #: src/viewmodel/ContactListEntryViewModel.vala:75 -#: src/ui/user_status_menu.ui:34 +#: src/view/ApplicationWindow.vala:198 src/ui/user_status_menu.ui:34 msgid "Away" msgstr "Abwesend" #: src/viewmodel/ContactListEntryViewModel.vala:77 -#: src/ui/user_status_menu.ui:40 +#: src/view/ApplicationWindow.vala:199 src/ui/user_status_menu.ui:40 msgid "Busy" msgstr "Beschäftigt" #: src/viewmodel/ContactListEntryViewModel.vala:79 -#: src/ui/user_status_menu.ui:28 +#: src/view/ApplicationWindow.vala:197 src/ui/user_status_menu.ui:28 msgid "Online" msgstr "Online" @@ -290,15 +269,15 @@ msgstr "Gestern um %s" msgid "Unnamed conference %u" msgstr "Unbenannte Konferenz %u" -#: src/tox/ToxSession.vala:650 +#: src/tox/ToxSession.vala:645 msgid "Address must consist of 76 hexadecimal characters" msgstr "Addresse muss aus 76 hexadezimalen Zeichen bestehen" -#: src/view/ConversationWindow.vala:231 src/view/FileTransferEntry.vala:86 +#: src/view/ConversationWindow.vala:234 src/view/FileTransferEntry.vala:86 msgid "_Cancel" msgstr "Abbre_chen" -#: src/view/ConversationWindow.vala:230 +#: src/view/ConversationWindow.vala:233 msgid "_Open" msgstr "Öffnen" @@ -342,96 +321,88 @@ msgstr "Sprache" msgid "That's you" msgstr "Das bist du" -#: src/ui/welcome_widget.ui:173 +#: src/ui/welcome_widget.ui:169 msgid "Get involved" msgstr "Mitmachen" -#: src/ui/settings_widget.ui:158 +#: src/ui/settings_widget.ui:198 msgid "Proxy" msgstr "Proxy" -#: src/ui/settings_widget.ui:308 +#: src/ui/settings_widget.ui:348 msgid "Use a dark variant of the theme" msgstr "Benutze eine dunkle Variante des Themas" -#: src/ui/settings_widget.ui:364 +#: src/ui/settings_widget.ui:404 msgid "Animations" msgstr "Animationen" -#: src/ui/conference_info_widget.ui:196 src/ui/settings_widget.ui:598 +#: src/ui/conference_info_widget.ui:196 src/ui/settings_widget.ui:638 #: src/ui/friend_info_widget.ui:291 msgid "Notifications" msgstr "Benachrichtigungen" -#: src/ui/settings_widget.ui:1184 -msgid "Keep forever" -msgstr "Behalte für immer" - -#: src/ui/settings_widget.ui:1220 -msgid "Remove older than" -msgstr "Lösche älter als" - -#: src/ui/settings_widget.ui:1386 +#: src/ui/settings_widget.ui:1281 msgid "Connection" msgstr "Verbindung" -#: src/ui/settings_widget.ui:1428 +#: src/ui/settings_widget.ui:1323 msgid "UDP" msgstr "UDP" -#: src/ui/settings_widget.ui:1497 +#: src/ui/settings_widget.ui:1392 msgid "IPv6" msgstr "IPv6" -#: src/ui/settings_widget.ui:1510 +#: src/ui/settings_widget.ui:1405 msgid "Allow both IPv4 and IPv6 communication" msgstr "Erlaube IPv4 und IPv6 Verbindungen" -#: src/ui/settings_widget.ui:1566 +#: src/ui/settings_widget.ui:1461 msgid "Local discovery" msgstr "Lokale Erkennung" -#: src/ui/settings_widget.ui:1579 +#: src/ui/settings_widget.ui:1474 msgid "Look for peers on the local network" msgstr "Suche nach Mitgliedern im lokalen Netzwerk" -#: src/ui/settings_widget.ui:1635 +#: src/ui/settings_widget.ui:1530 msgid "Hole punching" msgstr "Hole punching" -#: src/ui/settings_widget.ui:1648 +#: src/ui/settings_widget.ui:1543 msgid "Enable UDP hole punching" msgstr "Aktiviere UDP hole punching" -#: src/ui/settings_widget.ui:1742 +#: src/ui/settings_widget.ui:1716 msgid "Bootstrap nodes" msgstr "Bootstrap Knoten" -#: src/ui/settings_widget.ui:1878 +#: src/ui/settings_widget.ui:1852 msgid "Proxy" msgstr "Proxy" -#: src/ui/settings_widget.ui:1945 +#: src/ui/settings_widget.ui:1920 msgid "System settings" msgstr "Systemeinstellungen" -#: src/ui/settings_widget.ui:1958 +#: src/ui/settings_widget.ui:1933 msgid "Use your systems proxy settings" msgstr "Benutze die Proxy Einstellungen deines Systems" -#: src/ui/settings_widget.ui:2012 +#: src/ui/settings_widget.ui:1987 msgid "Manual settings" msgstr "Manuelle Einstellung" -#: src/ui/settings_widget.ui:2025 +#: src/ui/settings_widget.ui:2000 msgid "Use custom proxy settings" msgstr "Setze Benutzerdefinierte Proxy Einstellungen" -#: src/ui/settings_widget.ui:2066 +#: src/ui/settings_widget.ui:2041 msgid "Host" msgstr "Host" -#: src/ui/settings_widget.ui:2106 +#: src/ui/settings_widget.ui:2084 msgid "Set a SOCKS5 proxy to connect to" msgstr "Setze einen SOCKS5 Proxyserver" @@ -525,23 +496,15 @@ msgstr "Verlasse Konferenz" msgid "Notifications have been globally disabled" msgstr "Benachrichtigungen sind global deaktiviert" -#: src/ui/conference_invite_entry.ui:108 -msgid "Accept invite" -msgstr "Einladung annehmen" - -#: src/ui/conference_invite_entry.ui:132 -msgid "Reject invite" -msgstr "Einladung ablehnen" - #: src/ui/add_contact_widget.ui:52 msgid "No new friend requests" msgstr "Keine neuen Freundschaftsanfragen" -#: src/ui/contact_list_widget.ui:56 src/ui/add_contact_widget.ui:349 +#: src/ui/contact_list_widget.ui:56 src/ui/add_contact_widget.ui:350 msgid "Add a friend" msgstr "Füge einen Freund hinzu" -#: src/ui/add_contact_widget.ui:362 src/ui/add_contact_widget.ui:396 +#: src/ui/add_contact_widget.ui:363 src/ui/add_contact_widget.ui:397 msgid "Friend requests" msgstr "Freundschaftsanfragen" @@ -549,15 +512,15 @@ msgstr "Freundschaftsanfragen" msgid "Status message:" msgstr "Statusmeldung:" -#: src/ui/settings_widget.ui:1441 +#: src/ui/settings_widget.ui:1336 msgid "Use UDP communication when available" msgstr "Benutze wenn möglich UDP-Verbindungen" -#: src/view/WelcomeWidget.vala:35 +#: src/view/WelcomeWidget.vala:129 msgid "A new kind of instant messaging" msgstr "Eine neue Art des Instant Messagings" -#: src/core/Application.vala:70 +#: src/core/Application.vala:73 msgid "Settings" msgstr "Einstellungen" @@ -577,11 +540,11 @@ msgstr "Ändere deinen Status" msgid "Public key:" msgstr "Öffentlicher Schlüssel:" -#: src/view/ConversationWindow.vala:227 +#: src/view/ConversationWindow.vala:230 msgid "Choose a file to send" msgstr "Wähle eine Datei zum Senden aus" -#: src/view/UserInfoWidget.vala:56 +#: src/view/UserInfoWidget.vala:82 msgid "Images" msgstr "Bilder" @@ -625,7 +588,7 @@ msgstr "War noch nie online" msgid "Last seen: %s" msgstr "Zuletzt gesehen: %s" -#: src/view/ConversationWindow.vala:124 +#: src/view/ConversationWindow.vala:127 msgid "%s is typing…" msgstr "%s tippt gerade …" @@ -637,7 +600,7 @@ msgstr "Screenshot anhängen…" msgid "Attach file…" msgstr "Datei anhängen…" -#: src/view/AboutDialog.vala:54 data/chat.tox.venom.desktop.in:4 +#: src/view/AboutDialog.vala:54 data/com.github.naxuroqa.venom.desktop.in:4 msgid "A modern Tox client for the Linux desktop" msgstr "Ein moderner Tox Client für den Linux Desktop" @@ -645,23 +608,19 @@ msgstr "Ein moderner Tox Client für den Linux Desktop" msgid "Copyright © 2013-2018 Venom authors and contributors" msgstr "Copyright © 2013-2018 Venom Authoren und Mitwirkende" -#: data/chat.tox.venom.desktop.in:3 +#: data/com.github.naxuroqa.venom.desktop.in:3 msgid "Venom" msgstr "Venom" -#: data/chat.tox.venom.desktop.in:5 +#: data/com.github.naxuroqa.venom.desktop.in:5 msgid "tox;instant messaging;video chat;" msgstr "tox;instant messaging;video chat;" -#: data/chat.tox.venom.desktop.in:7 -msgid "chat.tox.venom-symbolic" -msgstr "chat.tox.venom-symbolic" - #: src/view/ConferenceInviteEntry.vala:48 msgid "Invite from %s" msgstr "Einladung von %s" -#: src/ui/settings_widget.ui:433 +#: src/ui/settings_widget.ui:473 msgid "Small contacts" msgstr "Kleine Kontakte" @@ -677,75 +636,75 @@ msgstr "Konferenzeinladung" msgid "%s invites you to a conference" msgstr "%s lädt dich zu einer Konferenz ein" -#: src/core/Application.vala:77 +#: src/ui/error_widget.ui:164 msgid "Log" msgstr "Protokoll" -#: src/ui/settings_widget.ui:377 +#: src/ui/settings_widget.ui:417 msgid "Turn on animated transitions" msgstr "Schalte animierte Übergänge ein" -#: src/ui/settings_widget.ui:446 +#: src/ui/settings_widget.ui:486 msgid "Show contacts in a compact format" msgstr "Zeige Kontakte in einem kompaten Format" -#: src/ui/settings_widget.ui:502 +#: src/ui/settings_widget.ui:542 msgid "Spellcheck" msgstr "Rechtschreibprüfung" -#: src/ui/settings_widget.ui:515 +#: src/ui/settings_widget.ui:555 msgid "Check your spelling while you type" msgstr "Prüfe deine Rechtschreibung während du tippst" -#: src/ui/settings_widget.ui:658 +#: src/ui/settings_widget.ui:699 msgid "Sounds" msgstr "Töne" -#: src/ui/settings_widget.ui:671 +#: src/ui/settings_widget.ui:712 msgid "Play sounds on new notifications" msgstr "Spiele einen Ton bei neuen Benachrichtigungen" -#: src/ui/settings_widget.ui:727 +#: src/ui/settings_widget.ui:768 msgid "While Busy" msgstr "Wenn beschäftigt" -#: src/ui/settings_widget.ui:740 +#: src/ui/settings_widget.ui:781 msgid "Receive notifications even while you are busy" msgstr "Erhalte Benachrichtigungen auch während du beschäftigt bist" -#: src/ui/settings_widget.ui:825 +#: src/ui/settings_widget.ui:866 msgid "Tray icon" msgstr "Taskleistensymbol" -#: src/ui/settings_widget.ui:885 +#: src/ui/settings_widget.ui:927 msgid "Minimize" msgstr "Minimiere" -#: src/ui/settings_widget.ui:898 +#: src/ui/settings_widget.ui:940 msgid "Minimize to tray instead of close" msgstr "Minimiere in die Taskleiste anstatt zu schließen" -#: src/ui/settings_widget.ui:1026 +#: src/ui/settings_widget.ui:1068 msgid "Send typing status" msgstr "Sende Tippstatus" -#: src/ui/settings_widget.ui:1039 +#: src/ui/settings_widget.ui:1081 msgid "Show others when you are typing" msgstr "Zeige anderen wenn du tippst" -#: src/ui/error_widget.ui:97 +#: src/ui/error_widget.ui:104 msgid "Oh no! Something broke!" msgstr "Oh nein! Da lief etwas schief!" -#: src/ui/error_widget.ui:112 +#: src/ui/error_widget.ui:119 msgid "Please check your settings and retry" msgstr "Bitte prüfe deine Einstellungen und versuche es erneut" -#: src/ui/error_widget.ui:133 +#: src/ui/error_widget.ui:140 msgid "Info" msgstr "Info" -#: src/ui/error_widget.ui:170 +#: src/ui/error_widget.ui:202 msgid "Retry" msgstr "Erneut versuchen" @@ -753,7 +712,7 @@ msgstr "Erneut versuchen" msgid "Mute conversation" msgstr "Stummschalten" -#: src/core/Application.vala:38 +#: src/core/Application.vala:39 msgid "Show preferences" msgstr "Öffne Einstellungen" @@ -769,39 +728,40 @@ msgstr "Verlasse Konferenz" msgid "Remove friend" msgstr "Entferne Freund" -#: src/tox/ConferenceMessage.vala:67 +#: src/tox/ConferenceMessage.vala:64 msgid "%s in %s" msgstr "%s in %s" -#: src/view/WelcomeWidget.vala:40 +#: src/view/WelcomeWidget.vala:138 msgid "Now with 50% less bugs." msgstr "Jetzt mit 50% weniger bugs." -#: src/view/WelcomeWidget.vala:41 +#: src/view/WelcomeWidget.vala:139 msgid "Generating witty dialog…" msgstr "Generiere lustige Nachrichten…" -#: src/view/WelcomeWidget.vala:42 +#: src/view/WelcomeWidget.vala:140 msgid "Thank you for using Venom." msgstr "Danke, dass du Venom verwendest." -#: src/view/WelcomeWidget.vala:43 +#: src/view/WelcomeWidget.vala:141 msgid "Always think positive." msgstr "Denke immer positiv." -#: src/view/WelcomeWidget.vala:44 +#: src/view/WelcomeWidget.vala:142 msgid "Have a good day and stay safe." msgstr "Hab einen guten Tag und bleib auf der sicheren Seite." -#: src/view/WelcomeWidget.vala:45 +#: src/view/WelcomeWidget.vala:143 msgid "You can do it. ― Coffee" msgstr "Du kannst das. ― Kaffee" -#: src/view/WelcomeWidget.vala:46 +#: src/view/WelcomeWidget.vala:144 msgid "Life moves pretty fast. If you don’t stop and look around once in a while, you could miss it. ― Ferris Bueller" msgstr "Das Leben bewegt sich sehr, sehr schnell. Wenn du nicht gelegentlich anhältst und dich umschaust, könntest du es verpassen. ― Ferris Bueller" -#: src/view/SettingsWidget.vala:78 data/chat.tox.venom.desktop.in:17 +#: src/view/ApplicationWindow.vala:203 src/view/SettingsWidget.vala:77 +#: data/com.github.naxuroqa.venom.desktop.in:17 msgid "Preferences" msgstr "Einstellungen" @@ -817,3 +777,151 @@ msgstr "Alle ablehnen" msgid "Open preferences" msgstr "Öffne Einstellungen" +#: data/com.github.naxuroqa.venom.desktop.in:7 +msgid "com.github.naxuroqa.venom-symbolic" +msgstr "com.github.naxuroqa.venom-symbolic" + +#: src/viewmodel/MessageViewModel.vala:66 +msgid "Notice" +msgstr "Systemnotiz" + +#: src/tox/ToxAdapterFriendListener.vala:29 +msgid "%s has been removed from your contact list." +msgstr "%s wurde von deiner Kontaktliste entfernt." + +#: src/tox/ToxAdapterFriendListener.vala:30 +msgid "Undo" +msgstr "Rückgängig" + +#: src/view/ApplicationWindow.vala:194 +msgid "Hide" +msgstr "Verstecken" + +#: src/view/ApplicationWindow.vala:194 +msgid "Show" +msgstr "Zeigen" + +#: src/view/ApplicationWindow.vala:200 +msgid "Status" +msgstr "Status" + +#: src/view/ApplicationWindow.vala:209 +msgid "Quit" +msgstr "Beenden" + +#: src/view/LoginWidget.vala:167 +msgid "Wrong password" +msgstr "Falsches Passwort" + +#: src/view/LoginWidget.vala:173 +msgid "Username can not be empty" +msgstr "Benutzername darf nicht leer sein" + +#: src/view/LoginWidget.vala:175 +msgid "Username is already taken" +msgstr "Benutzername ist schon vergeben" + +#: src/view/LoginWidget.vala:189 +msgid "Password must be at least 6 characters long" +msgstr "Passwort muss mindestens 6 Zeichen lang sein" + +#: src/view/LoginWidget.vala:202 +msgid "Passwords must match" +msgstr "Passwörter müssen übereinstimmen" + +#: src/view/LoginWidget.vala:226 +msgid "Creating profile failed: " +msgstr "Profilerstellung fehlgeschlagen: " + +#: src/view/LoginWidget.vala:249 +msgid "Profile is encrypted" +msgstr "Profil ist verschlüsselt" + +#: src/view/WelcomeWidget.vala:189 +msgid "Drink your milk for extra strong bones." +msgstr "Trinke deine Milch für extra starke Knochen." + +#: src/view/FriendRequestWidget.vala:44 +msgid "“%s”" +msgstr "\"%s\"" + +#: src/ui/login_widget.ui:132 +msgid "Login automatically" +msgstr "Automatisch anmelden" + +#: src/ui/login_widget.ui:152 src/ui/login_widget.ui:471 +msgid "Password" +msgstr "Passwort" + +#: src/ui/login_widget.ui:169 src/ui/login_widget.ui:330 +msgid "Login" +msgstr "Einloggen" + +#: src/ui/login_widget.ui:205 +msgid "Other profiles" +msgstr "Andere Profile" + +#: src/ui/login_widget.ui:267 +msgid "Import profile…" +msgstr "Importiere Profil…" + +#: src/ui/login_widget.ui:395 +msgid "New profile" +msgstr "Neues Profil" + +#: src/ui/login_widget.ui:417 +msgid "Username" +msgstr "Benutzername" + +#: src/ui/login_widget.ui:526 +msgid "Confirm Password" +msgstr "Bestätige Passwort" + +#: src/ui/login_widget.ui:620 +msgid "Create profile" +msgstr "Erstelle Profil" + +#: src/ui/friend_request_widget.ui:66 src/ui/conference_invite_entry.ui:107 +msgid "Accept" +msgstr "Annehmen" + +#: src/ui/friend_request_widget.ui:83 src/ui/conference_invite_entry.ui:124 +msgid "Deny" +msgstr "Ablehnen" + +#: src/ui/user_info_widget.ui:462 +msgid "Advanced" +msgstr "Erweitert" + +#: src/ui/user_info_widget.ui:510 +msgid "Tox nospam" +msgstr "Tox nospam" + +#: src/ui/user_info_widget.ui:532 +msgid "Generate new random nospam" +msgstr "Erzeuge neues zufälliges nospam" + +#: src/ui/user_info_widget.ui:546 +msgid "Set this nospam" +msgstr "Setze dieses nospam" + +#: src/ui/user_info_widget.ui:578 +msgid "Previous nospams" +msgstr "Vorherige nospams" + +#: src/ui/settings_widget.ui:158 +msgid "Bootstrap nodes" +msgstr "Startknoten" + +#: src/ui/settings_widget.ui:1137 +msgid "Keep History" +msgstr "Behalte Verlauf" + +#: src/ui/settings_widget.ui:1150 +msgid "Store your sent and received messages" +msgstr "Speichere deine gesendeten und empfangenen Nachrichten" + +#: src/view/ApplicationWindow.vala:208 src/ui/app_menu.ui:38 +msgid "Logout" +msgstr "Ausloggen" + diff --git a/po/pt_br.po b/po/pt_BR.po similarity index 73% rename from po/pt_br.po rename to po/pt_BR.po index 431fdde..587f726 100644 --- a/po/pt_br.po +++ b/po/pt_BR.po @@ -8,15 +8,15 @@ msgstr "" "Language: pt-br\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" -#: src/core/Application.vala:36 +#: src/core/Application.vala:37 msgid "Set level of messages to log" msgstr "" -#: src/core/Application.vala:36 +#: src/core/Application.vala:37 msgid "" msgstr "" -#: src/core/Application.vala:37 +#: src/core/Application.vala:38 msgid "Display version number" msgstr "Mostrar número da versão" @@ -36,6 +36,7 @@ msgstr "%u Pares online" msgid "Packagers" msgstr "" +#. Please add yourself to the list after translating #: src/view/AboutDialog.vala:58 msgid "translator-credits" msgstr "" @@ -44,39 +45,39 @@ msgstr "" msgid "Please let me add you to my contact list. 😁" msgstr "Por favor deixe eu te adicionar na minha lista de contatos. 😁" -#: src/ui/add_contact_widget.ui:132 +#: src/ui/add_contact_widget.ui:133 msgid "Send a friend request" msgstr "Envie uma solicitação de amizade" -#: src/ui/add_contact_widget.ui:166 +#: src/ui/add_contact_widget.ui:167 msgid "_ID:" msgstr "_ID:" -#: src/ui/add_contact_widget.ui:183 +#: src/ui/add_contact_widget.ui:184 msgid "Enter a Tox ID or URI here" msgstr "Entre um Tox ID ou URI aqui" -#: src/ui/create_groupchat_widget.ui:122 src/ui/add_contact_widget.ui:187 +#: src/ui/create_groupchat_widget.ui:122 src/ui/add_contact_widget.ui:188 msgid "paste from clipboard" msgstr "colar da área de transferência" -#: src/ui/add_contact_widget.ui:221 +#: src/ui/add_contact_widget.ui:222 msgid "Enter your friends Tox ID" msgstr "Entre o Tox ID do seu amigo" -#: src/ui/add_contact_widget.ui:253 +#: src/ui/add_contact_widget.ui:254 msgid "_Message:" msgstr "_Mensagem:" -#: src/ui/add_contact_widget.ui:279 +#: src/ui/add_contact_widget.ui:280 msgid "Send a custom message to be displayed to the friend you are adding" msgstr "Envie uma mensagem customizada para ser mostrada ao amigo que você está adicionando" -#: src/ui/add_contact_widget.ui:301 +#: src/ui/add_contact_widget.ui:302 msgid "Send your friend a short message" msgstr "Mande ao seu amigo uma mensagem curta" -#: src/ui/add_contact_widget.ui:332 +#: src/ui/add_contact_widget.ui:333 msgid "Send" msgstr "Enviar" @@ -84,11 +85,11 @@ msgstr "Enviar" msgid "_Preferences" msgstr "_Preferências" -#: src/ui/app_menu.ui:34 +#: src/view/ApplicationWindow.vala:204 src/ui/app_menu.ui:34 msgid "About" msgstr "Sobre" -#: src/ui/app_menu.ui:38 +#: src/ui/app_menu.ui:42 msgid "_Quit" msgstr "_Sair" @@ -100,7 +101,7 @@ msgstr "Conferência" msgid "Title:" msgstr "Título:" -#: src/ui/conference_info_widget.ui:266 src/ui/user_info_widget.ui:443 +#: src/ui/conference_info_widget.ui:266 src/ui/user_info_widget.ui:624 #: src/ui/friend_info_widget.ui:634 msgid "Apply" msgstr "Aplicar" @@ -146,7 +147,7 @@ msgstr "_Título" msgid "T_ype:" msgstr "" -#: src/ui/create_groupchat_widget.ui:282 +#: src/ui/login_widget.ui:570 src/ui/create_groupchat_widget.ui:282 msgid "Create" msgstr "Criar" @@ -174,15 +175,15 @@ msgstr "Defina um apelido personalizado para achar seus amigos rapidament msgid "Tox" msgstr "Tox" -#: src/ui/user_info_widget.ui:349 +#: src/ui/user_info_widget.ui:381 msgid "ID:" msgstr "ID:" -#: src/viewmodel/MessageViewModel.vala:79 +#: src/viewmodel/MessageViewModel.vala:90 msgid "Message sent ✓" msgstr "Mensagem enviada ✓" -#: src/viewmodel/MessageViewModel.vala:79 +#: src/viewmodel/MessageViewModel.vala:93 msgid "Message received ✓" msgstr "Mensagem recebida ✓" @@ -194,48 +195,35 @@ msgstr "Esse par também está na sua lista de amigos" msgid "General" msgstr "Geral" -#: src/ui/settings_widget.ui:251 +#: src/ui/settings_widget.ui:291 msgid "Appearance" msgstr "Aparência" -#: src/ui/settings_widget.ui:295 +#: src/ui/settings_widget.ui:335 msgid "Dark Theme" msgstr "Tema escuro" -#: src/ui/settings_widget.ui:983 +#: src/ui/settings_widget.ui:1025 msgid "Privacy" msgstr "Privacidade" -#: src/ui/settings_widget.ui:1122 -msgid "History" -msgstr "Histórico" - -#: src/ui/settings_widget.ui:1254 -msgid "days" -msgstr "dias" - -#: src/ui/settings_widget.ui:1285 -msgid "Delete all previous conversations" -msgstr "Deletar todas conversas anteriores" - -#: src/ui/settings_widget.ui:1706 +#: src/ui/settings_widget.ui:1681 msgid "Update bootstrap nodes" msgstr "Atualizar bootstrap nodes" -#: src/ui/user_info_widget.ui:391 +#: src/ui/user_info_widget.ui:349 msgid "Copy to clipboard" msgstr "Copiar para área de transferência" -#: src/view/WelcomeWidget.vala:39 +#: src/view/WelcomeWidget.vala:137 msgid "Chat with your friends and family without anyone else listening in." msgstr "Converse com seus amigos e familiares sem mais ninguém escutando." -#: src/ui/welcome_widget.ui:127 +#: src/ui/welcome_widget.ui:123 msgid "Learn more" msgstr "Aprenda mais" -#: src/core/Message.vala:80 src/viewmodel/MessageViewModel.vala:80 -#: src/tox/ConferenceMessage.vala:60 +#: src/tox/ToxMessage.vala:53 src/tox/ConferenceMessage.vala:57 msgid "me" msgstr "eu" @@ -243,14 +231,6 @@ msgstr "eu" msgid "Choose an avatar" msgstr "Escolha um avatar" -#: src/ui/friend_request_widget.ui:156 -msgid "Reject request" -msgstr "Rejeitar pedido" - -#: src/ui/friend_request_widget.ui:132 -msgid "Accept request" -msgstr "Aceitar pedido" - #: src/core/NotificationListener.vala:139 msgid "New file from %s" msgstr "Novo arquivo de %s" @@ -264,17 +244,17 @@ msgid "Offline" msgstr "Offline" #: src/viewmodel/ContactListEntryViewModel.vala:75 -#: src/ui/user_status_menu.ui:34 +#: src/view/ApplicationWindow.vala:198 src/ui/user_status_menu.ui:34 msgid "Away" msgstr "Ausente" #: src/viewmodel/ContactListEntryViewModel.vala:77 -#: src/ui/user_status_menu.ui:40 +#: src/view/ApplicationWindow.vala:199 src/ui/user_status_menu.ui:40 msgid "Busy" msgstr "Ocupado" #: src/viewmodel/ContactListEntryViewModel.vala:79 -#: src/ui/user_status_menu.ui:28 +#: src/view/ApplicationWindow.vala:197 src/ui/user_status_menu.ui:28 msgid "Online" msgstr "Online" @@ -290,15 +270,15 @@ msgstr "Ontem às %s" msgid "Unnamed conference %u" msgstr "Conferência sem nome %u" -#: src/tox/ToxSession.vala:650 +#: src/tox/ToxSession.vala:645 msgid "Address must consist of 76 hexadecimal characters" msgstr "Endereço deve consistir de 76 caracteres hexadecimais" -#: src/view/ConversationWindow.vala:231 src/view/FileTransferEntry.vala:86 +#: src/view/ConversationWindow.vala:234 src/view/FileTransferEntry.vala:86 msgid "_Cancel" msgstr "_Cancelar" -#: src/view/ConversationWindow.vala:230 +#: src/view/ConversationWindow.vala:233 msgid "_Open" msgstr "_Abrir" @@ -342,96 +322,88 @@ msgstr "Fala" msgid "That's you" msgstr "Esse é você" -#: src/ui/welcome_widget.ui:173 +#: src/ui/welcome_widget.ui:169 msgid "Get involved" msgstr "Se involva" -#: src/ui/settings_widget.ui:158 +#: src/ui/settings_widget.ui:198 msgid "Proxy" msgstr "Proxy" -#: src/ui/settings_widget.ui:308 +#: src/ui/settings_widget.ui:348 msgid "Use a dark variant of the theme" msgstr "Use uma variação escura do tema" -#: src/ui/settings_widget.ui:364 +#: src/ui/settings_widget.ui:404 msgid "Animations" msgstr "Animações" -#: src/ui/conference_info_widget.ui:196 src/ui/settings_widget.ui:598 +#: src/ui/conference_info_widget.ui:196 src/ui/settings_widget.ui:638 #: src/ui/friend_info_widget.ui:291 msgid "Notifications" msgstr "Notificações" -#: src/ui/settings_widget.ui:1184 -msgid "Keep forever" -msgstr "Manter para sempre" - -#: src/ui/settings_widget.ui:1220 -msgid "Remove older than" -msgstr "Remover mais antigo que" - -#: src/ui/settings_widget.ui:1386 +#: src/ui/settings_widget.ui:1281 msgid "Connection" msgstr "Conexão" -#: src/ui/settings_widget.ui:1428 +#: src/ui/settings_widget.ui:1323 msgid "UDP" msgstr "UDP" -#: src/ui/settings_widget.ui:1497 +#: src/ui/settings_widget.ui:1392 msgid "IPv6" msgstr "IPv6" -#: src/ui/settings_widget.ui:1510 +#: src/ui/settings_widget.ui:1405 msgid "Allow both IPv4 and IPv6 communication" msgstr "Permitir comunicação IPv4 e IPv6" -#: src/ui/settings_widget.ui:1566 +#: src/ui/settings_widget.ui:1461 msgid "Local discovery" msgstr "Descoberta local" -#: src/ui/settings_widget.ui:1579 +#: src/ui/settings_widget.ui:1474 msgid "Look for peers on the local network" msgstr "Procurar por pares na rede local" -#: src/ui/settings_widget.ui:1635 +#: src/ui/settings_widget.ui:1530 msgid "Hole punching" msgstr "Hole punching" -#: src/ui/settings_widget.ui:1648 +#: src/ui/settings_widget.ui:1543 msgid "Enable UDP hole punching" msgstr "Habilitar UDP hole punching" -#: src/ui/settings_widget.ui:1742 +#: src/ui/settings_widget.ui:1716 msgid "Bootstrap nodes" msgstr "Bootstrap nodes" -#: src/ui/settings_widget.ui:1878 +#: src/ui/settings_widget.ui:1852 msgid "Proxy" msgstr "Proxy" -#: src/ui/settings_widget.ui:1945 +#: src/ui/settings_widget.ui:1920 msgid "System settings" msgstr "Configurações do sistema" -#: src/ui/settings_widget.ui:1958 +#: src/ui/settings_widget.ui:1933 msgid "Use your systems proxy settings" msgstr "Usar configurações de proxy do sistema" -#: src/ui/settings_widget.ui:2012 +#: src/ui/settings_widget.ui:1987 msgid "Manual settings" msgstr "Configuração manual" -#: src/ui/settings_widget.ui:2025 +#: src/ui/settings_widget.ui:2000 msgid "Use custom proxy settings" msgstr "Use configurações customizadas para proxy" -#: src/ui/settings_widget.ui:2066 +#: src/ui/settings_widget.ui:2041 msgid "Host" msgstr "Host" -#: src/ui/settings_widget.ui:2106 +#: src/ui/settings_widget.ui:2084 msgid "Set a SOCKS5 proxy to connect to" msgstr "Defina um proxy SOCKS5 para se conectar" @@ -525,23 +497,15 @@ msgstr "Abandonar conferência" msgid "Notifications have been globally disabled" msgstr "Notificações foram desativadas globalmente" -#: src/ui/conference_invite_entry.ui:108 -msgid "Accept invite" -msgstr "Aceitar convite" - -#: src/ui/conference_invite_entry.ui:132 -msgid "Reject invite" -msgstr "Rejeitar convite" - #: src/ui/add_contact_widget.ui:52 msgid "No new friend requests" msgstr "Nenhum novo pedido de amizade" -#: src/ui/contact_list_widget.ui:56 src/ui/add_contact_widget.ui:349 +#: src/ui/contact_list_widget.ui:56 src/ui/add_contact_widget.ui:350 msgid "Add a friend" msgstr "Adicionar um amigo" -#: src/ui/add_contact_widget.ui:362 src/ui/add_contact_widget.ui:396 +#: src/ui/add_contact_widget.ui:363 src/ui/add_contact_widget.ui:397 msgid "Friend requests" msgstr "Pedidos de amizade" @@ -549,15 +513,15 @@ msgstr "Pedidos de amizade" msgid "Status message:" msgstr "Mensagem de status:" -#: src/ui/settings_widget.ui:1441 +#: src/ui/settings_widget.ui:1336 msgid "Use UDP communication when available" msgstr "Use a comunicação UDP quando disponível" -#: src/view/WelcomeWidget.vala:35 +#: src/view/WelcomeWidget.vala:129 msgid "A new kind of instant messaging" msgstr "Um novo tipo de mensagem instantânea" -#: src/core/Application.vala:70 +#: src/core/Application.vala:73 msgid "Settings" msgstr "Configurações" @@ -577,11 +541,11 @@ msgstr "Mude seu status:" msgid "Public key:" msgstr "Chave pública:" -#: src/view/ConversationWindow.vala:227 +#: src/view/ConversationWindow.vala:230 msgid "Choose a file to send" msgstr "Escolha um arquivo para enviar" -#: src/view/UserInfoWidget.vala:56 +#: src/view/UserInfoWidget.vala:82 msgid "Images" msgstr "Imagens" @@ -625,7 +589,7 @@ msgstr "Nunca visto online" msgid "Last seen: %s" msgstr "Visto por último: %s" -#: src/view/ConversationWindow.vala:124 +#: src/view/ConversationWindow.vala:127 msgid "%s is typing…" msgstr "%s está digitando..." @@ -637,7 +601,7 @@ msgstr "Anexar captura de tela..." msgid "Attach file…" msgstr "Anexar arquivo..." -#: src/view/AboutDialog.vala:54 data/chat.tox.venom.desktop.in:4 +#: src/view/AboutDialog.vala:54 data/com.github.naxuroqa.venom.desktop.in:4 msgid "A modern Tox client for the Linux desktop" msgstr "Um cliente Tox moderno para o desktop Linux" @@ -645,23 +609,19 @@ msgstr "Um cliente Tox moderno para o desktop Linux" msgid "Copyright © 2013-2018 Venom authors and contributors" msgstr "" -#: data/chat.tox.venom.desktop.in:3 +#: data/com.github.naxuroqa.venom.desktop.in:3 msgid "Venom" msgstr "Venom" -#: data/chat.tox.venom.desktop.in:5 +#: data/com.github.naxuroqa.venom.desktop.in:5 msgid "tox;instant messaging;video chat;" msgstr "" -#: data/chat.tox.venom.desktop.in:7 -msgid "chat.tox.venom-symbolic" -msgstr "" - #: src/view/ConferenceInviteEntry.vala:48 msgid "Invite from %s" msgstr "Convite de %s" -#: src/ui/settings_widget.ui:433 +#: src/ui/settings_widget.ui:473 msgid "Small contacts" msgstr "Contatos pequenos" @@ -677,75 +637,75 @@ msgstr "Convite para conferência" msgid "%s invites you to a conference" msgstr "%s te convidou para uma conferência" -#: src/core/Application.vala:77 +#: src/ui/error_widget.ui:164 msgid "Log" msgstr "Log" -#: src/ui/settings_widget.ui:377 +#: src/ui/settings_widget.ui:417 msgid "Turn on animated transitions" msgstr "Ativar transições animadas" -#: src/ui/settings_widget.ui:446 +#: src/ui/settings_widget.ui:486 msgid "Show contacts in a compact format" msgstr "Mostrar contatos de forma compacta" -#: src/ui/settings_widget.ui:502 +#: src/ui/settings_widget.ui:542 msgid "Spellcheck" msgstr "Verificação ortográfica" -#: src/ui/settings_widget.ui:515 +#: src/ui/settings_widget.ui:555 msgid "Check your spelling while you type" msgstr "Verifique sua ortografia enquanto digita" -#: src/ui/settings_widget.ui:658 +#: src/ui/settings_widget.ui:699 msgid "Sounds" msgstr "Sons" -#: src/ui/settings_widget.ui:671 +#: src/ui/settings_widget.ui:712 msgid "Play sounds on new notifications" msgstr "Tocar sons em novas notificações" -#: src/ui/settings_widget.ui:727 +#: src/ui/settings_widget.ui:768 msgid "While Busy" msgstr "Enquanto Ocupado" -#: src/ui/settings_widget.ui:740 +#: src/ui/settings_widget.ui:781 msgid "Receive notifications even while you are busy" msgstr "Receber notificações até mesmo quando você está ocupado" -#: src/ui/settings_widget.ui:825 +#: src/ui/settings_widget.ui:866 msgid "Tray icon" msgstr "Ícone de bandeja" -#: src/ui/settings_widget.ui:885 +#: src/ui/settings_widget.ui:927 msgid "Minimize" msgstr "Minimizar" -#: src/ui/settings_widget.ui:898 +#: src/ui/settings_widget.ui:940 msgid "Minimize to tray instead of close" msgstr "Minimizar para bandeja ao fechar" -#: src/ui/settings_widget.ui:1026 +#: src/ui/settings_widget.ui:1068 msgid "Send typing status" msgstr "Enviar status de digitação" -#: src/ui/settings_widget.ui:1039 +#: src/ui/settings_widget.ui:1081 msgid "Show others when you are typing" msgstr "Mostrar aos outros quando você está digitando" -#: src/ui/error_widget.ui:97 +#: src/ui/error_widget.ui:104 msgid "Oh no! Something broke!" msgstr "Oh não! Algo deu errado!" -#: src/ui/error_widget.ui:112 +#: src/ui/error_widget.ui:119 msgid "Please check your settings and retry" msgstr "Por favor verifique suas configurações e tente novamente" -#: src/ui/error_widget.ui:133 +#: src/ui/error_widget.ui:140 msgid "Info" msgstr "" -#: src/ui/error_widget.ui:170 +#: src/ui/error_widget.ui:202 msgid "Retry" msgstr "" @@ -753,7 +713,7 @@ msgstr "" msgid "Mute conversation" msgstr "" -#: src/core/Application.vala:38 +#: src/core/Application.vala:39 msgid "Show preferences" msgstr "" @@ -769,39 +729,40 @@ msgstr "" msgid "Remove friend" msgstr "" -#: src/tox/ConferenceMessage.vala:67 +#: src/tox/ConferenceMessage.vala:64 msgid "%s in %s" msgstr "" -#: src/view/WelcomeWidget.vala:40 +#: src/view/WelcomeWidget.vala:138 msgid "Now with 50% less bugs." msgstr "" -#: src/view/WelcomeWidget.vala:41 +#: src/view/WelcomeWidget.vala:139 msgid "Generating witty dialog…" msgstr "" -#: src/view/WelcomeWidget.vala:42 +#: src/view/WelcomeWidget.vala:140 msgid "Thank you for using Venom." msgstr "" -#: src/view/WelcomeWidget.vala:43 +#: src/view/WelcomeWidget.vala:141 msgid "Always think positive." msgstr "" -#: src/view/WelcomeWidget.vala:44 +#: src/view/WelcomeWidget.vala:142 msgid "Have a good day and stay safe." msgstr "" -#: src/view/WelcomeWidget.vala:45 +#: src/view/WelcomeWidget.vala:143 msgid "You can do it. ― Coffee" msgstr "" -#: src/view/WelcomeWidget.vala:46 +#: src/view/WelcomeWidget.vala:144 msgid "Life moves pretty fast. If you don’t stop and look around once in a while, you could miss it. ― Ferris Bueller" msgstr "" -#: src/view/SettingsWidget.vala:78 data/chat.tox.venom.desktop.in:17 +#: src/view/ApplicationWindow.vala:203 src/view/SettingsWidget.vala:77 +#: data/com.github.naxuroqa.venom.desktop.in:17 msgid "Preferences" msgstr "" @@ -817,3 +778,151 @@ msgstr "" msgid "Open preferences" msgstr "" +#: data/com.github.naxuroqa.venom.desktop.in:7 +msgid "com.github.naxuroqa.venom-symbolic" +msgstr "com.github.naxuroqa.venom-symbolic" + +#: src/viewmodel/MessageViewModel.vala:66 +msgid "Notice" +msgstr "" + +#: src/tox/ToxAdapterFriendListener.vala:29 +msgid "%s has been removed from your contact list." +msgstr "" + +#: src/tox/ToxAdapterFriendListener.vala:30 +msgid "Undo" +msgstr "" + +#: src/view/ApplicationWindow.vala:194 +msgid "Hide" +msgstr "" + +#: src/view/ApplicationWindow.vala:194 +msgid "Show" +msgstr "" + +#: src/view/ApplicationWindow.vala:200 +msgid "Status" +msgstr "" + +#: src/view/ApplicationWindow.vala:209 +msgid "Quit" +msgstr "" + +#: src/view/LoginWidget.vala:167 +msgid "Wrong password" +msgstr "" + +#: src/view/LoginWidget.vala:173 +msgid "Username can not be empty" +msgstr "" + +#: src/view/LoginWidget.vala:175 +msgid "Username is already taken" +msgstr "" + +#: src/view/LoginWidget.vala:189 +msgid "Password must be at least 6 characters long" +msgstr "" + +#: src/view/LoginWidget.vala:202 +msgid "Passwords must match" +msgstr "" + +#: src/view/LoginWidget.vala:226 +msgid "Creating profile failed: " +msgstr "" + +#: src/view/LoginWidget.vala:249 +msgid "Profile is encrypted" +msgstr "" + +#: src/view/WelcomeWidget.vala:189 +msgid "Drink your milk for extra strong bones." +msgstr "" + +#: src/view/FriendRequestWidget.vala:44 +msgid "“%s”" +msgstr "" + +#: src/ui/login_widget.ui:132 +msgid "Login automatically" +msgstr "" + +#: src/ui/login_widget.ui:152 src/ui/login_widget.ui:471 +msgid "Password" +msgstr "" + +#: src/ui/login_widget.ui:169 src/ui/login_widget.ui:330 +msgid "Login" +msgstr "" + +#: src/ui/login_widget.ui:205 +msgid "Other profiles" +msgstr "" + +#: src/ui/login_widget.ui:267 +msgid "Import profile…" +msgstr "" + +#: src/ui/login_widget.ui:395 +msgid "New profile" +msgstr "" + +#: src/ui/login_widget.ui:417 +msgid "Username" +msgstr "" + +#: src/ui/login_widget.ui:526 +msgid "Confirm Password" +msgstr "" + +#: src/ui/login_widget.ui:620 +msgid "Create profile" +msgstr "" + +#: src/ui/friend_request_widget.ui:66 src/ui/conference_invite_entry.ui:107 +msgid "Accept" +msgstr "" + +#: src/ui/friend_request_widget.ui:83 src/ui/conference_invite_entry.ui:124 +msgid "Deny" +msgstr "" + +#: src/ui/user_info_widget.ui:462 +msgid "Advanced" +msgstr "" + +#: src/ui/user_info_widget.ui:510 +msgid "Tox nospam" +msgstr "" + +#: src/ui/user_info_widget.ui:532 +msgid "Generate new random nospam" +msgstr "" + +#: src/ui/user_info_widget.ui:546 +msgid "Set this nospam" +msgstr "" + +#: src/ui/user_info_widget.ui:578 +msgid "Previous nospams" +msgstr "" + +#: src/ui/settings_widget.ui:158 +msgid "Bootstrap nodes" +msgstr "" + +#: src/ui/settings_widget.ui:1137 +msgid "Keep History" +msgstr "" + +#: src/ui/settings_widget.ui:1150 +msgid "Store your sent and received messages" +msgstr "" + +#: src/view/ApplicationWindow.vala:208 src/ui/app_menu.ui:38 +msgid "Logout" +msgstr "" + diff --git a/po/ru.po b/po/ru.po index 168acb4..1c6ba0c 100644 --- a/po/ru.po +++ b/po/ru.po @@ -8,15 +8,15 @@ msgstr "" "Language: ru\n" "Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n" -#: src/core/Application.vala:36 +#: src/core/Application.vala:37 msgid "Set level of messages to log" msgstr "Установить уровень логирования" -#: src/core/Application.vala:36 +#: src/core/Application.vala:37 msgid "" msgstr "" -#: src/core/Application.vala:37 +#: src/core/Application.vala:38 msgid "Display version number" msgstr "Показывать номер версии" @@ -44,39 +44,39 @@ msgstr "Ackermann Yuriy, Sorrow, Wrewolf" msgid "Please let me add you to my contact list. 😁" msgstr "Пожалуйста, добавьте меня в свой список контактов. 😁" -#: src/ui/add_contact_widget.ui:132 +#: src/ui/add_contact_widget.ui:133 msgid "Send a friend request" msgstr "Отправить запрос другу" -#: src/ui/add_contact_widget.ui:166 +#: src/ui/add_contact_widget.ui:167 msgid "_ID:" msgstr "_ID:" -#: src/ui/add_contact_widget.ui:183 +#: src/ui/add_contact_widget.ui:184 msgid "Enter a Tox ID or URI here" msgstr "Вставьте Tox ID или URI" -#: src/ui/create_groupchat_widget.ui:122 src/ui/add_contact_widget.ui:187 +#: src/ui/create_groupchat_widget.ui:122 src/ui/add_contact_widget.ui:188 msgid "paste from clipboard" msgstr "вставить из буфера обмена" -#: src/ui/add_contact_widget.ui:221 +#: src/ui/add_contact_widget.ui:222 msgid "Enter your friends Tox ID" msgstr "Введите Tox ID друга" -#: src/ui/add_contact_widget.ui:253 +#: src/ui/add_contact_widget.ui:254 msgid "_Message:" msgstr "_Сообщение:" -#: src/ui/add_contact_widget.ui:279 +#: src/ui/add_contact_widget.ui:280 msgid "Send a custom message to be displayed to the friend you are adding" msgstr "Отправьте сообщение которое увидит друг, которого вы добавите" -#: src/ui/add_contact_widget.ui:301 +#: src/ui/add_contact_widget.ui:302 msgid "Send your friend a short message" msgstr "Отправить другу короткое сообщение" -#: src/ui/add_contact_widget.ui:332 +#: src/ui/add_contact_widget.ui:333 msgid "Send" msgstr "Отправить" @@ -84,11 +84,11 @@ msgstr "Отправить" msgid "_Preferences" msgstr "_Настройки" -#: src/ui/app_menu.ui:34 +#: src/view/ApplicationWindow.vala:204 src/ui/app_menu.ui:34 msgid "About" msgstr "О программе" -#: src/ui/app_menu.ui:38 +#: src/ui/app_menu.ui:42 msgid "_Quit" msgstr "_Выход" @@ -100,7 +100,7 @@ msgstr "Конференция" msgid "Title:" msgstr "Заголовок:" -#: src/ui/conference_info_widget.ui:266 src/ui/user_info_widget.ui:443 +#: src/ui/conference_info_widget.ui:266 src/ui/user_info_widget.ui:624 #: src/ui/friend_info_widget.ui:634 msgid "Apply" msgstr "Применить" @@ -146,7 +146,7 @@ msgstr "_Заголовок:" msgid "T_ype:" msgstr "_Тип:" -#: src/ui/create_groupchat_widget.ui:282 +#: src/ui/login_widget.ui:570 src/ui/create_groupchat_widget.ui:282 msgid "Create" msgstr "Создать" @@ -174,15 +174,15 @@ msgstr "Установите пользовательский псевд msgid "Tox" msgstr "Tox" -#: src/ui/user_info_widget.ui:349 +#: src/ui/user_info_widget.ui:381 msgid "ID:" msgstr "ID:" -#: src/viewmodel/MessageViewModel.vala:79 +#: src/viewmodel/MessageViewModel.vala:90 msgid "Message sent ✓" msgstr "Сообщение отправлено ✓" -#: src/viewmodel/MessageViewModel.vala:79 +#: src/viewmodel/MessageViewModel.vala:93 msgid "Message received ✓" msgstr "Сообщение получено ✓" @@ -195,48 +195,35 @@ msgstr "Этот пир также находится в вашем списке msgid "General" msgstr "Основные" -#: src/ui/settings_widget.ui:251 +#: src/ui/settings_widget.ui:291 msgid "Appearance" msgstr "Внешний вид" -#: src/ui/settings_widget.ui:295 +#: src/ui/settings_widget.ui:335 msgid "Dark Theme" msgstr "Темная тема" -#: src/ui/settings_widget.ui:983 +#: src/ui/settings_widget.ui:1025 msgid "Privacy" msgstr "Приватность" -#: src/ui/settings_widget.ui:1122 -msgid "History" -msgstr "История" - -#: src/ui/settings_widget.ui:1254 -msgid "days" -msgstr "дни" - -#: src/ui/settings_widget.ui:1285 -msgid "Delete all previous conversations" -msgstr "Удалить все предыдущие разговоры" - -#: src/ui/settings_widget.ui:1706 +#: src/ui/settings_widget.ui:1681 msgid "Update bootstrap nodes" msgstr "Обновление узлов начальной загрузки" -#: src/ui/user_info_widget.ui:391 +#: src/ui/user_info_widget.ui:349 msgid "Copy to clipboard" msgstr "Скопировать в буфер обмена" -#: src/view/WelcomeWidget.vala:39 +#: src/view/WelcomeWidget.vala:137 msgid "Chat with your friends and family without anyone else listening in." msgstr "Общайтесь со своими друзьями и семьей, не слушая других." -#: src/ui/welcome_widget.ui:127 +#: src/ui/welcome_widget.ui:123 msgid "Learn more" msgstr "Узнать больше" -#: src/core/Message.vala:80 src/viewmodel/MessageViewModel.vala:80 -#: src/tox/ConferenceMessage.vala:60 +#: src/tox/ToxMessage.vala:53 src/tox/ConferenceMessage.vala:57 msgid "me" msgstr "меня" @@ -244,14 +231,6 @@ msgstr "меня" msgid "Choose an avatar" msgstr "Выберите аватар" -#: src/ui/friend_request_widget.ui:156 -msgid "Reject request" -msgstr "Отклонить запрос" - -#: src/ui/friend_request_widget.ui:132 -msgid "Accept request" -msgstr "Принять запрос" - #: src/core/NotificationListener.vala:139 msgid "New file from %s" msgstr "Новый файл от %s" @@ -265,17 +244,17 @@ msgid "Offline" msgstr "Не в сети" #: src/viewmodel/ContactListEntryViewModel.vala:75 -#: src/ui/user_status_menu.ui:34 +#: src/view/ApplicationWindow.vala:198 src/ui/user_status_menu.ui:34 msgid "Away" msgstr "Отошел" #: src/viewmodel/ContactListEntryViewModel.vala:77 -#: src/ui/user_status_menu.ui:40 +#: src/view/ApplicationWindow.vala:199 src/ui/user_status_menu.ui:40 msgid "Busy" msgstr "Занят" #: src/viewmodel/ContactListEntryViewModel.vala:79 -#: src/ui/user_status_menu.ui:28 +#: src/view/ApplicationWindow.vala:197 src/ui/user_status_menu.ui:28 msgid "Online" msgstr "В сети" @@ -291,15 +270,15 @@ msgstr "Вчера в %s" msgid "Unnamed conference %u" msgstr "Безымянная конференция %u" -#: src/tox/ToxSession.vala:650 +#: src/tox/ToxSession.vala:645 msgid "Address must consist of 76 hexadecimal characters" msgstr "Адрес должен состоять из 76 шестнадцатеричных символов" -#: src/view/ConversationWindow.vala:231 src/view/FileTransferEntry.vala:86 +#: src/view/ConversationWindow.vala:234 src/view/FileTransferEntry.vala:86 msgid "_Cancel" msgstr "О_тмена" -#: src/view/ConversationWindow.vala:230 +#: src/view/ConversationWindow.vala:233 msgid "_Open" msgstr "_Открыть" @@ -343,96 +322,88 @@ msgstr "Речь" msgid "That's you" msgstr "Это Вы" -#: src/ui/welcome_widget.ui:173 +#: src/ui/welcome_widget.ui:169 msgid "Get involved" msgstr "Поучаствовать" -#: src/ui/settings_widget.ui:158 +#: src/ui/settings_widget.ui:198 msgid "Proxy" msgstr "Прокси" -#: src/ui/settings_widget.ui:308 +#: src/ui/settings_widget.ui:348 msgid "Use a dark variant of the theme" msgstr "Использовать тёмный вариант темы" -#: src/ui/settings_widget.ui:364 +#: src/ui/settings_widget.ui:404 msgid "Animations" msgstr "Анимации" -#: src/ui/conference_info_widget.ui:196 src/ui/settings_widget.ui:598 +#: src/ui/conference_info_widget.ui:196 src/ui/settings_widget.ui:638 #: src/ui/friend_info_widget.ui:291 msgid "Notifications" msgstr "Уведомления" -#: src/ui/settings_widget.ui:1184 -msgid "Keep forever" -msgstr "Хранить вечно" - -#: src/ui/settings_widget.ui:1220 -msgid "Remove older than" -msgstr "Удалить старше чем" - -#: src/ui/settings_widget.ui:1386 +#: src/ui/settings_widget.ui:1281 msgid "Connection" msgstr "Соединение" -#: src/ui/settings_widget.ui:1428 +#: src/ui/settings_widget.ui:1323 msgid "UDP" msgstr "UDP" -#: src/ui/settings_widget.ui:1497 +#: src/ui/settings_widget.ui:1392 msgid "IPv6" msgstr "IPv6" -#: src/ui/settings_widget.ui:1510 +#: src/ui/settings_widget.ui:1405 msgid "Allow both IPv4 and IPv6 communication" msgstr "Использовать и IPv4 и IPv6" -#: src/ui/settings_widget.ui:1566 +#: src/ui/settings_widget.ui:1461 msgid "Local discovery" msgstr "Обнаружение локальных устройств" -#: src/ui/settings_widget.ui:1579 +#: src/ui/settings_widget.ui:1474 msgid "Look for peers on the local network" msgstr "Искать пиры в локальной сети" -#: src/ui/settings_widget.ui:1635 +#: src/ui/settings_widget.ui:1530 msgid "Hole punching" msgstr "Пробитие NAT" -#: src/ui/settings_widget.ui:1648 +#: src/ui/settings_widget.ui:1543 msgid "Enable UDP hole punching" msgstr "Включить UDP пробитие NAT" -#: src/ui/settings_widget.ui:1742 +#: src/ui/settings_widget.ui:1716 msgid "Bootstrap nodes" msgstr "Узлы начальной загрузки" -#: src/ui/settings_widget.ui:1878 +#: src/ui/settings_widget.ui:1852 msgid "Proxy" msgstr "Прокси" -#: src/ui/settings_widget.ui:1945 +#: src/ui/settings_widget.ui:1920 msgid "System settings" msgstr "Системные настройки" -#: src/ui/settings_widget.ui:1958 +#: src/ui/settings_widget.ui:1933 msgid "Use your systems proxy settings" msgstr "Использовать системные настройки прокси" -#: src/ui/settings_widget.ui:2012 +#: src/ui/settings_widget.ui:1987 msgid "Manual settings" msgstr "Ручные настройки" -#: src/ui/settings_widget.ui:2025 +#: src/ui/settings_widget.ui:2000 msgid "Use custom proxy settings" msgstr "Пользовательские настройки прокси" -#: src/ui/settings_widget.ui:2066 +#: src/ui/settings_widget.ui:2041 msgid "Host" msgstr "Хост" -#: src/ui/settings_widget.ui:2106 +#: src/ui/settings_widget.ui:2084 msgid "Set a SOCKS5 proxy to connect to" msgstr "Указать SOCKS5 прокси для подключения" @@ -534,23 +505,15 @@ msgstr "Покинуть конференцию" msgid "Notifications have been globally disabled" msgstr "Уведомления глобально отключены" -#: src/ui/conference_invite_entry.ui:108 -msgid "Accept invite" -msgstr "Принять приглашение" - -#: src/ui/conference_invite_entry.ui:132 -msgid "Reject invite" -msgstr "Отклонить приглашение" - #: src/ui/add_contact_widget.ui:52 msgid "No new friend requests" msgstr "Нет новых запросов в друзья" -#: src/ui/contact_list_widget.ui:56 src/ui/add_contact_widget.ui:349 +#: src/ui/contact_list_widget.ui:56 src/ui/add_contact_widget.ui:350 msgid "Add a friend" msgstr "Добавить друга" -#: src/ui/add_contact_widget.ui:362 src/ui/add_contact_widget.ui:396 +#: src/ui/add_contact_widget.ui:363 src/ui/add_contact_widget.ui:397 msgid "Friend requests" msgstr "Запросы дружбы" @@ -558,15 +521,15 @@ msgstr "Запросы дружбы" msgid "Status message:" msgstr "Статус:" -#: src/ui/settings_widget.ui:1441 +#: src/ui/settings_widget.ui:1336 msgid "Use UDP communication when available" msgstr "Использовать UDP если доступно" -#: src/view/WelcomeWidget.vala:35 +#: src/view/WelcomeWidget.vala:129 msgid "A new kind of instant messaging" msgstr "Новый вид обмена мгновенными сообщениями" -#: src/core/Application.vala:70 +#: src/core/Application.vala:73 msgid "Settings" msgstr "Настройки" @@ -586,11 +549,11 @@ msgstr "Изменить статус" msgid "Public key:" msgstr "Публичный ключ:" -#: src/view/ConversationWindow.vala:227 +#: src/view/ConversationWindow.vala:230 msgid "Choose a file to send" msgstr "Выбери файл для отправки" -#: src/view/UserInfoWidget.vala:56 +#: src/view/UserInfoWidget.vala:82 msgid "Images" msgstr "Изображения" @@ -634,7 +597,7 @@ msgstr "Никогда не был онлайн" msgid "Last seen: %s" msgstr "Был в сети: %s" -#: src/view/ConversationWindow.vala:124 +#: src/view/ConversationWindow.vala:127 msgid "%s is typing…" msgstr "%s набирает сообщение…" @@ -646,7 +609,7 @@ msgstr "Приложить скриншот…" msgid "Attach file…" msgstr "Приложить файл…" -#: src/view/AboutDialog.vala:54 data/chat.tox.venom.desktop.in:4 +#: src/view/AboutDialog.vala:54 data/com.github.naxuroqa.venom.desktop.in:4 msgid "A modern Tox client for the Linux desktop" msgstr "Новый Tox клиент для Linux" @@ -654,23 +617,19 @@ msgstr "Новый Tox клиент для Linux" msgid "Copyright © 2013-2018 Venom authors and contributors" msgstr "Copyright © 2013-2018 Venom authors and contributors" -#: data/chat.tox.venom.desktop.in:3 +#: data/com.github.naxuroqa.venom.desktop.in:3 msgid "Venom" msgstr "Venom" -#: data/chat.tox.venom.desktop.in:5 +#: data/com.github.naxuroqa.venom.desktop.in:5 msgid "tox;instant messaging;video chat;" msgstr "tox;instant messaging;video chat;" -#: data/chat.tox.venom.desktop.in:7 -msgid "chat.tox.venom-symbolic" -msgstr "chat.tox.venom-symbolic" - #: src/view/ConferenceInviteEntry.vala:48 msgid "Invite from %s" msgstr "Приглашение от %s" -#: src/ui/settings_widget.ui:433 +#: src/ui/settings_widget.ui:473 msgid "Small contacts" msgstr "Компактный вид" @@ -686,75 +645,75 @@ msgstr "Приглашение в конференцию" msgid "%s invites you to a conference" msgstr "%s приглашает вас в конференцию" -#: src/core/Application.vala:77 +#: src/ui/error_widget.ui:164 msgid "Log" msgstr "Лог" -#: src/ui/settings_widget.ui:377 +#: src/ui/settings_widget.ui:417 msgid "Turn on animated transitions" msgstr "Включить анимацию" -#: src/ui/settings_widget.ui:446 +#: src/ui/settings_widget.ui:486 msgid "Show contacts in a compact format" msgstr "Показывать контакты в компактном формате" -#: src/ui/settings_widget.ui:502 +#: src/ui/settings_widget.ui:542 msgid "Spellcheck" msgstr "Проверка орфографии" -#: src/ui/settings_widget.ui:515 +#: src/ui/settings_widget.ui:555 msgid "Check your spelling while you type" msgstr "Проверять орфографию, когда выпечатаете" -#: src/ui/settings_widget.ui:658 +#: src/ui/settings_widget.ui:699 msgid "Sounds" msgstr "Звуки" -#: src/ui/settings_widget.ui:671 +#: src/ui/settings_widget.ui:712 msgid "Play sounds on new notifications" msgstr "Звуковые уведомления" -#: src/ui/settings_widget.ui:727 +#: src/ui/settings_widget.ui:768 msgid "While Busy" msgstr "Когда занят" -#: src/ui/settings_widget.ui:740 +#: src/ui/settings_widget.ui:781 msgid "Receive notifications even while you are busy" msgstr "Принимать уведомления в занятом режиме" -#: src/ui/settings_widget.ui:825 +#: src/ui/settings_widget.ui:866 msgid "Tray icon" msgstr "Иконка в трее" -#: src/ui/settings_widget.ui:885 +#: src/ui/settings_widget.ui:927 msgid "Minimize" msgstr "Свернуть" -#: src/ui/settings_widget.ui:898 +#: src/ui/settings_widget.ui:940 msgid "Minimize to tray instead of close" msgstr "Свернуть в трей, вместо закрытия" -#: src/ui/settings_widget.ui:1026 +#: src/ui/settings_widget.ui:1068 msgid "Send typing status" msgstr "Отправлять статус печатаю" -#: src/ui/settings_widget.ui:1039 +#: src/ui/settings_widget.ui:1081 msgid "Show others when you are typing" msgstr "Показывать другим, когда вы печатаете" -#: src/ui/error_widget.ui:97 +#: src/ui/error_widget.ui:104 msgid "Oh no! Something broke!" msgstr "О нет! Что то пошло не так!" -#: src/ui/error_widget.ui:112 +#: src/ui/error_widget.ui:119 msgid "Please check your settings and retry" msgstr "Проверьте настройки и повторите попытку" -#: src/ui/error_widget.ui:133 +#: src/ui/error_widget.ui:140 msgid "Info" msgstr "Информация" -#: src/ui/error_widget.ui:170 +#: src/ui/error_widget.ui:202 msgid "Retry" msgstr "Повторить" @@ -762,7 +721,7 @@ msgstr "Повторить" msgid "Mute conversation" msgstr "Отключить уведомления" -#: src/core/Application.vala:38 +#: src/core/Application.vala:39 msgid "Show preferences" msgstr "Показать настройки" @@ -778,39 +737,40 @@ msgstr "Покинуть конференцию" msgid "Remove friend" msgstr "Удалить друга" -#: src/tox/ConferenceMessage.vala:67 +#: src/tox/ConferenceMessage.vala:64 msgid "%s in %s" msgstr "%s в %s" -#: src/view/WelcomeWidget.vala:40 +#: src/view/WelcomeWidget.vala:138 msgid "Now with 50% less bugs." msgstr "Теперь на 50% меньше багов." -#: src/view/WelcomeWidget.vala:41 +#: src/view/WelcomeWidget.vala:139 msgid "Generating witty dialog…" msgstr "Создание остроумного диалога…" -#: src/view/WelcomeWidget.vala:42 +#: src/view/WelcomeWidget.vala:140 msgid "Thank you for using Venom." msgstr "Спасибо за использование Venom." -#: src/view/WelcomeWidget.vala:43 +#: src/view/WelcomeWidget.vala:141 msgid "Always think positive." msgstr "Всегда мысли позитивно." -#: src/view/WelcomeWidget.vala:44 +#: src/view/WelcomeWidget.vala:142 msgid "Have a good day and stay safe." msgstr "Хорошего дня и оставайтесь в безопасности." -#: src/view/WelcomeWidget.vala:45 +#: src/view/WelcomeWidget.vala:143 msgid "You can do it. ― Coffee" msgstr "Ты можешь сделать это. ― Кофе" -#: src/view/WelcomeWidget.vala:46 +#: src/view/WelcomeWidget.vala:144 msgid "Life moves pretty fast. If you don’t stop and look around once in a while, you could miss it. ― Ferris Bueller" msgstr "Жизнь протекает достаточно быстро. Если иногда не останавливаться и не осматриваться, можно пропустить её. ― Феррис Буллер" -#: src/view/SettingsWidget.vala:78 data/chat.tox.venom.desktop.in:17 +#: src/view/ApplicationWindow.vala:203 src/view/SettingsWidget.vala:77 +#: data/com.github.naxuroqa.venom.desktop.in:17 msgid "Preferences" msgstr "Настройки" @@ -826,3 +786,151 @@ msgstr "Отклонить всё" msgid "Open preferences" msgstr "Открыть настройки" +#: data/com.github.naxuroqa.venom.desktop.in:7 +msgid "com.github.naxuroqa.venom-symbolic" +msgstr "com.github.naxuroqa.venom-symbolic" + +#: src/viewmodel/MessageViewModel.vala:66 +msgid "Notice" +msgstr "Заметка" + +#: src/tox/ToxAdapterFriendListener.vala:29 +msgid "%s has been removed from your contact list." +msgstr "%s удалил вас из списка контактов." + +#: src/tox/ToxAdapterFriendListener.vala:30 +msgid "Undo" +msgstr "Отменить" + +#: src/view/ApplicationWindow.vala:194 +msgid "Hide" +msgstr "Скрыть" + +#: src/view/ApplicationWindow.vala:194 +msgid "Show" +msgstr "Показать" + +#: src/view/ApplicationWindow.vala:200 +msgid "Status" +msgstr "Статус" + +#: src/view/ApplicationWindow.vala:209 +msgid "Quit" +msgstr "Выход" + +#: src/view/LoginWidget.vala:167 +msgid "Wrong password" +msgstr "Не верный пароль" + +#: src/view/LoginWidget.vala:173 +msgid "Username can not be empty" +msgstr "Имя не может быть пустым" + +#: src/view/LoginWidget.vala:175 +msgid "Username is already taken" +msgstr "Имя уже занято" + +#: src/view/LoginWidget.vala:189 +msgid "Password must be at least 6 characters long" +msgstr "Пароль должен быть длиннее 6 символов" + +#: src/view/LoginWidget.vala:202 +msgid "Passwords must match" +msgstr "Пароли должны совпадать" + +#: src/view/LoginWidget.vala:226 +msgid "Creating profile failed: " +msgstr "Ошибка создания профиля: " + +#: src/view/LoginWidget.vala:249 +msgid "Profile is encrypted" +msgstr "Профиль зашифрован" + +#: src/view/WelcomeWidget.vala:189 +msgid "Drink your milk for extra strong bones." +msgstr "Пейте дети молоко, будете здоровы." + +#: src/view/FriendRequestWidget.vala:44 +msgid "“%s”" +msgstr "\"%s\"" + +#: src/ui/login_widget.ui:132 +msgid "Login automatically" +msgstr "Входить автоматически" + +#: src/ui/login_widget.ui:152 src/ui/login_widget.ui:471 +msgid "Password" +msgstr "Пароль" + +#: src/ui/login_widget.ui:169 src/ui/login_widget.ui:330 +msgid "Login" +msgstr "Вход" + +#: src/ui/login_widget.ui:205 +msgid "Other profiles" +msgstr "Другие профили" + +#: src/ui/login_widget.ui:267 +msgid "Import profile…" +msgstr "Импорт профиля..." + +#: src/ui/login_widget.ui:395 +msgid "New profile" +msgstr "Новый профиль" + +#: src/ui/login_widget.ui:417 +msgid "Username" +msgstr "Имя" + +#: src/ui/login_widget.ui:526 +msgid "Confirm Password" +msgstr "Подтвердить пароль" + +#: src/ui/login_widget.ui:620 +msgid "Create profile" +msgstr "Создать профиль" + +#: src/ui/friend_request_widget.ui:66 src/ui/conference_invite_entry.ui:107 +msgid "Accept" +msgstr "Принять" + +#: src/ui/friend_request_widget.ui:83 src/ui/conference_invite_entry.ui:124 +msgid "Deny" +msgstr "Отклонить" + +#: src/ui/user_info_widget.ui:462 +msgid "Advanced" +msgstr "Дополнительно" + +#: src/ui/user_info_widget.ui:510 +msgid "Tox nospam" +msgstr "Tox nospam" + +#: src/ui/user_info_widget.ui:532 +msgid "Generate new random nospam" +msgstr "Создать случайный nospam" + +#: src/ui/user_info_widget.ui:546 +msgid "Set this nospam" +msgstr "Установить nospam" + +#: src/ui/user_info_widget.ui:578 +msgid "Previous nospams" +msgstr "Предыдуший nospam" + +#: src/ui/settings_widget.ui:158 +msgid "Bootstrap nodes" +msgstr "Бутстрап ноды" + +#: src/ui/settings_widget.ui:1137 +msgid "Keep History" +msgstr "Сохранять историю" + +#: src/ui/settings_widget.ui:1150 +msgid "Store your sent and received messages" +msgstr "Сохранять принятые и отправленные сообщения" + +#: src/view/ApplicationWindow.vala:208 src/ui/app_menu.ui:38 +msgid "Logout" +msgstr "" + diff --git a/src/core/AVManager.vala b/src/core/AVManager.vala index 825d047..113bcca 100644 --- a/src/core/AVManager.vala +++ b/src/core/AVManager.vala @@ -142,7 +142,7 @@ namespace Venom { } public static void free() { - Logger.log(LogLevel.DEBUG, "AVManager free called"); + CommandLineLogger.log(LogLevel.DEBUG, "AVManager free called"); instance = null; } @@ -155,7 +155,7 @@ namespace Venom { } catch (Error e) { throw new AVManagerError.INIT(e.message); } - Logger.log(LogLevel.INFO, "Gstreamer initialized"); + CommandLineLogger.log(LogLevel.INFO, "Gstreamer initialized"); // input audio pipeline try { @@ -225,9 +225,9 @@ namespace Venom { Gst.Caps caps = Gst.Caps.from_string(get_audio_caps_from_codec_settings(ref default_settings)); Gst.Caps vcaps_in = Gst.Caps.from_string(get_video_in_caps_from_dimensions(640, 480)); Gst.Caps vcaps_out = Gst.Caps.from_string(VIDEO_CAPS_OUT); - Logger.log(LogLevel.INFO, "Audio caps are [" + caps.to_string() + "]"); - Logger.log(LogLevel.INFO, "Video(IN) caps are [" + vcaps_in.to_string() + "]"); - Logger.log(LogLevel.INFO, "Video(OUT) caps are [" + vcaps_out.to_string() + "]"); + CommandLineLogger.log(LogLevel.INFO, "Audio caps are [" + caps.to_string() + "]"); + CommandLineLogger.log(LogLevel.INFO, "Video(IN) caps are [" + vcaps_in.to_string() + "]"); + CommandLineLogger.log(LogLevel.INFO, "Video(OUT) caps are [" + vcaps_out.to_string() + "]"); audio_source_in.caps = caps; audio_sink_out.caps = caps; @@ -251,7 +251,7 @@ namespace Venom { } ~AVManager() { - Logger.log(LogLevel.INFO, "AVManager destructor called"); + CommandLineLogger.log(LogLevel.INFO, "AVManager destructor called"); audio_status_changes.push( AVStatusChange() { type = VideoStatusChangeType.KILL }); @@ -265,32 +265,32 @@ namespace Venom { join(); Gst.deinit(); - Logger.log(LogLevel.INFO, "Gstreamer deinitialized"); + CommandLineLogger.log(LogLevel.INFO, "Gstreamer deinitialized"); } private bool bus_callback(Gst.Bus bus, Gst.Message message) { Error e; if (bus == audio_in_bus) { - Logger.log(LogLevel.DEBUG, "Audio_in_bus with message: " + message.type.get_name()); + CommandLineLogger.log(LogLevel.DEBUG, "Audio_in_bus with message: " + message.type.get_name()); } else if (bus == audio_out_bus) { - Logger.log(LogLevel.DEBUG, "Audio_out_bus with message: " + message.type.get_name()); + CommandLineLogger.log(LogLevel.DEBUG, "Audio_out_bus with message: " + message.type.get_name()); } else if (bus == video_in_bus) { - Logger.log(LogLevel.DEBUG, "Video_in_bus with message: " + message.type.get_name()); + CommandLineLogger.log(LogLevel.DEBUG, "Video_in_bus with message: " + message.type.get_name()); } else if (bus == video_out_bus) { - Logger.log(LogLevel.DEBUG, "Video_out_bus with message: " + message.type.get_name()); + CommandLineLogger.log(LogLevel.DEBUG, "Video_out_bus with message: " + message.type.get_name()); switch (message.type) { case Gst.MessageType.ERROR: message.parse_error(out e, null); - Logger.log(LogLevel.ERROR, e.message); + CommandLineLogger.log(LogLevel.ERROR, e.message); video_pipeline_out.set_state(Gst.State.NULL); break; case Gst.MessageType.WARNING: message.parse_warning(out e, null); - Logger.log(LogLevel.WARNING, e.message); + CommandLineLogger.log(LogLevel.WARNING, e.message); break; case Gst.MessageType.EOS: - Logger.log(LogLevel.DEBUG, message.src.name); + CommandLineLogger.log(LogLevel.DEBUG, message.src.name); break; case Gst.MessageType.STATE_CHANGED: @@ -300,7 +300,7 @@ namespace Venom { Gst.State pending_state; message.parse_state_changed(out old_state, out new_state, out pending_state); - Logger.log(LogLevel.DEBUG, "Pipeline state changed from %d to %d:\n".printf( + CommandLineLogger.log(LogLevel.DEBUG, "Pipeline state changed from %d to %d:\n".printf( old_state, new_state) ); break; @@ -313,13 +313,13 @@ namespace Venom { } private void audio_receive_callback(ToxAV.ToxAV toxav, int32 call_index, int16[] samples) { - //Logger.log(LogLevel.DEBUG, "Received audio samples (%d bytes)".printf(samples.length * 2)); + //CommandLineLogger.log(LogLevel.DEBUG, "Received audio samples (%d bytes)".printf(samples.length * 2)); set_audio_caps(call_index); play_audio_buffer(samples, samples.length); } private void video_receive_callback(ToxAV.ToxAV toxav, int32 call_index, Vpx.Image frame) { - Logger.log(LogLevel.DEBUG, "Got video frame, of size: %u".printf(frame.d_w * frame.d_h)); + CommandLineLogger.log(LogLevel.DEBUG, "Got video frame, of size: %u".printf(frame.d_w * frame.d_h)); set_video_in_caps(frame.d_w, frame.d_h); video_buffer_in(frame); } @@ -332,25 +332,25 @@ namespace Venom { private void destroy_audio_pipeline() { pipeline_in.set_state(Gst.State.NULL); pipeline_out.set_state(Gst.State.NULL); - Logger.log(LogLevel.INFO, "Audio pipeline destroyed"); + CommandLineLogger.log(LogLevel.INFO, "Audio pipeline destroyed"); } private void set_audio_pipeline_playing() { pipeline_in.set_state(Gst.State.PLAYING); pipeline_out.set_state(Gst.State.PLAYING); - Logger.log(LogLevel.INFO, "Audio pipeline set to playing"); + CommandLineLogger.log(LogLevel.INFO, "Audio pipeline set to playing"); } private void set_video_pipeline_playing() { video_pipeline_in.set_state(Gst.State.PLAYING); video_pipeline_out.set_state(Gst.State.PLAYING); - Logger.log(LogLevel.INFO, "Video pipeline set to playing"); + CommandLineLogger.log(LogLevel.INFO, "Video pipeline set to playing"); } private void destroy_video_pipeline() { video_pipeline_in.set_state(Gst.State.NULL); video_pipeline_out.set_state(Gst.State.NULL); - Logger.log(LogLevel.INFO, "Video pipeline destroyed"); + CommandLineLogger.log(LogLevel.INFO, "Video pipeline destroyed"); } private static string get_audio_caps_from_codec_settings(ref ToxAV.CodecSettings settings) { @@ -370,7 +370,7 @@ namespace Venom { w_ = w; h_ = h; string caps_string = get_video_in_caps_from_dimensions(w_, h_); - Logger.log(LogLevel.INFO, "Changing video caps to " + caps_string); + CommandLineLogger.log(LogLevel.INFO, "Changing video caps to " + caps_string); video_pipeline_in.set_state(Gst.State.NULL); video_source_in.caps = Gst.Caps.from_string(caps_string); video_pipeline_in.set_state(Gst.State.PLAYING); @@ -381,7 +381,7 @@ namespace Venom { ToxAV.AV_Error ret = toxav.get_peer_csettings(call_index, 0, ref settings); if (ret != ToxAV.AV_Error.NONE) { - Logger.log(LogLevel.DEBUG, "Could not acquire codec settings for contact %i, assuming default settings".printf(call_index)); + CommandLineLogger.log(LogLevel.DEBUG, "Could not acquire codec settings for contact %i, assuming default settings".printf(call_index)); settings = ToxAV.DefaultCodecSettings; } @@ -389,7 +389,7 @@ namespace Venom { settings.audio_sample_rate != current_audio_settings.audio_sample_rate) { current_audio_settings = settings; string caps_string = get_audio_caps_from_codec_settings(ref settings); - Logger.log(LogLevel.INFO, "Changing caps to " + caps_string); + CommandLineLogger.log(LogLevel.INFO, "Changing caps to " + caps_string); audio_source_in.caps = Gst.Caps.from_string(caps_string); } } @@ -408,28 +408,28 @@ namespace Venom { //Allocate the new buffer here, we will return this buffer (it is dest) int len = int.min(gst_buf.data.length, dest.length * 2); Memory.copy(dest, gst_buf.data, len); - //Logger.log(LogLevel.DEBUG, "pulled %i bytes from OUT pipeline".printf(len)); + //CommandLineLogger.log(LogLevel.DEBUG, "pulled %i bytes from OUT pipeline".printf(len)); return len / 2; } private void video_buffer_in(Vpx.Image frame) { int w = (int) frame.d_w, h = (int) frame.d_h; - //Logger.log(LogLevel.DEBUG, "received new vpx frame of size %ix%i".printf(w, h)); + //CommandLineLogger.log(LogLevel.DEBUG, "received new vpx frame of size %ix%i".printf(w, h)); int comp_offset_y = Gst.video_format_get_component_offset(Gst.VideoFormat.I420, 0, w, h); int comp_offset_u = Gst.video_format_get_component_offset(Gst.VideoFormat.I420, 1, w, h); int comp_offset_v = Gst.video_format_get_component_offset(Gst.VideoFormat.I420, 2, w, h); - //Logger.log(LogLevel.DEBUG, "comp_offset_y: %i, comp_offset_u: %i, comp_offset_v: %i".printf(comp_offset_y, comp_offset_u, comp_offset_v)); + //CommandLineLogger.log(LogLevel.DEBUG, "comp_offset_y: %i, comp_offset_u: %i, comp_offset_v: %i".printf(comp_offset_y, comp_offset_u, comp_offset_v)); int row_stride_y = Gst.video_format_get_row_stride(Gst.VideoFormat.I420, 0, w); int row_stride_u = Gst.video_format_get_row_stride(Gst.VideoFormat.I420, 1, w); int row_stride_v = Gst.video_format_get_row_stride(Gst.VideoFormat.I420, 2, w); - //Logger.log(LogLevel.DEBUG, "row_stride_y: %i, row_stride_u: %i, row_stride_v: %i".printf(row_stride_y, row_stride_u, row_stride_v)); + //CommandLineLogger.log(LogLevel.DEBUG, "row_stride_y: %i, row_stride_u: %i, row_stride_v: %i".printf(row_stride_y, row_stride_u, row_stride_v)); int pixel_stride_y = Gst.video_format_get_pixel_stride(Gst.VideoFormat.I420, 0); int pixel_stride_u = Gst.video_format_get_pixel_stride(Gst.VideoFormat.I420, 1); int pixel_stride_v = Gst.video_format_get_pixel_stride(Gst.VideoFormat.I420, 2); - //Logger.log(LogLevel.DEBUG, "pixel_stride_y: %i, pixel_stride_u: %i, pixel_stride_v: %i".printf(pixel_stride_y, pixel_stride_u, pixel_stride_v)); + //CommandLineLogger.log(LogLevel.DEBUG, "pixel_stride_y: %i, pixel_stride_u: %i, pixel_stride_v: %i".printf(pixel_stride_y, pixel_stride_u, pixel_stride_v)); Gst.Buffer gst_buf = new Gst.Buffer.and_alloc(Gst.video_format_get_size(Gst.VideoFormat.I420, w, h)); - //Logger.log(LogLevel.DEBUG, "Created buffer of size %i".printf(gst_buf.data.length)); + //CommandLineLogger.log(LogLevel.DEBUG, "Created buffer of size %i".printf(gst_buf.data.length)); unowned uint8[] y = gst_buf.data[comp_offset_y : comp_offset_u]; unowned uint8[] u = gst_buf.data[comp_offset_u : comp_offset_v]; unowned uint8[] v = gst_buf.data[comp_offset_v : gst_buf.data.length]; @@ -457,7 +457,7 @@ namespace Venom { } video_source_in.push_buffer(gst_buf); - Logger.log(LogLevel.DEBUG, "pushed %u bytes to VIDEO_IN pipeline".printf(gst_buf.data.length)); + CommandLineLogger.log(LogLevel.DEBUG, "pushed %u bytes to VIDEO_IN pipeline".printf(gst_buf.data.length)); } private Vpx.Image ? make_vpx_image() { @@ -465,12 +465,12 @@ namespace Venom { if (gst_buf == null) { return null; } - //Logger.log(LogLevel.DEBUG, "pulled %i bytes form VIDEO_OUT pipeline".printf(gst_buf.data.length)); + //CommandLineLogger.log(LogLevel.DEBUG, "pulled %i bytes form VIDEO_OUT pipeline".printf(gst_buf.data.length)); unowned Gst.Structure structure = gst_buf.caps.get_structure(0); int height = 0, width = 0; if (!structure.get_int("height", out height) || !structure.get_int("width", out width)) { - Logger.log(LogLevel.ERROR, "Error retrieving height and width from caps: " + gst_buf.caps.to_string()); + CommandLineLogger.log(LogLevel.ERROR, "Error retrieving height and width from caps: " + gst_buf.caps.to_string()); return null; } @@ -485,7 +485,7 @@ namespace Venom { } private int video_thread_fun() { - Logger.log(LogLevel.INFO, "starting video thread..."); + CommandLineLogger.log(LogLevel.INFO, "starting video thread..."); set_video_pipeline_playing(); VideoCallInfo[] calls = new VideoCallInfo[MAX_CALLS]; @@ -500,33 +500,33 @@ namespace Venom { if (c != null) { switch (c.type) { case VideoStatusChangeType.START: - Logger.log(LogLevel.DEBUG, "Starting video session #%i".printf(c.call_index)); + CommandLineLogger.log(LogLevel.DEBUG, "Starting video session #%i".printf(c.call_index)); if (calls[c.call_index].active == false) { number_of_calls++; } calls[c.call_index].active = true; break; case VideoStatusChangeType.START_PREVIEW: - Logger.log(LogLevel.DEBUG, "Starting video preview"); + CommandLineLogger.log(LogLevel.DEBUG, "Starting video preview"); preview = true; break; case VideoStatusChangeType.END: - Logger.log(LogLevel.DEBUG, "Ending video session %i".printf(c.call_index)); + CommandLineLogger.log(LogLevel.DEBUG, "Ending video session %i".printf(c.call_index)); if (calls[c.call_index].active == true) { number_of_calls--; } calls[c.call_index].active = false; break; case VideoStatusChangeType.END_PREVIEW: - Logger.log(LogLevel.DEBUG, "Stopping video preview %i".printf(c.call_index)); + CommandLineLogger.log(LogLevel.DEBUG, "Stopping video preview %i".printf(c.call_index)); preview = false; break; case VideoStatusChangeType.KILL: - Logger.log(LogLevel.DEBUG, "Video thread received kill message"); + CommandLineLogger.log(LogLevel.DEBUG, "Video thread received kill message"); running = false; continue; default: - Logger.log(LogLevel.ERROR, "unknown video status change type"); + CommandLineLogger.log(LogLevel.ERROR, "unknown video status change type"); break; } } @@ -543,7 +543,7 @@ namespace Venom { Vpx.Image out_image = make_vpx_image(); if (out_image == null) { // either eos or pipeline stopped - Logger.log(LogLevel.DEBUG, "Pulled null buffer from video device"); + CommandLineLogger.log(LogLevel.DEBUG, "Pulled null buffer from video device"); Thread.usleep(10000); continue; } @@ -556,23 +556,23 @@ namespace Venom { if (calls[i].active) { prep_frame_ret = toxav.prepare_video_frame(i, video_enc_buffer, out_image); if (prep_frame_ret <= 0) { - Logger.log(LogLevel.WARNING, "prepare_video_frame returned an error: " + prep_frame_ret.to_string()); + CommandLineLogger.log(LogLevel.WARNING, "prepare_video_frame returned an error: " + prep_frame_ret.to_string()); } else { send_video_ret = toxav.send_video(i, video_enc_buffer, prep_frame_ret); if (send_video_ret != ToxAV.AV_Error.NONE) { - Logger.log(LogLevel.WARNING, "send_video returned " + send_video_ret.to_string()); + CommandLineLogger.log(LogLevel.WARNING, "send_video returned " + send_video_ret.to_string()); } } } } } - Logger.log(LogLevel.INFO, "stopping video thread..."); + CommandLineLogger.log(LogLevel.INFO, "stopping video thread..."); destroy_video_pipeline(); return 0; } private int audio_thread_fun() { - Logger.log(LogLevel.INFO, "starting audio thread..."); + CommandLineLogger.log(LogLevel.INFO, "starting audio thread..."); set_audio_pipeline_playing(); int perframe = (int) (ToxAV.DefaultCodecSettings.audio_frame_duration * ToxAV.DefaultCodecSettings.audio_sample_rate) / 1000; @@ -592,7 +592,7 @@ namespace Venom { if (c != null) { switch (c.type) { case AudioStatusChangeType.START: - Logger.log(LogLevel.DEBUG, "Starting audio session %i".printf(c.call_index)); + CommandLineLogger.log(LogLevel.DEBUG, "Starting audio session %i".printf(c.call_index)); ToxAV.AV_Error e = toxav.prepare_transmission( c.call_index, ToxAV.JITTER_BUFFER_DEFAULT_CAPACITY, @@ -600,47 +600,47 @@ namespace Venom { c.var1 // video support ); if (e != ToxAV.AV_Error.NONE) { - Logger.log(LogLevel.FATAL, "Could not prepare AV transmission: %s".printf(e.to_string())); + CommandLineLogger.log(LogLevel.FATAL, "Could not prepare AV transmission: %s".printf(e.to_string())); } else { number_of_calls++; calls[c.call_index].active = true; } break; case AudioStatusChangeType.START_PREVIEW: - Logger.log(LogLevel.DEBUG, "Starting audio preview"); + CommandLineLogger.log(LogLevel.DEBUG, "Starting audio preview"); preview = true; break; case AudioStatusChangeType.END: - Logger.log(LogLevel.DEBUG, "Ending audio session %i".printf(c.call_index)); + CommandLineLogger.log(LogLevel.DEBUG, "Ending audio session %i".printf(c.call_index)); ToxAV.AV_Error e = toxav.kill_transmission(c.call_index); if (e != ToxAV.AV_Error.NONE) { - Logger.log(LogLevel.FATAL, "Could not shutdown Audio transmission: %s".printf(e.to_string())); + CommandLineLogger.log(LogLevel.FATAL, "Could not shutdown Audio transmission: %s".printf(e.to_string())); } else { number_of_calls--; calls[c.call_index].active = false; } break; case AudioStatusChangeType.END_PREVIEW: - Logger.log(LogLevel.DEBUG, "Ending audio preview"); + CommandLineLogger.log(LogLevel.DEBUG, "Ending audio preview"); preview = false; break; case AudioStatusChangeType.MUTE: bool muted = (c.var1 == 1); - Logger.log(LogLevel.DEBUG, muted ? "Muting %i".printf(c.call_index) : "Unmuting %i".printf(c.call_index)); + CommandLineLogger.log(LogLevel.DEBUG, muted ? "Muting %i".printf(c.call_index) : "Unmuting %i".printf(c.call_index)); calls[c.call_index].muted = muted; break; case AudioStatusChangeType.VOLUME: calls[c.call_index].volume = c.var1; - Logger.log(LogLevel.DEBUG, "Set receive volume for %i to %i".printf(c.call_index, c.var1)); + CommandLineLogger.log(LogLevel.DEBUG, "Set receive volume for %i to %i".printf(c.call_index, c.var1)); //FIXME this only works for one contact right now audio_volume_in.set("volume", ((double) c.var1) / 100.0); break; case AudioStatusChangeType.KILL: - Logger.log(LogLevel.DEBUG, "Audio thread received kill message"); + CommandLineLogger.log(LogLevel.DEBUG, "Audio thread received kill message"); running = false; continue; default: - Logger.log(LogLevel.ERROR, "unknown av status change type"); + CommandLineLogger.log(LogLevel.ERROR, "unknown av status change type"); break; } } @@ -666,18 +666,18 @@ namespace Venom { if (calls[i].active) { prep_frame_ret = toxav.prepare_audio_frame(i, enc_buffer, buffer, buffer_size); if (prep_frame_ret <= 0) { - Logger.log(LogLevel.WARNING, "prepare_audio_frame returned an error: %s".printf(prep_frame_ret.to_string())); + CommandLineLogger.log(LogLevel.WARNING, "prepare_audio_frame returned an error: %s".printf(prep_frame_ret.to_string())); } else { send_audio_ret = toxav.send_audio(i, enc_buffer, prep_frame_ret); if (send_audio_ret != ToxAV.AV_Error.NONE) { - Logger.log(LogLevel.WARNING, "send_audio returned %s".printf(send_audio_ret.to_string())); + CommandLineLogger.log(LogLevel.WARNING, "send_audio returned %s".printf(send_audio_ret.to_string())); } } } } } - Logger.log(LogLevel.INFO, "stopping audio thread..."); + CommandLineLogger.log(LogLevel.INFO, "stopping audio thread..."); destroy_audio_pipeline(); return 0; } @@ -781,7 +781,7 @@ namespace Venom { } public void play_sound(string location) { - Logger.log(LogLevel.DEBUG, "playing sound " + location); + CommandLineLogger.log(LogLevel.DEBUG, "playing sound " + location); Gst.Element playbin = Gst.ElementFactory.make("playbin2", "playbin"); Gst.Element sink = Gst.ElementFactory.make("autoaudiosink", "sink"); playbin.set("uri", location, @@ -792,16 +792,16 @@ namespace Venom { switch (message.type) { case Gst.MessageType.ERROR: message.parse_error(out e, null); - Logger.log(LogLevel.ERROR, "Error playing sound: " + e.message); + CommandLineLogger.log(LogLevel.ERROR, "Error playing sound: " + e.message); break; case Gst.MessageType.EOS: playbin.set_state(Gst.State.NULL); playbin.unref(); - Logger.log(LogLevel.DEBUG, "sound finished playing"); + CommandLineLogger.log(LogLevel.DEBUG, "sound finished playing"); break; case Gst.MessageType.WARNING: message.parse_warning(out e, null); - Logger.log(LogLevel.WARNING, "Error playing sound: " + e.message); + CommandLineLogger.log(LogLevel.WARNING, "Error playing sound: " + e.message); break; default: break; diff --git a/src/core/Application.vala b/src/core/Application.vala index 362eb11..5aa73c9 100644 --- a/src/core/Application.vala +++ b/src/core/Application.vala @@ -24,6 +24,7 @@ namespace Venom { { "preferences", on_show_preferences, null, null, null }, { "about", on_show_about, null, null, null }, { "quit", on_quit, null, null, null }, + { "logout", on_logout, null, null, null }, { "show-filetransfers", on_show_filetransfers, null, null, null }, { "show-conferences", on_show_conferences, null, null, null }, { "show-add-contact", on_show_add_contact, null, null, null }, @@ -41,14 +42,18 @@ namespace Venom { private static LogLevel loglevel = LogLevel.INFO; - private IDatabase database; - private ILogger logger; + private Database database; + private Logger logger; private ToxSession session; - private IDhtNodeDatabase node_database; + private DhtNodeRepository node_database; private ISettingsDatabase settings_database; - private IContactDatabase contact_database; - private Factory.IWidgetFactory widget_factory; - private IDatabaseFactory database_factory; + private ContactRepository contact_repository; + private FriendRequestRepository friend_request_repository; + private NospamRepository nospam_repository; + private MessageRepository message_repository; + private Factory.WidgetFactory widget_factory; + private DatabaseFactory database_factory; + private GlobalSettings global_settings; public Application() { Object( @@ -59,29 +64,20 @@ namespace Venom { add_action_entries(app_entries, this); } - private Gtk.ApplicationWindow create_application_window() throws Error { - return widget_factory.createApplicationWindow(this, session, node_database, settings_database, contact_database); + private Gtk.ApplicationWindow create_application_window(Profile profile) throws Error { + return widget_factory.create_application_window(this, session, profile, nospam_repository, friend_request_repository, message_repository, node_database, settings_database, contact_repository); } private Gtk.ApplicationWindow create_error_window(string error_message) { - var window = new Gtk.ApplicationWindow(this); - window.set_default_size(800, 600); - var err_widget = new ErrorWidget(window, error_message); - err_widget.add_page(widget_factory.createSettingsWidget(null, settings_database, node_database), "settings", _("Settings")); - var log_view = new Gtk.TextView(); - log_view.editable = false; - log_view.monospace = true; - Gtk.TextIter iter; - log_view.buffer.get_start_iter(out iter); - log_view.buffer.insert_markup(ref iter, logger.get_log(), -1); - err_widget.add_page(log_view, "log", _("Log")); + var err_widget = new ErrorWidget(this, logger, error_message); + err_widget.add_page(widget_factory.create_settings_widget(null, settings_database, node_database), "settings", _("Settings")); + err_widget.on_retry.connect(() => { - window.destroy(); + err_widget.destroy(); + settings_database.save(); try_show_app_window(); }); - window.add(err_widget); - window.show_all(); - return window; + return err_widget; } private static void create_path_for_filename(string filename) throws Error { @@ -99,27 +95,14 @@ namespace Venom { Posix.signal(Posix.Signal.TERM, on_sig_int); #endif - Gtk.AccelMap.load(R.constants.accels_filename()); + CommandLineLogger.displayed_level = loglevel; + widget_factory = new Factory.DefaultWidgetFactory(); + logger = widget_factory.create_logger(); - Logger.displayed_level = loglevel; - widget_factory = new Factory.WidgetFactory(); - logger = widget_factory.createLogger(); - database_factory = widget_factory.createDatabaseFactory(); - - try { - var db_file = R.constants.db_filename(); - create_path_for_filename(db_file); - database = database_factory.createDatabase(db_file); - var statementFactory = database_factory.createStatementFactory(database); - node_database = database_factory.createNodeDatabase(statementFactory, logger); - contact_database = database_factory.createContactDatabase(statementFactory, logger); - settings_database = database_factory.createSettingsDatabase(statementFactory, logger); - settings_database.load(); - - } catch (Error e) { - logger.f("Database creation failed: " + e.message); - assert_not_reached(); - } + var screen = Gdk.Screen.get_default(); + var css_provider = new Gtk.CssProvider(); + css_provider.load_from_resource("/com/github/naxuroqa/venom/css/custom.css"); + Gtk.StyleContext.add_provider_for_screen(screen, css_provider, Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION); var builder = new Gtk.Builder(); try { @@ -132,17 +115,33 @@ namespace Venom { var app_menu = builder.get_object("app_menu") as MenuModel; assert(app_menu != null); set_app_menu(app_menu); + + load_global_settings(); } protected override void shutdown() { - settings_database.save(); + var window = get_active_window(); + if (window != null) { + window.destroy(); + window = null; + } + + save_global_settings(); + + widget_factory = null; + + if (settings_database != null) { + settings_database.save(); + } settings_database = null; - contact_database = null; + contact_repository = null; node_database = null; + nospam_repository = null; + friend_request_repository = null; + message_repository = null; database = null; - Gtk.AccelMap.save(R.constants.accels_filename()); base.shutdown(); } @@ -153,12 +152,45 @@ namespace Venom { return; } + if (global_settings.auto_login && global_settings.last_used_profile.length > 0) { + var profile = new Profile(R.constants.default_profile_dir(), global_settings.last_used_profile); + if (profile.is_sane() && !profile.test_is_encrypted()) { + try_show_main_window(profile); + return; + } else { + logger.i("Auto login set, but profile either does not exist or is encrypted"); + } + } + + window = new LoginWidget(this, global_settings, logger); + window.present(); + } + + public void try_show_main_window(Profile profile) { + var window = get_active_window() as Gtk.ApplicationWindow; + database_factory = widget_factory.create_database_factory(); try { + var db_file = profile.dbfile; + create_path_for_filename(db_file); + database = database_factory.create_database(db_file, profile.get_db_key()); + var statement_factory = database_factory.create_statement_factory(database); + node_database = database_factory.create_node_repository(statement_factory, logger); + contact_repository = database_factory.create_contact_repository(statement_factory, logger); + friend_request_repository = database_factory.create_friend_request_repository(statement_factory, logger); + nospam_repository = database_factory.create_nospam_repository(statement_factory, logger); + message_repository = database_factory.create_message_repository(statement_factory, logger); + settings_database = database_factory.create_settings_database(statement_factory, logger); + settings_database.load(); + if (session == null) { - var session_io = new ToxSessionIOImpl(logger); - session = new ToxSessionImpl(session_io, node_database, settings_database, logger); + session = new ToxSessionImpl(profile, node_database, settings_database, logger); + } + + if (window != null) { + window.destroy(); } - create_application_window().present(); + + create_application_window(profile).present(); } catch (Error e) { create_error_window(e.message).present(); } @@ -193,6 +225,25 @@ namespace Venom { logger.f("not implemented."); } + private void load_global_settings() { + try { + var settings_string = FileIO.load_contents_text(R.constants.default_global_settings()); + global_settings = GlobalSettings.deserialize(settings_string); + } catch (Error e) { + logger.i("Loading global settings failed: " + e.message); + global_settings = new GlobalSettings(); + } + } + + private void save_global_settings() { + try { + var settings_string = GlobalSettings.serialize(global_settings); + FileIO.save_contents_text(R.constants.default_global_settings(), settings_string); + } catch (Error e) { + logger.e("Saving global settings failed: " + e.message); + } + } + private delegate void AppWindowDelegate(ApplicationWindow win); private void on_active_window(AppWindowDelegate run) { @@ -203,12 +254,42 @@ namespace Venom { } private void on_show_preferences() { + logger.d("on_show_preferences"); activate(); on_active_window((win) => win.show_settings()); } + private void on_logout() { + logger.d("on_logout"); + global_settings.auto_login = false; + var active_window = get_active_window() as ApplicationWindow; + if (active_window != null) { + active_window.destroy(); + active_window = null; + + widget_factory = new Factory.DefaultWidgetFactory(); + + if (settings_database != null) { + settings_database.save(); + } + + settings_database = null; + contact_repository = null; + node_database = null; + nospam_repository = null; + friend_request_repository = null; + message_repository = null; + database = null; + session = null; + + Gtk.Settings.get_default().gtk_application_prefer_dark_theme = false; + try_show_app_window(); + } + } + public void on_show_about() { - var about_dialog = widget_factory.createAboutDialog(); + logger.d("on_show_about"); + var about_dialog = widget_factory.create_about_dialog(); about_dialog.transient_for = get_active_window(); about_dialog.run(); about_dialog.destroy(); diff --git a/src/testing/mocks/MockMessageDb.vala b/src/core/GlobalSettings.vala similarity index 50% rename from src/testing/mocks/MockMessageDb.vala rename to src/core/GlobalSettings.vala index 6e60b72..ec603c8 100644 --- a/src/testing/mocks/MockMessageDb.vala +++ b/src/core/GlobalSettings.vala @@ -1,7 +1,7 @@ /* - * MessageDbMock.vala + * GlobalSettings.vala * - * Copyright (C) 2017 Venom authors and contributors + * Copyright (C) 2018 Venom authors and contributors * * This file is part of Venom. * @@ -19,21 +19,17 @@ * along with Venom. If not, see . */ -using Venom; +namespace Venom { + public class GlobalSettings : GLib.Object { + public string last_used_profile { get; set; default = ""; } + public bool auto_login { get; set; default = false; } -namespace Mock { - public class MockLoggedMessageFactory : ILoggedMessageFactory, Object { - public ILoggedMessage createLoggedMessage(string userId, string contactId, string message, DateTime time, bool outgoing) { - var args = Arguments.builder() - .string(userId) - .string(contactId) - .string(message) - .bool(outgoing) - .create(); - return (ILoggedMessage) mock().actual_call(this, "createLoggedMessage", args).get_object(); + public static string serialize(GlobalSettings settings) throws Error { + return Json.gobject_to_data(settings, null); } - } - public class MockLoggedMessage : ILoggedMessage, Object { + public static GlobalSettings deserialize(string data) throws Error { + return Json.gobject_from_data(typeof(GlobalSettings), data) as GlobalSettings; + } } } diff --git a/src/core/Interfaces.vala b/src/core/Interfaces.vala deleted file mode 100644 index 5792cbc..0000000 --- a/src/core/Interfaces.vala +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Interfaces.vala - * - * Copyright (C) 2017-2018 Venom authors and contributors - * - * This file is part of Venom. - * - * Venom is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Venom is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with Venom. If not, see . - */ - -namespace Venom { - public interface IDhtNode : Object { - public abstract string pub_key { get; set; } - public abstract string host { get; set; } - public abstract uint port { get; set; } - public abstract string maintainer { get; set; } - public abstract string location { get; set; } - public abstract bool is_blocked { get; set; } - - public string to_string() { - return "%s:%u %s - %s / %s (blocked: %s)".printf(host, port, pub_key, maintainer, location, is_blocked ? "true" : "false"); - } - } - - public interface ILogger : Object { - public abstract void d(string message); - public abstract void i(string message); - public abstract void w(string message); - public abstract void e(string message); - public abstract void f(string message); - public abstract string get_log(); - public abstract void attach_to_glib(); - } - - public enum UserStatus { - NONE, - AWAY, - BUSY - } -} diff --git a/src/core/Logger.vala b/src/core/Logger.vala index ded78a1..615c465 100644 --- a/src/core/Logger.vala +++ b/src/core/Logger.vala @@ -20,6 +20,16 @@ */ namespace Venom { + public interface Logger : Object { + public abstract void d(string message); + public abstract void i(string message); + public abstract void w(string message); + public abstract void e(string message); + public abstract void f(string message); + public abstract string get_log(); + public abstract void attach_to_glib(); + } + namespace TermColor { public const string BLACK = "\x1B[30m"; public const string RED = "\x1B[31m"; @@ -42,6 +52,9 @@ namespace Venom { public static string insert_span(string attributes, string content) { return @"$content"; } + public static string bold(string text) { + return @"$text"; + } } public enum LogLevel { @@ -86,20 +99,20 @@ namespace Venom { } } - public class Logger : ILogger, Object { + public class CommandLineLogger : Logger, Object { public static LogLevel displayed_level { get; set; default = LogLevel.WARNING; } private StringBuilder log_builder; public string get_log() { return log_builder.str; } - public Logger() { + public CommandLineLogger() { log_builder = new StringBuilder(); - d("Logger created."); + d("CommandLineLogger created."); } - ~Logger() { - d("Logger destroyed."); + ~CommandLineLogger() { + d("CommandLineLogger destroyed."); } public void d(string message) { diff --git a/src/core/Message.vala b/src/core/Message.vala index b95eb3c..90c8cc3 100644 --- a/src/core/Message.vala +++ b/src/core/Message.vala @@ -20,99 +20,38 @@ */ namespace Venom { - public enum MessageDirection { - INCOMING, - OUTGOING + public enum MessageSender { + REMOTE, + LOCAL, + SYSTEM } - public interface IMessage : Object { - public signal void message_changed(); + public enum TransmissionState { + NONE, + SENT, + RECEIVED + } + + public interface Message : GLib.Object { + public abstract int id { get; set; } + public abstract int peers_index { get; set; } + public abstract DateTime timestamp { get; set; } + public abstract MessageSender sender { get; set; } + public abstract string message { get; set; } + public abstract bool is_action { get; set; } + public abstract TransmissionState state { get; set; } - public abstract DateTime timestamp { get; protected set; } - public abstract MessageDirection message_direction { get; protected set; } - public abstract bool important { get; set; } - public abstract bool is_action { get; set; } - public abstract bool received { get; set; } + public abstract bool is_conference_message(); + public abstract bool equals_sender(Message m); + } + public interface FormattedMessage : GLib.Object { public abstract string get_sender_plain(); public abstract string get_sender_full(); public abstract string get_sender_id(); public abstract string get_conversation_id(); public abstract string get_message_plain(); public abstract string get_time_plain(); - - public abstract bool is_conference_message(); - public abstract Gdk.Pixbuf get_sender_image(); - public abstract bool equals_sender(IMessage m); - } - - public class Message : IMessage, ILoggedMessage, Object { - public GLib.DateTime timestamp { get; protected set; } - public MessageDirection message_direction { get; protected set; } - public bool important { get; set; default = false; } - public bool is_action { get; set; default = false; } - public bool received { get; set; } - - public unowned IContact from { get; protected set; } - public unowned IContact to { get; protected set; } - public string message { get; protected set; } - public uint32 message_id { get; set; } - - public Message.outgoing(IContact receiver, string message, GLib.DateTime timestamp = new GLib.DateTime.now_local()) { - this.message_direction = MessageDirection.OUTGOING; - this.from = null; - this.to = receiver; - this.message = message; - this.timestamp = timestamp; - } - - public Message.incoming(IContact sender, string message, GLib.DateTime timestamp = new GLib.DateTime.now_local()) { - this.message_direction = MessageDirection.INCOMING; - this.from = sender; - this.to = null; - this.message = message; - this.timestamp = timestamp; - } - - public string get_sender_plain() { - if (from == null) { - return _("me"); - } else { - return from.get_name_string(); - } - } - - public string get_sender_full() { - return get_sender_plain(); - } - - public string get_sender_id() { - return (from == null) ? to.get_id() : from.get_id(); - } - - public string get_conversation_id() { - return get_sender_id(); - } - - public string get_message_plain() { - return message; - } - - public string get_time_plain() { - return timestamp.format("%c"); - } - - public bool is_conference_message() { - return false; - } - - public Gdk.Pixbuf get_sender_image() { - return from != null ? from.get_image() : pixbuf_from_resource(R.icons.default_contact); - } - - public bool equals_sender(IMessage m) { - return m is Message && from == (m as Message).from; - } } } diff --git a/src/core/NotificationListener.vala b/src/core/NotificationListener.vala index c76e8ef..b4715f4 100644 --- a/src/core/NotificationListener.vala +++ b/src/core/NotificationListener.vala @@ -22,7 +22,7 @@ namespace Venom { public interface NotificationListener : GLib.Object { public abstract bool show_notifications { get; set; } public abstract bool play_sound_notifications { get; set; } - public abstract void on_unread_message(IMessage message, IContact sender); + public abstract void on_unread_message(FormattedMessage message, IContact sender); public abstract void on_friend_request(FriendRequest friend_request); public abstract void on_filetransfer(FileTransfer transfer, IContact sender); public abstract void on_conference_invite(ConferenceInvite invite); @@ -33,14 +33,14 @@ namespace Venom { public bool show_notifications { get; set; default = true; } public bool play_sound_notifications { get; set; default = true; } private const int PIXBUF_SIZE = 48; - private ILogger logger; + private Logger logger; private Canberra.Context context; private static string message_id = "new-message"; private static string friend_request_id = "new-friend-request"; private static string transfer_id = "new-transfer"; private static string invite_id = "new-invite"; - public NotificationListenerImpl(ILogger logger) { + public NotificationListenerImpl(Logger logger) { this.logger = logger; var result = Canberra.Context.create(out context); if (result != 0) { @@ -67,7 +67,7 @@ namespace Venom { return pixbuf.scale_simple(PIXBUF_SIZE, PIXBUF_SIZE, Gdk.InterpType.BILINEAR); } - public virtual void on_unread_message(IMessage message, IContact sender) { + public virtual void on_unread_message(FormattedMessage message, IContact sender) { logger.d("on_unread_message"); if (!show_notifications || !sender.show_notifications()) { return; diff --git a/src/core/ObservableList.vala b/src/core/ObservableList.vala index b50e9ad..14befcb 100644 --- a/src/core/ObservableList.vala +++ b/src/core/ObservableList.vala @@ -34,11 +34,12 @@ namespace Venom { } } - public void set_collection(Gee.Collection collection) { - this.list.clear(); - foreach (var item in collection) { - this.list.add(item); - } + public void set_collection(Gee.Traversable iterable) { + list.clear(); + iterable.@foreach((item) => { + list.add(item); + return true; + }); } public void append(GLib.Object item) { @@ -59,6 +60,10 @@ namespace Venom { return list.size; } + public Gee.Iterable get_all() { + return list; + } + public uint index(GLib.Object item) { return (uint) list.index_of(item); } @@ -103,10 +108,10 @@ namespace Venom { public class LazyObservableListModel : ObservableListModel { private const int NUM_ENTRIES_PER_ITERATION = 10; - protected ILogger logger; + protected Logger logger; private uint index = 0; private bool initialized = false; - public LazyObservableListModel(ILogger logger, ObservableList list, Cancellable? cancellable = null) { + public LazyObservableListModel(Logger logger, ObservableList list, Cancellable? cancellable = null) { base(list); this.logger = logger; initialize.begin(cancellable); @@ -134,6 +139,7 @@ namespace Venom { protected override void on_added(GLib.Object item, uint index) { if (!initialized) { // ignore while not initialized + stderr.printf("not initialized, dropping....\n"); return; } items_changed(index, 0, 1); diff --git a/src/core/Profile.vala b/src/core/Profile.vala new file mode 100644 index 0000000..5b7e313 --- /dev/null +++ b/src/core/Profile.vala @@ -0,0 +1,180 @@ +/* + * Profile.vala + * + * Copyright (C) 2018 Venom authors and contributors + * + * This file is part of Venom. + * + * Venom is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Venom is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Venom. If not, see . + */ + + errordomain ErrorEncryption { + CREATE, + ENCRYPT, + DECRYPT + } + +namespace Venom { + public class Profile : GLib.Object { + public string name { get; set; } + public string dir { get; set; } + public string toxfile { get; set; } + public string windowstatefile { get; set; } + public string dbfile { get; set; } + public string accelsfile { get; set; } + public bool is_encrypted { get; set; } + public ToxEncryptSave.PassKey? pass_key = null; + + public Profile(string path, string username) { + name = username; + dir = path; + toxfile = Path.build_filename(dir, name + ".tox"); + windowstatefile = Path.build_filename(dir, name + ".json"); + dbfile = Path.build_filename(dir, name + ".db"); + accelsfile = Path.build_filename(dir, name + ".accels"); + is_encrypted = false; + } + + public uint8[] ? load_sessiondata() throws Error { + if (!FileUtils.test(toxfile, FileTest.EXISTS)) { + return null; + } + + uint8[] data; + FileUtils.get_data(toxfile, out data); + + if (pass_key != null) { + return decrypt(data); + } + return data; + } + + public string get_db_key() { + if (pass_key == null) { + return ""; + } + unowned uint8[] data = (uint8[]) pass_key; + data.length = 64; + return Tools.bin_to_hexstring(data[32:64]); + } + + public void save_sessiondata(uint8[] sessiondata) throws Error { + if (pass_key != null) { + ToxEncryptSave.ErrEncryption err; + var data = pass_key.encrypt(sessiondata, out err); + if (err != ToxEncryptSave.ErrEncryption.OK) { + throw new ErrorEncryption.ENCRYPT("Failed to encrypt data: " + err.to_string()); + } + FileUtils.set_data(toxfile, data); + } else { + FileUtils.set_data(toxfile, sessiondata); + } + } + + public bool test_is_encrypted() { + var file = File.new_for_path(toxfile); + uint8[] contents; + try { + file.load_contents(null, out contents, null); + } catch (Error e) { + return false; + } + return ToxEncryptSave.is_data_encrypted(contents); + } + + private uint8[] decrypt(uint8[] data) throws Error { + ToxEncryptSave.ErrDecryption err_decrypt; + var plain = pass_key.decrypt(data, out err_decrypt); + if (err_decrypt != ToxEncryptSave.ErrDecryption.OK) { + throw new ErrorEncryption.DECRYPT("Decryption failed: " + err_decrypt.to_string()); + } + return plain; + } + + public uint8[] load(string password) throws Error { + var data = load_sessiondata(); + if (is_encrypted) { + ToxEncryptSave.ErrGetSalt err_salt; + var salt = ToxEncryptSave.get_salt(data, out err_salt); + if (err_salt != ToxEncryptSave.ErrGetSalt.OK) { + throw new ErrorEncryption.DECRYPT("Retrieving salt failed: " + err_salt.to_string()); + } + + ToxEncryptSave.ErrKeyDerivation err_deriv; + pass_key = new ToxEncryptSave.PassKey.derive_with_salt(password.data, salt, out err_deriv); + if (err_deriv != ToxEncryptSave.ErrKeyDerivation.OK || pass_key == null) { + throw new ErrorEncryption.DECRYPT("PassKey derivation failed: " + err_deriv.to_string()); + } + + try { + return decrypt(data); + } catch (Error e) { + pass_key = null; + throw e; + } + } + return data; + } + + public bool is_sane() { + var baseprof = GLib.Path.build_filename(dir, name); + return exists(baseprof + ".tox") && exists(baseprof + ".db"); + } + + public static Profile create(string path, string username, string password) throws Error { + var profile = new Profile(path, username); + + if (password.length > 0) { + ToxEncryptSave.ErrKeyDerivation err; + profile.pass_key = new ToxEncryptSave.PassKey.derive(password.data, out err); + if (err != ToxEncryptSave.ErrKeyDerivation.OK) { + throw new ErrorEncryption.CREATE("PassKey derivation failed: " + err.to_string()); + } + profile.is_encrypted = true; + } + + return profile; + } + + public static bool is_username_available(string path, string username) { + var baseprof = GLib.Path.build_filename(path, username); + return (!exists(baseprof + ".tox") && !exists(baseprof + ".db") + && !exists(baseprof + ".json") && !exists(baseprof + ".accels")); + } + + private static bool exists(string path) { + return GLib.FileUtils.test(path, GLib.FileTest.EXISTS); + } + + public static Gee.Iterable scan_profiles(Logger logger, string directory) { + var profiles = new Gee.LinkedList(); + try { + var dir = Dir.open(directory, 0); + string? name = null; + + while ((name = dir.read_name()) != null) { + var path = Path.build_filename(directory, name); + if (name.has_suffix(".tox") && FileUtils.test(path, FileTest.IS_REGULAR)) { + var profile = new Profile(directory, name.substring(0, name.last_index_of(".tox"))); + profile.is_encrypted = profile.test_is_encrypted(); + profiles.add(profile); + } + } + } catch (FileError e) { + logger.w("When scanning profiles: " + e.message); + } + return profiles; + } + } +} diff --git a/src/core/R.vala b/src/core/R.vala index d9ac2d2..aff8b42 100644 --- a/src/core/R.vala +++ b/src/core/R.vala @@ -72,14 +72,11 @@ namespace Venom { } public sealed class ConstantsResource { - public string user_config_dir { get; set; default = GLib.Environment.get_user_config_dir(); } public string downloads_dir { get; set; default = GLib.Environment.get_user_special_dir(GLib.UserDirectory.DOWNLOAD); } - public string profile_name { get; set; default = "tox"; } - public string avatars_folder() { return GLib.Path.build_filename(user_config_dir, "tox", "avatars"); } - public string window_state_filename() { return GLib.Path.build_filename(user_config_dir, "tox", profile_name + ".json"); } - public string db_filename() { return GLib.Path.build_filename(user_config_dir, "tox", profile_name + ".db"); } - public string tox_data_filename() { return GLib.Path.build_filename(user_config_dir, "tox", profile_name + ".data"); } - public string accels_filename() { return GLib.Path.build_filename(user_config_dir, "tox", profile_name + ".accels"); } + public string default_profile_dir() { return GLib.Path.build_filename(GLib.Environment.get_user_data_dir(), "tox"); } + public string default_global_dir() { return GLib.Path.build_filename(GLib.Environment.get_user_config_dir(), "tox"); } + public string default_global_settings() { return GLib.Path.build_filename(default_global_dir(), "venom.json"); } + public string avatars_folder() { return GLib.Path.build_filename(GLib.Environment.get_user_config_dir(), "tox", "avatars"); } public string icons_prefix() { return "/com/github/naxuroqa/venom/icons/scalable/status/"; } public string icons_suffix() { return ".svg"; } public string app_id() { return "com.github.naxuroqa.venom"; } diff --git a/src/core/Tools.vala b/src/core/Tools.vala index 7295a83..2bac395 100644 --- a/src/core/Tools.vala +++ b/src/core/Tools.vala @@ -28,7 +28,7 @@ namespace Venom { File path = File.new_for_path(pathname); if (!path.query_exists()) { DirUtils.create_with_parents(pathname, mode); - // Logger.log(LogLevel.INFO, "created directory " + pathname); + // CommandLineLogger.log(LogLevel.INFO, "created directory " + pathname); } } @@ -70,7 +70,7 @@ namespace Venom { if (u != (unichar) (-1)) { sb.append_unichar(u); } else { - // Logger.log(LogLevel.WARNING, "Invalid UTF-8 character detected"); + // CommandLineLogger.log(LogLevel.WARNING, "Invalid UTF-8 character detected"); } } return sb.str; @@ -108,7 +108,7 @@ namespace Venom { try { _action_regex = new GLib.Regex("^/(?P\\S+)(\\s+(?P.+))?$"); } catch (GLib.RegexError e) { - // Logger.log(LogLevel.ERROR, "Can't create action regex: " + e.message); + // CommandLineLogger.log(LogLevel.ERROR, "Can't create action regex: " + e.message); } } return _action_regex; @@ -121,7 +121,7 @@ namespace Venom { try { _uri_regex = new GLib.Regex("(?[a-z]+://\\S*)"); } catch (GLib.RegexError e) { - // Logger.log(LogLevel.ERROR, "Can't create uri regex: " + e.message); + // CommandLineLogger.log(LogLevel.ERROR, "Can't create uri regex: " + e.message); } } return _uri_regex; diff --git a/src/core/UserInfo.vala b/src/core/UserInfo.vala index 9cb15cc..1d1bbfa 100644 --- a/src/core/UserInfo.vala +++ b/src/core/UserInfo.vala @@ -20,6 +20,12 @@ */ namespace Venom { + public enum UserStatus { + NONE, + AWAY, + BUSY + } + public interface UserInfo : GLib.Object { public signal void info_changed(); @@ -40,12 +46,12 @@ namespace Venom { hash = new GLib.Bytes(new uint8[] {}); } - public void set_from_pixbuf(ILogger logger, Gdk.Pixbuf pixbuf) { + public void set_from_pixbuf(Logger logger, Gdk.Pixbuf pixbuf) { this.hash = new GLib.Bytes(new uint8[] {}); this.pixbuf = pixbuf; } - public void set_from_data(ILogger logger, uint8[] data, Gdk.Pixbuf? pixbuf = null) throws Error { + public void set_from_data(Logger logger, uint8[] data, Gdk.Pixbuf? pixbuf = null) throws Error { this.hash = new GLib.Bytes(ToxCore.Tox.hash(data)); if (pixbuf != null) { this.pixbuf = pixbuf; diff --git a/src/core/WidgetFactory.vala b/src/core/WidgetFactory.vala index bf1a0c7..694de9a 100644 --- a/src/core/WidgetFactory.vala +++ b/src/core/WidgetFactory.vala @@ -20,38 +20,38 @@ */ namespace Venom.Factory { - public interface IWidgetFactory : Object { - public abstract ApplicationWindow createApplicationWindow(Gtk.Application application, ToxSession session, IDhtNodeDatabase node_database, ISettingsDatabase settings_database, IContactDatabase contact_database); - public abstract ILogger createLogger(); - public abstract Gtk.Dialog createAboutDialog(); - public abstract IDatabaseFactory createDatabaseFactory(); - public abstract SettingsWidget createSettingsWidget(ApplicationWindow? app_window, ISettingsDatabase database, IDhtNodeDatabase nodeDatabase); + public interface WidgetFactory : Object { + public abstract ApplicationWindow create_application_window(Gtk.Application application, ToxSession session, Profile profile, NospamRepository nospam_repository, FriendRequestRepository friend_request_repository, MessageRepository message_repository, DhtNodeRepository node_database, ISettingsDatabase settings_database, ContactRepository contact_repository); + public abstract Logger create_logger(); + public abstract Gtk.Dialog create_about_dialog(); + public abstract DatabaseFactory create_database_factory(); + public abstract SettingsWidget create_settings_widget(ApplicationWindow? app_window, ISettingsDatabase database, DhtNodeRepository nodeRepository); } - public class WidgetFactory : IWidgetFactory, Object { - private ILogger logger; + public class DefaultWidgetFactory : WidgetFactory, Object { + private Logger logger; private Gtk.Dialog about_dialog; private ApplicationWindow app_window; - public ApplicationWindow createApplicationWindow(Gtk.Application application, ToxSession session, IDhtNodeDatabase node_database, ISettingsDatabase settings_database, IContactDatabase contact_database) { + public ApplicationWindow create_application_window(Gtk.Application application, ToxSession session, Profile profile, NospamRepository nospam_repository, FriendRequestRepository friend_request_repository, MessageRepository message_repository, DhtNodeRepository node_database, ISettingsDatabase settings_database, ContactRepository contact_repository) { if (app_window == null) { - app_window = new ApplicationWindow(application, this, session, node_database, settings_database, contact_database); + app_window = new ApplicationWindow(application, this, session, profile, nospam_repository, friend_request_repository, message_repository, node_database, settings_database, contact_repository); } return app_window; } - public ILogger createLogger() { + public Logger create_logger() { if (logger == null) { - logger = new Logger(); + logger = new CommandLineLogger(); } return logger; } - public IDatabaseFactory createDatabaseFactory() { + public DatabaseFactory create_database_factory() { return new SqliteWrapperFactory(); } - public Gtk.Dialog createAboutDialog() { + public Gtk.Dialog create_about_dialog() { if (about_dialog == null) { about_dialog = new AboutDialog(logger); about_dialog.modal = true; @@ -60,8 +60,8 @@ namespace Venom.Factory { return about_dialog; } - public SettingsWidget createSettingsWidget(ApplicationWindow? app_window, ISettingsDatabase settingsDatabase, IDhtNodeDatabase nodeDatabase) { - return new SettingsWidget(createLogger(), app_window, settingsDatabase, nodeDatabase); + public SettingsWidget create_settings_widget(ApplicationWindow? app_window, ISettingsDatabase settingsDatabase, DhtNodeRepository nodeRepository) { + return new SettingsWidget(create_logger(), app_window, settingsDatabase, nodeRepository); } } diff --git a/src/db/DatabaseInterfaces.vala b/src/db/DatabaseInterfaces.vala index 0cdf227..9535564 100644 --- a/src/db/DatabaseInterfaces.vala +++ b/src/db/DatabaseInterfaces.vala @@ -1,7 +1,7 @@ /* * DatabaseInterfaces.vala * - * Copyright (C) 2017 Venom authors and contributors + * Copyright (C) 2017-2018 Venom authors and contributors * * This file is part of Venom. * @@ -20,7 +20,7 @@ */ namespace Venom { - public interface ISettingsDatabase : Object { + public interface ISettingsDatabase : GLib.Object { public abstract bool enable_dark_theme { get; set; } public abstract bool enable_animations { get; set; } public abstract bool enable_logging { get; set; } @@ -28,9 +28,7 @@ namespace Venom { public abstract bool enable_tray { get; set; } public abstract bool enable_tray_minimize { get; set; } public abstract bool enable_notify { get; set; } - public abstract bool enable_infinite_log { get; set; } public abstract bool enable_send_typing { get; set; } - public abstract int days_to_log { get; set; } public abstract bool enable_proxy { get; set; } public abstract bool enable_custom_proxy { get; set; } public abstract string custom_proxy_host { get; set; } @@ -48,36 +46,45 @@ namespace Venom { public abstract void save(); } - public interface IDhtNodeDatabase : Object { - public abstract void insertDhtNode(string key, string address, uint port, bool isBlocked, string owner, string location); - public abstract List getDhtNodes(IDhtNodeFactory factory); - public abstract void deleteDhtNode(string key); - } + public interface Specification : GLib.Object {} - public interface IDhtNodeFactory : Object { - public abstract IDhtNode createDhtNode(string key, string address, uint port, bool blocked, string owner, string location); + public interface DhtNodeRepository : GLib.Object { + public abstract void create(DhtNode node); + public abstract void read(DhtNode node); + public abstract void update(DhtNode node); + public abstract void delete (DhtNode node); + public abstract Gee.Iterable query_all(); } - public interface ILoggedMessage : Object {} + public interface MessageRepository : GLib.Object { + public abstract void create(Message message); + public abstract void update(Message message); + public abstract Gee.Iterable query_all_for_contact(IContact contact); + } - public interface ILoggedMessageFactory : Object { - public abstract ILoggedMessage createLoggedMessage(string userId, string contactId, string message, DateTime time, bool outgoing); + public interface FriendRequestRepository : GLib.Object { + public abstract void create(FriendRequest friend_request); + public abstract void delete (FriendRequest friend_request); + public abstract Gee.Iterable query_all(); } - public interface IMessageDatabase : Object { - public abstract void insertMessage(string userId, string contactId, string message, DateTime time, bool outgoing); - public abstract List retrieveMessages(string userId, string contactId, ILoggedMessageFactory messageFactory); - public abstract void deleteMessagesBefore(DateTime date); + public interface ContactRepository : GLib.Object { + public abstract void create(IContact contact); + public abstract void read(IContact contact); + public abstract void update(IContact contact); + public abstract void delete (IContact contact); } - public interface IContactDatabase : Object { - public abstract void loadContactData(string userId, IContactData contactData); - public abstract void saveContactData(string userId, string note, string alias, bool isBlocked, string group); - public abstract void deleteContactData(string userId); + public class Nospam : GLib.Object { + public int id { get; set; } + public int nospam { get; set; } + public DateTime timestamp { get; set; } } - public interface IContactData : Object { - public abstract void saveContactData(string note, string alias, bool isBlocked, string group); + public interface NospamRepository : GLib.Object { + public abstract void create(Nospam friend_request); + public abstract void delete (Nospam friend_request); + public abstract Gee.Iterable query_all(); } public errordomain DatabaseStatementError { @@ -89,7 +96,8 @@ namespace Venom { public errordomain DatabaseError { OPEN, - QUERY + QUERY, + EXEC } public enum DatabaseResult { @@ -101,10 +109,10 @@ namespace Venom { OTHER } - public interface IDatabase : Object { + public interface Database : GLib.Object { } - public interface IDatabaseStatement : Object { + public interface DatabaseStatement : GLib.Object { public abstract DatabaseResult step() throws DatabaseStatementError; public abstract void bind_text(string key, string val) throws DatabaseStatementError; public abstract void bind_int64(string key, int64 val) throws DatabaseStatementError; @@ -115,29 +123,31 @@ namespace Venom { public abstract int column_int(int key) throws DatabaseStatementError; public abstract bool column_bool(int key) throws DatabaseStatementError; public abstract void reset(); - public abstract IDatabaseStatementBuilder builder(); + public abstract DatabaseStatementBuilder builder(); } - public interface IDatabaseStatementBuilder : Object { - public abstract IDatabaseStatementBuilder step() throws DatabaseStatementError; - public abstract IDatabaseStatementBuilder bind_text(string key, string val) throws DatabaseStatementError; - public abstract IDatabaseStatementBuilder bind_int64(string key, int64 val) throws DatabaseStatementError; - public abstract IDatabaseStatementBuilder bind_int(string key, int val) throws DatabaseStatementError; - public abstract IDatabaseStatementBuilder bind_bool(string key, bool val) throws DatabaseStatementError; - public abstract IDatabaseStatementBuilder reset(); + public interface DatabaseStatementBuilder : GLib.Object { + public abstract DatabaseStatementBuilder step() throws DatabaseStatementError; + public abstract DatabaseStatementBuilder bind_text(string key, string val) throws DatabaseStatementError; + public abstract DatabaseStatementBuilder bind_int64(string key, int64 val) throws DatabaseStatementError; + public abstract DatabaseStatementBuilder bind_int(string key, int val) throws DatabaseStatementError; + public abstract DatabaseStatementBuilder bind_bool(string key, bool val) throws DatabaseStatementError; + public abstract DatabaseStatementBuilder reset(); } - public interface IDatabaseFactory : Object { - public abstract IDatabase createDatabase(string path) throws DatabaseError; - public abstract IDatabaseStatementFactory createStatementFactory(IDatabase database); + public interface DatabaseFactory : GLib.Object { + public abstract Database create_database(string path, string key) throws DatabaseError; + public abstract DatabaseStatementFactory create_statement_factory(Database database); - public abstract IDhtNodeDatabase createNodeDatabase(IDatabaseStatementFactory factory, ILogger logger) throws DatabaseStatementError; - public abstract IContactDatabase createContactDatabase(IDatabaseStatementFactory factory, ILogger logger) throws DatabaseStatementError; - public abstract IMessageDatabase createMessageDatabase(IDatabaseStatementFactory factory, ILogger logger) throws DatabaseStatementError; - public abstract ISettingsDatabase createSettingsDatabase(IDatabaseStatementFactory factory, ILogger logger) throws DatabaseStatementError; + public abstract DhtNodeRepository create_node_repository(DatabaseStatementFactory factory, Logger logger) throws DatabaseStatementError; + public abstract ContactRepository create_contact_repository(DatabaseStatementFactory factory, Logger logger) throws DatabaseStatementError; + public abstract MessageRepository create_message_repository(DatabaseStatementFactory factory, Logger logger) throws DatabaseStatementError; + public abstract ISettingsDatabase create_settings_database(DatabaseStatementFactory factory, Logger logger) throws DatabaseStatementError; + public abstract FriendRequestRepository create_friend_request_repository(DatabaseStatementFactory factory, Logger logger) throws DatabaseStatementError; + public abstract NospamRepository create_nospam_repository(DatabaseStatementFactory factory, Logger logger) throws DatabaseStatementError; } - public interface IDatabaseStatementFactory : Object { - public abstract IDatabaseStatement createStatement(string zSql) throws DatabaseStatementError; + public interface DatabaseStatementFactory : GLib.Object { + public abstract DatabaseStatement create_statement(string zSql) throws DatabaseStatementError; } } diff --git a/src/db/SqliteSettingsDatabase.vala b/src/db/SqliteSettingsDatabase.vala index 69126b4..450c995 100644 --- a/src/db/SqliteSettingsDatabase.vala +++ b/src/db/SqliteSettingsDatabase.vala @@ -28,9 +28,7 @@ namespace Venom { public bool enable_tray { get; set; default = false; } public bool enable_tray_minimize { get; set; default = false; } public bool enable_notify { get; set; default = false; } - public bool enable_infinite_log { get; set; default = true; } public bool enable_send_typing { get; set; default = false; } - public int days_to_log { get; set; default = 180; } public bool enable_proxy { get; set; default = true; } public bool enable_custom_proxy { get; set; default = false; } public string custom_proxy_host { get; set; default = "localhost"; } @@ -68,21 +66,21 @@ namespace Venom { private static string STATEMENT_SELECT_SETTINGS = "SELECT * FROM Settings WHERE id = (%s)".printf(TABLE_ID); - private IDatabaseStatement insertStatement; - private IDatabaseStatement selectStatement; + private DatabaseStatement insertStatement; + private DatabaseStatement selectStatement; - private ILogger logger; + private Logger logger; - public SqliteSettingsDatabase(IDatabaseStatementFactory statementFactory, ILogger logger) throws DatabaseStatementError { + public SqliteSettingsDatabase(DatabaseStatementFactory statementFactory, Logger logger) throws DatabaseStatementError { this.logger = logger; statementFactory - .createStatement(CREATE_TABLE_SETTINGS) + .create_statement(CREATE_TABLE_SETTINGS) .step(); - insertStatement = statementFactory.createStatement(STATEMENT_INSERT_SETTINGS); - selectStatement = statementFactory.createStatement(STATEMENT_SELECT_SETTINGS); + insertStatement = statementFactory.create_statement(STATEMENT_INSERT_SETTINGS); + selectStatement = statementFactory.create_statement(STATEMENT_SELECT_SETTINGS); logger.d("SqliteSettingsDatabase created."); } diff --git a/src/db/SqliteWrapper.vala b/src/db/SqliteWrapper.vala index 3f50ef7..011ab69 100644 --- a/src/db/SqliteWrapper.vala +++ b/src/db/SqliteWrapper.vala @@ -1,7 +1,7 @@ /* * SqliteWrapper.vala * - * Copyright (C) 2013-2018 Venom authors and contributors + * Copyright (C) 2013-2018 Venom authors and contributors * * This file is part of Venom. * @@ -21,42 +21,54 @@ namespace Venom { - public class SqliteWrapperFactory : IDatabaseFactory, Object { - public IDatabase createDatabase(string path) throws DatabaseError { - return new SqliteDatabaseWrapper(path); + public class SqliteWrapperFactory : DatabaseFactory, Object { + public Database create_database(string path, string key) throws DatabaseError { + var update = new SqliteDatabaseV1(null); + return new SqliteDatabaseWrapper(path, key, update); } - - public IDatabaseStatementFactory createStatementFactory(IDatabase database) { + public DatabaseStatementFactory create_statement_factory(Database database) { return new SqliteStatementFactory(database as SqliteDatabaseWrapper); } - - public IDhtNodeDatabase createNodeDatabase(IDatabaseStatementFactory factory, ILogger logger) throws DatabaseStatementError { - return new SqliteDhtNodeDatabase(factory, logger); + public DhtNodeRepository create_node_repository(DatabaseStatementFactory factory, Logger logger) throws DatabaseStatementError { + return new SqliteDhtNodeRepository(factory, logger); } - public IContactDatabase createContactDatabase(IDatabaseStatementFactory factory, ILogger logger) throws DatabaseStatementError { - return new SqliteContactDatabase(factory, logger); + public ContactRepository create_contact_repository(DatabaseStatementFactory factory, Logger logger) throws DatabaseStatementError { + return new SqliteContactRepository(factory, logger); } - public IMessageDatabase createMessageDatabase(IDatabaseStatementFactory factory, ILogger logger) throws DatabaseStatementError { - return new SqliteMessageDatabase(factory, logger); + public MessageRepository create_message_repository(DatabaseStatementFactory factory, Logger logger) throws DatabaseStatementError { + return new SqliteMessageRepository(factory, logger); } - - public ISettingsDatabase createSettingsDatabase(IDatabaseStatementFactory factory, ILogger logger) throws DatabaseStatementError { + public ISettingsDatabase create_settings_database(DatabaseStatementFactory factory, Logger logger) throws DatabaseStatementError { return new SqliteSettingsDatabase(factory, logger); } + public FriendRequestRepository create_friend_request_repository(DatabaseStatementFactory factory, Logger logger) throws DatabaseStatementError { + return new SqliteFriendRequestRepository(factory, logger); + } + public NospamRepository create_nospam_repository(DatabaseStatementFactory factory, Logger logger) throws DatabaseStatementError { + return new SqliteNospamRepository(factory, logger); + } } - public class SqliteStatementFactory : IDatabaseStatementFactory, Object { + public class SqliteStatementFactory : DatabaseStatementFactory, Object { private SqliteDatabaseWrapper database; public SqliteStatementFactory(SqliteDatabaseWrapper database) { this.database = database; } - public IDatabaseStatement createStatement(string zSql) throws DatabaseStatementError { + public DatabaseStatement create_statement(string zSql) throws DatabaseStatementError { return new SqliteStatementWrapper(database, zSql); } + + public SqliteQueryResult query_database(string sql) throws DatabaseError { + return database.query(sql); + } + + public int64 last_insert_rowid() { + return database.last_insert_rowid(); + } } - public class SqliteStatementWrapper : IDatabaseStatement, Object { + public class SqliteStatementWrapper : DatabaseStatement, Object { private Sqlite.Statement statement; public SqliteStatementWrapper(SqliteDatabaseWrapper database, string zSql) throws DatabaseStatementError { @@ -135,55 +147,164 @@ namespace Venom { statement.reset(); } - public IDatabaseStatementBuilder builder() { + public DatabaseStatementBuilder builder() { return new Builder(this); } - public class Builder : IDatabaseStatementBuilder, Object { - private IDatabaseStatement statement; - public Builder(IDatabaseStatement statement) { + public class Builder : DatabaseStatementBuilder, Object { + private DatabaseStatement statement; + public Builder(DatabaseStatement statement) { this.statement = statement; } - public IDatabaseStatementBuilder step() throws DatabaseStatementError { + public DatabaseStatementBuilder step() throws DatabaseStatementError { statement.step(); return this; } - public IDatabaseStatementBuilder bind_text(string key, string val) throws DatabaseStatementError { + public DatabaseStatementBuilder bind_text(string key, string val) throws DatabaseStatementError { statement.bind_text(key, val); return this; } - public IDatabaseStatementBuilder bind_int64(string key, int64 val) throws DatabaseStatementError { + public DatabaseStatementBuilder bind_int64(string key, int64 val) throws DatabaseStatementError { statement.bind_int64(key, val); return this; } - public IDatabaseStatementBuilder bind_int(string key, int val) throws DatabaseStatementError { + public DatabaseStatementBuilder bind_int(string key, int val) throws DatabaseStatementError { statement.bind_int(key, val); return this; } - public IDatabaseStatementBuilder bind_bool(string key, bool val) throws DatabaseStatementError { + public DatabaseStatementBuilder bind_bool(string key, bool val) throws DatabaseStatementError { statement.bind_bool(key, val); return this; } - public IDatabaseStatementBuilder reset() { + public DatabaseStatementBuilder reset() { statement.reset(); return this; } } } - public class SqliteDatabaseWrapper : IDatabase, Object { + public interface SqlSpecification : GLib.Object { + public abstract string create_statement(SqliteStatementFactory statement_factory); + } + + public interface SqliteDatabaseUpdate : GLib.Object { + public abstract void update_database(SqliteDatabaseWrapper database) throws DatabaseError; + } + + public class SqliteDatabaseV1 : SqliteDatabaseUpdate, GLib.Object { + private SqliteDatabaseUpdate? next_update; + public SqliteDatabaseV1(SqliteDatabaseUpdate? next_update) { + this.next_update = next_update; + } + public void update_database(SqliteDatabaseWrapper database) throws DatabaseError { + if (database.version == 0) { + database.query( + """ + DROP TABLE IF EXISTS Contacts; + DROP TABLE IF EXISTS Nodes; + PRAGMA user_version=1; + """ + ); + } + if (next_update != null) { + next_update.update_database(database); + } + } + } + + public class SqliteQueryResultRow { + public int n_columns; + public string[] values; + public string[] column_names; + public SqliteQueryResultRow(int n_columns, string[] values, string[] column_names) { + this.n_columns = n_columns; + this.values = new string[n_columns]; + this.column_names = new string[n_columns]; + for(var i = 0; i < n_columns; i++) { + this.values[i] = values[i]; + this.column_names[i] = column_names[i]; + } + } + } + + public class SqliteQueryResult { + public SqliteQueryResultRow[] rows; + public SqliteQueryResult(SqliteQueryResultRow[] rows) { + this.rows = rows; + } + + public string to_string() { + var str = new StringBuilder(); + foreach(var row in rows) { + for (var i = 0; i < row.n_columns; i++) { + str.append("[%s] %s\n".printf(row.column_names[i], row.values[i])); + } + } + return str.str; + } + } + + public class SqliteQuery { + private string query; + public SqliteQuery(string query) { + this.query = query; + } + public SqliteQueryResult exec(Sqlite.Database db) throws DatabaseError { + string errmsg; + SqliteQueryResultRow[] rows = {}; + var result = db.exec(query, (n_columns, values, column_names) => { + rows += new SqliteQueryResultRow(n_columns, values, column_names); + return 0; + }, out errmsg); + + if (result != Sqlite.OK) { + throw new DatabaseError.EXEC("Cannot execute query: " + errmsg); + } + return new SqliteQueryResult(rows); + } + } + + public class SqliteDatabaseWrapper : Database, Object { private Sqlite.Database database; + private int _version = 0; + private string key = ""; public Sqlite.Database handle { get { return database; } } - public SqliteDatabaseWrapper(string path) throws DatabaseError { + public int version { + get { return _version; } + } + + public SqliteQueryResult query(string sql) throws DatabaseError { + return (new SqliteQuery(sql)).exec(database); + } + + public int64 last_insert_rowid() { + return database.last_insert_rowid(); + } + + public SqliteDatabaseWrapper(string path, string key, SqliteDatabaseUpdate updater) throws DatabaseError { var result = Sqlite.Database.open_v2(path, out database); if (result != Sqlite.OK) { throw new DatabaseError.OPEN("Cannot open sqlite database: " + database.errmsg()); } + + if (key.length > 0) { + query(@"PRAGMA key = \"x'$key'\";"); + query(@"SELECT count(*) FROM sqlite_master;"); + } + + try { + var version = query("PRAGMA user_version;"); + _version = int.parse(version.rows[0].values[0]); + } catch (DatabaseError e) { + stdout.printf(""); + } + + updater.update_database(this); } } } diff --git a/src/meson.build b/src/meson.build index e858290..78eb74d 100644 --- a/src/meson.build +++ b/src/meson.build @@ -11,7 +11,7 @@ gee_dep = dependency('gee-0.8') gspell_dep = dependency('gspell-1') canberra_dep = dependency('libcanberra') gmodule_dep = dependency('gmodule-2.0') -sqlite_dep = dependency('sqlite3') +sqlcipher_dep = dependency('sqlcipher') json_dep = dependency('json-glib-1.0') soup_dep = dependency('libsoup-2.4') tox_dep = dependency('toxcore') @@ -39,12 +39,13 @@ venom_source = files( 'core/Contact.vala', 'core/FileIO.vala', 'core/FileTransfer.vala', + 'core/GlobalSettings.vala', 'core/Identicon.vala', - 'core/Interfaces.vala', 'core/Logger.vala', 'core/Message.vala', 'core/NotificationListener.vala', 'core/ObservableList.vala', + 'core/Profile.vala', 'core/R.vala', 'core/TimeStamp.vala', 'core/Tools.vala', @@ -60,20 +61,22 @@ venom_source = files( 'portal/Screenshot.vala', 'tox/Conference.vala', 'tox/ConferenceMessage.vala', - 'tox/ContactDatabase.vala', 'tox/DhtNode.vala', - 'tox/DhtNodeDatabase.vala', 'tox/FriendRequest.vala', - 'tox/JsonWebDhtNodeDatabase.vala', - 'tox/MessageDatabase.vala', - 'tox/SqliteDhtNodeDatabase.vala', + 'tox/JsonWebDhtNodeUpdater.vala', + 'tox/SqliteContactRepository.vala', + 'tox/SqliteDhtNodeRepository.vala', + 'tox/SqliteFriendRequestRepository.vala', + 'tox/SqliteMessageRepository.vala', + 'tox/SqliteNospamRepository.vala', + 'tox/StaticDhtNodeUpdater.vala', 'tox/ToxAdapterConferenceListener.vala', 'tox/ToxAdapterFriendListener.vala', 'tox/ToxAdapterFiletransferListener.vala', 'tox/ToxAdapterSelfListener.vala', 'tox/ToxContact.vala', + 'tox/ToxMessage.vala', 'tox/ToxSession.vala', - 'tox/ToxSessionIO.vala', 'tox/ToxSessionThread.vala', 'undo/SimpleUndoStack.vala', 'undo/TextBufferUndoBinding.vala', @@ -97,8 +100,11 @@ venom_source = files( 'view/FileTransferWidget.vala', 'view/FriendInfoWidget.vala', 'view/FriendRequestWidget.vala', + 'view/InAppNotification.vala', + 'view/LoginWidget.vala', 'view/MessageWidget.vala', 'view/NodeWidget.vala', + 'view/NospamEntry.vala', 'view/PeerEntry.vala', 'view/SettingsWidget.vala', 'view/UserInfoWidget.vala', @@ -114,7 +120,7 @@ venom_source = files( 'viewmodel/UserInfoViewModel.vala' ) -venom_deps = [gtk_dep, gio_dep, gmodule_dep, gee_dep, sqlite_dep, json_dep, tox_dep, config_dep, soup_dep, posix_dep, gspell_dep, canberra_dep] +venom_deps = [gtk_dep, gio_dep, gmodule_dep, gee_dep, sqlcipher_dep, json_dep, tox_dep, config_dep, soup_dep, posix_dep, gspell_dep, canberra_dep] venom_lib = static_library('venom', [venom_source, venom_ui_resources, venom_icons_resources], dependencies : venom_deps diff --git a/src/plugin/Plugin.vala b/src/plugin/Plugin.vala index a5fb85d..d18530d 100644 --- a/src/plugin/Plugin.vala +++ b/src/plugin/Plugin.vala @@ -27,7 +27,7 @@ namespace Venom { public interface Plugin : GLib.Object { public abstract PluginState get_state(); - public abstract void activate(ILogger logger); - public abstract void deactiviate(ILogger logger); + public abstract void activate(Logger logger); + public abstract void deactiviate(Logger logger); } } diff --git a/src/plugin/Pluginregistrar.vala b/src/plugin/Pluginregistrar.vala index 6af2105..f217393 100644 --- a/src/plugin/Pluginregistrar.vala +++ b/src/plugin/Pluginregistrar.vala @@ -28,11 +28,11 @@ namespace Venom { private string path; private GLib.Type type; private GLib.Module module; - private ILogger logger; + private Logger logger; private delegate GLib.Type RegisterPluginFunction(GLib.Module module); - public Pluginregistrar(ILogger logger, string name) { + public Pluginregistrar(Logger logger, string name) { assert(GLib.Module.supported()); this.logger = logger; this.path = GLib.Module.build_path(GLib.Environment.get_variable("PWD"), name); diff --git a/src/testing/ExamplePlugin.vala b/src/testing/ExamplePlugin.vala index e07ab12..60807ea 100644 --- a/src/testing/ExamplePlugin.vala +++ b/src/testing/ExamplePlugin.vala @@ -27,10 +27,10 @@ namespace Venom { return PluginState.INACTIVE; } - public virtual void activate(ILogger logger) { + public virtual void activate(Logger logger) { logger.i("%s activated.".printf(TAG)); } - public virtual void deactiviate(ILogger logger) { + public virtual void deactiviate(Logger logger) { logger.i("%s deactivated.".printf(TAG)); } } diff --git a/src/testing/TestAbout.vala b/src/testing/TestAbout.vala index 35c4203..1ee7f0f 100644 --- a/src/testing/TestAbout.vala +++ b/src/testing/TestAbout.vala @@ -24,7 +24,7 @@ using Mock; using Testing; public class TestAbout : UnitTest { - private ILogger logger; + private Logger logger; public TestAbout() { add_func("/test_gtk", test_gtk); diff --git a/src/testing/TestDhtNodeDb.vala b/src/testing/TestDhtNodeDb.vala index 776084b..44176bf 100644 --- a/src/testing/TestDhtNodeDb.vala +++ b/src/testing/TestDhtNodeDb.vala @@ -24,115 +24,113 @@ using Mock; using Testing; public class TestDhtNodeDb : UnitTest { - private ILogger logger; - private IDatabaseStatementFactory statement_factory; - private IDatabaseStatement statement; - private IDhtNodeFactory node_factory; - private IDatabaseStatementBuilder builder; - - public TestDhtNodeDb() { - add_func("test_init", test_init); - add_func("test_real_dht_node_db", test_real_dht_node_db); - add_func("test_insert", test_insert); - add_func("test_select", test_select); - add_func("test_real_dht_node_db_insert_select", test_real_dht_node_db_insert_select); - add_func("test_real_dht_node_db_insert_select_duplicate", test_real_dht_node_db_insert_select_duplicate); - add_func("test_real_dht_node_db_delete", test_real_dht_node_db_delete); - add_func("test_real_dht_node_db_insert_delete_select", test_real_dht_node_db_insert_delete_select); - } - - public override void set_up() throws GLib.Error { - logger = new MockLogger(); - statement = new MockStatement(); - builder = new SqliteStatementWrapper.Builder(statement); - statement_factory = new MockStatementFactory(); - node_factory = new MockDhtNodeFactory(); - - mock().when(statement, "builder").then_return_object(builder); - mock().when(statement_factory, "createStatement", args().string("", any_string()).create()) - .then_return_object(statement); - } - - private void test_init() throws GLib.Error { - var database = new SqliteDhtNodeDatabase(statement_factory, logger); - Assert.assert_not_null(database); - } - - private void test_real_dht_node_db() throws GLib.Error { - var factory = new SqliteWrapperFactory(); - var db = factory.createDatabase(":memory:"); - var statement_factory = factory.createStatementFactory(db); - var node_database = new SqliteDhtNodeDatabase(statement_factory, logger); - Assert.assert_not_null(node_database); - } - - private void test_insert() throws GLib.Error { - var node_database = new SqliteDhtNodeDatabase(statement_factory, logger); - Assert.assert_not_null(node_database); - node_database.insertDhtNode("a", "b", 0, false, "c", "d"); - - mock().verify(statement, "bind_text", args().string("$KEY").string("a").create()); - mock().verify(statement, "bind_text", args().string("$ADDRESS").string("b").create()); - mock().verify(statement, "bind_text", args().string("$OWNER").string("c").create()); - mock().verify(statement, "bind_text", args().string("$LOCATION").string("d").create()); - mock().verify(statement, "bind_int", args().string("$PORT").int(0).create()); - mock().verify(statement, "bind_bool", args().string("$ISBLOCKED").bool(false).create()); - mock().verify_count(statement, "step", 2); - } - - private void test_select() throws GLib.Error { - var node_database = new SqliteDhtNodeDatabase(statement_factory, logger); - Assert.assert_not_null(node_database); - - var nodes = node_database.getDhtNodes(node_factory); - Assert.assert_equals(0, nodes.length()); - } - - private void test_real_dht_node_db_insert_select() throws GLib.Error { - var statement_factory = create_memory_stmt_factory(); - var node_database = new SqliteDhtNodeDatabase(statement_factory, logger); - Assert.assert_not_null(node_database); - - node_database.insertDhtNode("a", "b", 0, false, "c", "d"); - var nodes = node_database.getDhtNodes(node_factory); - Assert.assert_equals(1, nodes.length()); - } - - private void test_real_dht_node_db_insert_select_duplicate() throws GLib.Error { - var statement_factory = create_memory_stmt_factory(); - var node_database = new SqliteDhtNodeDatabase(statement_factory, logger); - Assert.assert_not_null(node_database); - - node_database.insertDhtNode("a", "b", 0, false, "c", "d"); - node_database.insertDhtNode("a", "e", 0, false, "f", "g"); - var nodes = node_database.getDhtNodes(node_factory); - Assert.assert_equals(1, nodes.length()); - } - - private void test_real_dht_node_db_delete() throws GLib.Error { - var statement_factory = create_memory_stmt_factory(); - var node_database = new SqliteDhtNodeDatabase(statement_factory, logger); - Assert.assert_not_null(node_database); - - node_database.deleteDhtNode("a"); - } - - private void test_real_dht_node_db_insert_delete_select() throws GLib.Error { - var statement_factory = create_memory_stmt_factory(); - var node_database = new SqliteDhtNodeDatabase(statement_factory, logger); - Assert.assert_not_null(node_database); - - node_database.insertDhtNode("a", "b", 0, false, "c", "d"); - node_database.deleteDhtNode("a"); - var nodes = node_database.getDhtNodes(node_factory); - Assert.assert_equals(0, nodes.length()); - } - - private IDatabaseStatementFactory create_memory_stmt_factory() { - var factory = new SqliteWrapperFactory(); - var db = factory.createDatabase(":memory:"); - return factory.createStatementFactory(db); - } + // private Logger logger; + // private DatabaseStatementFactory statement_factory; + // private DatabaseStatement statement; + // private DatabaseStatementBuilder builder; + // + // public TestDhtNodeDb() { + // add_func("test_init", test_init); + // add_func("test_real_dht_node_db", test_real_dht_node_db); + // add_func("test_insert", test_insert); + // add_func("test_select", test_select); + // add_func("test_real_dht_node_db_insert_select", test_real_dht_node_db_insert_select); + // add_func("test_real_dht_node_db_insert_select_duplicate", test_real_dht_node_db_insert_select_duplicate); + // add_func("test_real_dht_node_db_delete", test_real_dht_node_db_delete); + // add_func("test_real_dht_node_db_insert_delete_select", test_real_dht_node_db_insert_delete_select); + // } + // + // public override void set_up() throws GLib.Error { + // logger = new MockCommandLineLogger(); + // statement = new MockStatement(); + // builder = new SqliteStatementWrapper.Builder(statement); + // statement_factory = new MockStatementFactory(); + // + // mock().when(statement, "builder").then_return_object(builder); + // mock().when(statement_factory, "create_statement", args().string("", any_string()).create()) + // .then_return_object(statement); + // } + // + // private void test_init() throws GLib.Error { + // var database = new SqliteDhtNodeRepository(statement_factory, logger); + // Assert.assert_not_null(database); + // } + // + // private void test_real_dht_node_db() throws GLib.Error { + // var factory = new SqliteWrapperFactory(); + // var db = factory.create_database(":memory:"); + // var statement_factory = factory.create_statement_factory(db); + // var node_database = new SqliteDhtNodeRepository(statement_factory, logger); + // Assert.assert_not_null(node_database); + // } + // + // private void test_insert() throws GLib.Error { + // var node_database = new SqliteDhtNodeRepository(statement_factory, logger); + // Assert.assert_not_null(node_database); + // node_database.insertDhtNode("a", "b", 0, false, "c", "d"); + // + // mock().verify(statement, "bind_text", args().string("$KEY").string("a").create()); + // mock().verify(statement, "bind_text", args().string("$ADDRESS").string("b").create()); + // mock().verify(statement, "bind_text", args().string("$OWNER").string("c").create()); + // mock().verify(statement, "bind_text", args().string("$LOCATION").string("d").create()); + // mock().verify(statement, "bind_int", args().string("$PORT").int(0).create()); + // mock().verify(statement, "bind_bool", args().string("$ISBLOCKED").bool(false).create()); + // mock().verify_count(statement, "step", 2); + // } + // + // private void test_select() throws GLib.Error { + // var node_database = new SqliteDhtNodeRepository(statement_factory, logger); + // Assert.assert_not_null(node_database); + // + // var nodes = node_database.getDhtNodes(node_factory); + // Assert.assert_equals(0, nodes.length()); + // } + // + // private void test_real_dht_node_db_insert_select() throws GLib.Error { + // var statement_factory = create_memory_stmt_factory(); + // var node_database = new SqliteDhtNodeRepository(statement_factory, logger); + // Assert.assert_not_null(node_database); + // + // node_database.insertDhtNode("a", "b", 0, false, "c", "d"); + // var nodes = node_database.getDhtNodes(node_factory); + // Assert.assert_equals(1, nodes.length()); + // } + // + // private void test_real_dht_node_db_insert_select_duplicate() throws GLib.Error { + // var statement_factory = create_memory_stmt_factory(); + // var node_database = new SqliteDhtNodeRepository(statement_factory, logger); + // Assert.assert_not_null(node_database); + // + // node_database.insertDhtNode("a", "b", 0, false, "c", "d"); + // node_database.insertDhtNode("a", "e", 0, false, "f", "g"); + // var nodes = node_database.getDhtNodes(node_factory); + // Assert.assert_equals(1, nodes.length()); + // } + // + // private void test_real_dht_node_db_delete() throws GLib.Error { + // var statement_factory = create_memory_stmt_factory(); + // var node_database = new SqliteDhtNodeRepository(statement_factory, logger); + // Assert.assert_not_null(node_database); + // + // node_database.deleteDhtNode("a"); + // } + // + // private void test_real_dht_node_db_insert_delete_select() throws GLib.Error { + // var statement_factory = create_memory_stmt_factory(); + // var node_database = new SqliteDhtNodeRepository(statement_factory, logger); + // Assert.assert_not_null(node_database); + // + // node_database.insertDhtNode("a", "b", 0, false, "c", "d"); + // node_database.deleteDhtNode("a"); + // var nodes = node_database.getDhtNodes(node_factory); + // Assert.assert_equals(0, nodes.length()); + // } + // + // private DatabaseStatementFactory create_memory_stmt_factory() { + // var factory = new SqliteWrapperFactory(); + // var db = factory.create_database(":memory:"); + // return factory.create_statement_factory(db); + // } private static void main(string[] args) { Test.init(ref args); diff --git a/src/testing/TestIdenticon.vala b/src/testing/TestIdenticon.vala index fd3eccb..cecf5d0 100644 --- a/src/testing/TestIdenticon.vala +++ b/src/testing/TestIdenticon.vala @@ -24,7 +24,7 @@ using Mock; using Testing; public class TestIdenticon : UnitTest { - private ILogger logger; + private Logger logger; private uint8[] key; private uint8[] expected_hash; diff --git a/src/testing/TestJsonWebDhtNodeDb.vala b/src/testing/TestJsonWebDhtNodeDb.vala index 028b312..ea8997c 100644 --- a/src/testing/TestJsonWebDhtNodeDb.vala +++ b/src/testing/TestJsonWebDhtNodeDb.vala @@ -1,7 +1,7 @@ /* * TestJsonWebDhtNodeDb.vala * - * Copyright (C) 2017 Venom authors and contributors + * Copyright (C) 2018 Venom authors and contributors * * This file is part of Venom. * @@ -23,26 +23,14 @@ using Venom; using Mock; namespace TestJsonWebDhtNodeDb { - private static ILogger logger; - private static IDhtNodeFactory nodeFactory; - - private static void before() { - logger = new MockLogger(); - nodeFactory = new MockDhtNodeFactory(); - } - private static void testWebNodeDb() { - before(); - var database = new JsonWebDhtNodeDatabase(logger); + var database = new JsonWebDhtNodeUpdater(new MockLogger()); assert_nonnull(database); } private static void testWebNodeDbGet() { - before(); - var database = new JsonWebDhtNodeDatabase(logger); - assert_nonnull(database); - var nodes = database.getDhtNodes(nodeFactory); - assert(nodes.length() != 0); + var database = new JsonWebDhtNodeUpdater(new MockLogger()); + assert(database.get_dht_nodes().iterator().next()); } private static int main(string[] args) { diff --git a/src/testing/TestMessageDb.vala b/src/testing/TestMessageDb.vala deleted file mode 100644 index ec45086..0000000 --- a/src/testing/TestMessageDb.vala +++ /dev/null @@ -1,88 +0,0 @@ -/* - * TestMessageDb.vala - * - * Copyright (C) 2017-2018 Venom authors and contributors - * - * This file is part of Venom. - * - * Venom is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Venom is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with Venom. If not, see . - */ - -using Venom; -using Mock; -using Testing; - -public class TestMessageDb : UnitTest { - private IDatabaseStatementBuilder builder; - private IDatabaseStatement statement; - private ILoggedMessageFactory messageFactory; - private IDatabaseStatementFactory statementFactory; - private ILogger logger; - private ILoggedMessage message; - - public TestMessageDb() { - add_func("test_init", test_init); - add_func("test_insert", test_insert); - add_func("test_retrieve", test_retrieve); - } - - public override void set_up() throws Error { - statement = new MockStatement(); - builder = new SqliteStatementWrapper.Builder(statement); - when(statement, "builder") - .then_return_object(builder); - - statementFactory = new MockStatementFactory(); - when(statementFactory, "createStatement", args().string("", any_string()).create()) - .then_return_object(statement); - - message = new MockLoggedMessage(); - messageFactory = new MockLoggedMessageFactory(); - when(messageFactory, "createLoggedMessage") - .then_return_object(message); - - logger = new MockLogger(); - } - - private void test_init() throws Error { - var messageDatabase = new SqliteMessageDatabase(statementFactory, logger); - Assert.assert_not_null(messageDatabase); - } - - private void test_insert() throws Error { - var messageDatabase = new SqliteMessageDatabase(statementFactory, logger); - Assert.assert_not_null(messageDatabase); - var time = new DateTime.now_local(); - messageDatabase.insertMessage("a", "b", "c", time, true); - - mock().verify(statement, "bind_text", args().string("$USER").string("a").create()); - mock().verify(statement, "bind_text", args().string("$CONTACT").string("b").create()); - mock().verify(statement, "bind_text", args().string("$MESSAGE").string("c").create()); - mock().verify(statement, "bind_int64", args().string("$TIME").int64(time.to_unix()).create()); - mock().verify(statement, "bind_bool", args().string("$SENDER").bool(true).create()); - } - - private void test_retrieve() throws Error { - var messageDatabase = new SqliteMessageDatabase(statementFactory, logger); - Assert.assert_not_null(messageDatabase); - var messages = messageDatabase.retrieveMessages("", "", messageFactory); - Assert.assert_null(messages); - } - - private static void main(string[] args) { - Test.init(ref args); - var test = new TestMessageDb(); - Test.run(); - } -} diff --git a/src/testing/TestPlugin.vala b/src/testing/TestPlugin.vala index 0f707c0..4b5ca23 100644 --- a/src/testing/TestPlugin.vala +++ b/src/testing/TestPlugin.vala @@ -23,7 +23,7 @@ using Mock; namespace TestPlugin { private static void test_plugin() { - var logger = new MockLogger(); + var logger = new MockCommandLineLogger(); mock().expect_one_call(logger, "i"); try { var test_plugin = new Venom.Pluginregistrar(logger, "/../src/testing/libexample_plugin"); diff --git a/src/testing/TestSqliteDb.vala b/src/testing/TestSqliteDb.vala index 842fa7a..b9ec1d7 100644 --- a/src/testing/TestSqliteDb.vala +++ b/src/testing/TestSqliteDb.vala @@ -40,22 +40,22 @@ public class TestSqliteDb : UnitTest { private void test_sqlite_database_wrapper() throws Error { var factory = new SqliteWrapperFactory(); - var database = factory.createDatabase(":memory:"); + var database = factory.create_database(":memory:", ""); Assert.assert_not_null(database); } private void test_sqlite_statement_wrapper() throws Error { var factory = new SqliteWrapperFactory(); - var database = factory.createDatabase(":memory:"); - var stmtFactory = factory.createStatementFactory(database); - var statement = stmtFactory.createStatement(""); + var database = factory.create_database(":memory:", ""); + var stmtFactory = factory.create_statement_factory(database); + var statement = stmtFactory.create_statement(""); Assert.assert_not_null(statement); } private void test_sqlite_fail_real_database() throws Error { var factory = new SqliteWrapperFactory(); try { - factory.createDatabase("file://invalid_path"); + factory.create_database("file://invalid_path", ""); } catch (Error e) { return; } @@ -65,9 +65,9 @@ public class TestSqliteDb : UnitTest { private void test_sqlite_fail_real_statement() throws Error { var factory = new SqliteWrapperFactory(); try { - var database = factory.createDatabase(":memory:"); - var stmtFactory = factory.createStatementFactory(database); - var statement = stmtFactory.createStatement(""); + var database = factory.create_database(":memory:", ""); + var stmtFactory = factory.create_statement_factory(database); + var statement = stmtFactory.create_statement(""); statement.step(); } catch (Error e) { return; @@ -77,9 +77,9 @@ public class TestSqliteDb : UnitTest { private void test_sqlite_real_statement() throws Error { var factory = new SqliteWrapperFactory(); - var database = factory.createDatabase(":memory:"); - var stmtFactory = factory.createStatementFactory(database); - var statement = stmtFactory.createStatement("ANALYZE sqlite_master"); + var database = factory.create_database(":memory:", ""); + var stmtFactory = factory.create_statement_factory(database); + var statement = stmtFactory.create_statement("ANALYZE sqlite_master"); statement.step(); } diff --git a/src/testing/TestToxAdapterFiletransferListener.vala b/src/testing/TestToxAdapterFiletransferListener.vala index adc1fac..1592b35 100644 --- a/src/testing/TestToxAdapterFiletransferListener.vala +++ b/src/testing/TestToxAdapterFiletransferListener.vala @@ -25,7 +25,7 @@ using Testing; public class TestToxAdapterFiletransferListener : UnitTest { private ObservableList transfers; - private ILogger logger; + private Logger logger; private NotificationListener notification_listener; private ToxSession session; private ToxAdapterFiletransferListenerImpl listener; diff --git a/src/testing/meson.build b/src/testing/meson.build index 4b5b69d..bf12864 100644 --- a/src/testing/meson.build +++ b/src/testing/meson.build @@ -35,104 +35,43 @@ test_identicon = executable('test_identicon', ['TestIdenticon.vala', dependencies : [venom_dep, mox_dep] ) -test_observable_list = executable('test_observable_list', ['TestObservableList.vala', - join_paths(root_source_dir, 'core/ObservableList.vala'), - join_paths(root_source_dir, 'core/Interfaces.vala')], - dependencies : [gio_dep, gee_dep] +test_observable_list = executable('test_observable_list', ['TestObservableList.vala'], + dependencies : [venom_dep, mox_dep] ) test_mock = executable('test_mock', ['TestMock.vala'], - dependencies : [gio_dep, mox_dep] + dependencies : [venom_dep, mox_dep] ) test_tox_core = executable('test_tox_core', ['TestToxCore.vala'], - dependencies : [gio_dep, tox_dep] + dependencies : [venom_dep, mox_dep] ) -test_contact = executable('test_contact', ['TestContact.vala', - join_paths(root_source_dir, 'core/Contact.vala'), - join_paths(root_source_dir, 'core/Interfaces.vala'), - join_paths(root_source_dir, 'core/R.vala'), - join_paths(root_source_dir, 'core/Tools.vala'), - join_paths(root_source_dir, 'tox/ToxContact.vala')], - dependencies : [gtk_dep, config_dep, gdk_dep, tox_dep, mox_dep] +test_contact = executable('test_contact', ['TestContact.vala'], + dependencies : [venom_dep, mox_dep] ) test_about = executable('test_about', ['TestAbout.vala', - 'mocks/MockLogger.vala', - join_paths(root_source_dir, 'view/AboutDialog.vala')], - join_paths(root_source_dir, 'core/Interfaces.vala'), - dependencies : [gtk_dep, config_dep, mox_dep] - ) - -test_sqlite_db = executable('test_sqlite_db', ['TestSqliteDb.vala', - join_paths(root_source_dir, 'tox/ContactDatabase.vala'), - join_paths(root_source_dir, 'tox/MessageDatabase.vala'), - join_paths(root_source_dir, 'core/Interfaces.vala'), - join_paths(root_source_dir, 'db/DatabaseInterfaces.vala'), - join_paths(root_source_dir, 'db/SqliteWrapper.vala'), - join_paths(root_source_dir, 'tox/SqliteDhtNodeDatabase.vala'), - join_paths(root_source_dir, 'db/SqliteSettingsDatabase.vala') + 'mocks/MockLogger.vala' ], - dependencies : [gtk_dep, sqlite_dep, mox_dep] + dependencies : [venom_dep, mox_dep] ) -test_message_db = executable('test_message_db', ['TestMessageDb.vala', - 'mocks/MockLogger.vala', - 'mocks/MockDb.vala', - 'mocks/MockMessageDb.vala', - join_paths(root_source_dir, 'tox/MessageDatabase.vala'), - join_paths(root_source_dir, 'tox/SqliteDhtNodeDatabase.vala'), - join_paths(root_source_dir, 'db/SqliteSettingsDatabase.vala'), - join_paths(root_source_dir, 'db/SqliteWrapper.vala'), - join_paths(root_source_dir, 'tox/ContactDatabase.vala'), - join_paths(root_source_dir, 'core/Interfaces.vala'), - join_paths(root_source_dir, 'db/DatabaseInterfaces.vala') - ], - dependencies : [gtk_dep, sqlite_dep, mox_dep] +test_sqlite_db = executable('test_sqlite_db', ['TestSqliteDb.vala'], + dependencies : [venom_dep, mox_dep] ) test_dht_node_db = executable('test_dht_node_db', ['TestDhtNodeDb.vala', 'mocks/MockLogger.vala', - 'mocks/MockDb.vala', - 'mocks/MockDht.vala', - join_paths(root_source_dir, 'tox/DhtNodeDatabase.vala'), - join_paths(root_source_dir, 'db/SqliteWrapper.vala'), - join_paths(root_source_dir, 'tox/SqliteDhtNodeDatabase.vala'), - join_paths(root_source_dir, 'db/SqliteSettingsDatabase.vala'), - join_paths(root_source_dir, 'tox/MessageDatabase.vala'), - join_paths(root_source_dir, 'tox/ContactDatabase.vala'), - join_paths(root_source_dir, 'core/Interfaces.vala'), - join_paths(root_source_dir, 'db/DatabaseInterfaces.vala') + 'mocks/MockDb.vala' ], - dependencies : [gtk_dep, sqlite_dep, mox_dep] + dependencies : [venom_dep, mox_dep] ) test_json_web_dht_node_db = executable('test_json_web_dht_node_db', ['TestJsonWebDhtNodeDb.vala', - 'mocks/MockLogger.vala', - 'mocks/MockDht.vala', - join_paths(root_source_dir, 'tox/JsonWebDhtNodeDatabase.vala'), - join_paths(root_source_dir, 'tox/DhtNodeDatabase.vala'), - join_paths(root_source_dir, 'core/Interfaces.vala'), - join_paths(root_source_dir, 'db/DatabaseInterfaces.vala') - ], - dependencies : [gtk_dep, soup_dep, json_dep, gee_dep, mox_dep] - ) - -example_plugin_lib = library('example_plugin', ['ExamplePlugin.vala', - join_paths(root_source_dir, 'plugin/Plugin.vala'), - join_paths(root_source_dir, 'core/Interfaces.vala') - ], - dependencies : [gio_dep, gmodule_dep] - ) - -test_plugin = executable('test_plugin', ['TestPlugin.vala', - 'mocks/MockLogger.vala', - join_paths(root_source_dir, 'core/Interfaces.vala'), - join_paths(root_source_dir, 'plugin/Pluginregistrar.vala'), - join_paths(root_source_dir, 'plugin/Plugin.vala') + 'mocks/MockLogger.vala' ], - dependencies : [gio_dep, gmodule_dep, mox_dep] + dependencies : [venom_dep, mox_dep] ) test_tox_transfer = executable('test_tox_transfer', ['TestToxAdapterFiletransferListener.vala', @@ -158,8 +97,6 @@ test('test observable list', test_observable_list) test('test contact', test_contact) test('test about', test_about) test('test sqlite db', test_sqlite_db) -test('test message db', test_message_db) test('test dht node db', test_dht_node_db) test('test json web dht node db', test_json_web_dht_node_db) -test('test plugin', test_plugin) test('test undo', test_undo) diff --git a/src/testing/mocks/MockDb.vala b/src/testing/mocks/MockDb.vala index 9cf27a8..ba4c3fe 100644 --- a/src/testing/mocks/MockDb.vala +++ b/src/testing/mocks/MockDb.vala @@ -22,9 +22,9 @@ using Venom; namespace Mock { - public class MockDatabase : IDatabase, Object {} + public class MockDatabase : Database, Object {} - public class MockStatement : IDatabaseStatement, Object { + public class MockStatement : DatabaseStatement, Object { public DatabaseResult step() throws DatabaseStatementError { return (DatabaseResult) mock().actual_call(this, "step").get_int(); } @@ -83,60 +83,75 @@ namespace Mock { public void reset() { mock().actual_call(this, "reset"); } - public IDatabaseStatementBuilder builder() { - return (IDatabaseStatementBuilder) mock().actual_call(this, "builder").get_object(); + public DatabaseStatementBuilder builder() { + return (DatabaseStatementBuilder) mock().actual_call(this, "builder").get_object(); } } - public class MockDatabaseFactory : IDatabaseFactory, Object { - public IDatabase createDatabase(string path) throws DatabaseError { + public class MockDatabaseFactory : DatabaseFactory, Object { + public Database create_database(string path, string key) throws DatabaseError { var args = Arguments.builder() .string(path) + .string(key) .create(); - return (IDatabase) mock().actual_call(this, "createDatabase", args).get_object(); + return (Database) mock().actual_call(this, "create_database", args).get_object(); } - public IDatabaseStatementFactory createStatementFactory(IDatabase database) { + public DatabaseStatementFactory create_statement_factory(Database database) { var args = Arguments.builder() .object(database) .create(); - return (IDatabaseStatementFactory) mock().actual_call(this, "createStatementFactory", args).get_object(); + return (DatabaseStatementFactory) mock().actual_call(this, "create_statement_factory", args).get_object(); + } + public DhtNodeRepository create_node_repository(DatabaseStatementFactory factory, Logger logger) throws DatabaseStatementError { + var args = Arguments.builder() + .object(factory) + .object(logger) + .create(); + return (DhtNodeRepository) mock().actual_call(this, "create_node_repository", args).get_object(); + } + public ContactRepository create_contact_repository(DatabaseStatementFactory factory, Logger logger) throws DatabaseStatementError { + var args = Arguments.builder() + .object(factory) + .object(logger) + .create(); + return (ContactRepository) mock().actual_call(this, "createContactDatabase", args).get_object(); } - public IDhtNodeDatabase createNodeDatabase(IDatabaseStatementFactory factory, ILogger logger) throws DatabaseStatementError { + public MessageRepository create_message_repository(DatabaseStatementFactory factory, Logger logger) throws DatabaseStatementError { var args = Arguments.builder() .object(factory) .object(logger) .create(); - return (IDhtNodeDatabase) mock().actual_call(this, "createNodeDatabase", args).get_object(); + return (MessageRepository) mock().actual_call(this, "create_message_repository", args).get_object(); } - public IContactDatabase createContactDatabase(IDatabaseStatementFactory factory, ILogger logger) throws DatabaseStatementError { + public ISettingsDatabase create_settings_database(DatabaseStatementFactory factory, Logger logger) throws DatabaseStatementError { var args = Arguments.builder() .object(factory) .object(logger) .create(); - return (IContactDatabase) mock().actual_call(this, "createContactDatabase", args).get_object(); + return (ISettingsDatabase) mock().actual_call(this, "create_settings_database", args).get_object(); } - public IMessageDatabase createMessageDatabase(IDatabaseStatementFactory factory, ILogger logger) throws DatabaseStatementError { + public FriendRequestRepository create_friend_request_repository(DatabaseStatementFactory factory, Logger logger) throws DatabaseStatementError { var args = Arguments.builder() .object(factory) .object(logger) .create(); - return (IMessageDatabase) mock().actual_call(this, "createMessageDatabase", args).get_object(); + return (FriendRequestRepository) mock().actual_call(this, "create_friend_request_repository", args).get_object(); } - public ISettingsDatabase createSettingsDatabase(IDatabaseStatementFactory factory, ILogger logger) throws DatabaseStatementError { + public NospamRepository create_nospam_repository(DatabaseStatementFactory factory, Logger logger) throws DatabaseStatementError { var args = Arguments.builder() .object(factory) .object(logger) .create(); - return (ISettingsDatabase) mock().actual_call(this, "createSettingsDatabase", args).get_object(); + return (NospamRepository) mock().actual_call(this, "create_nospam_repository", args).get_object(); } } - public class MockStatementFactory : IDatabaseStatementFactory, Object { - public IDatabaseStatement createStatement(string statement) { + public class MockStatementFactory : DatabaseStatementFactory, Object { + public DatabaseStatement create_statement(string statement) { var args = Arguments.builder() .string(statement) .create(); - return (IDatabaseStatement) mock().actual_call(this, "createStatement", args).get_object(); + return (DatabaseStatement) mock().actual_call(this, "create_statement", args).get_object(); } } } diff --git a/src/testing/mocks/MockDht.vala b/src/testing/mocks/MockDht.vala deleted file mode 100644 index 74e734c..0000000 --- a/src/testing/mocks/MockDht.vala +++ /dev/null @@ -1,47 +0,0 @@ -/* - * DhtMock.vala - * - * Copyright (C) 2017-2018 Venom authors and contributors - * - * This file is part of Venom. - * - * Venom is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Venom is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with Venom. If not, see . - */ - -using Venom; - -namespace Mock { - public class MockDhtNode : IDhtNode, Object { - public string pub_key { get; set; } - public string host { get; set; } - public uint port { get; set; } - public bool is_blocked { get; set; } - public string maintainer { get; set; } - public string location { get; set; } - } - - public class MockDhtNodeFactory : IDhtNodeFactory, GLib.Object { - public IDhtNode createDhtNode(string address, string key, uint port, bool blocked, string owner, string location) { - var args = Arguments.builder() - .string(address) - .string(key) - .uint(port) - .bool(blocked) - .string(owner) - .string(location) - .create(); - return (IDhtNode) mock().actual_call(this, "createDhtNode", args).get_object(); - } - } -} diff --git a/src/testing/mocks/MockLogger.vala b/src/testing/mocks/MockLogger.vala index 56e4614..e562956 100644 --- a/src/testing/mocks/MockLogger.vala +++ b/src/testing/mocks/MockLogger.vala @@ -1,5 +1,5 @@ /* - * MockLogger.vala + * MockCommandLineLogger.vala * * Copyright (C) 2017-2018 Venom authors and contributors * @@ -22,7 +22,7 @@ using Venom; namespace Mock { - public class MockLogger : ILogger, Object { + public class MockLogger : Logger, Object { public MockLogger() {} public void d(string message) { mock().actual_call(this, "d", args().string(message).create()); } public void i(string message) { mock().actual_call(this, "i", args().string(message).create()); } diff --git a/src/testing/mocks/MockNotificationListener.vala b/src/testing/mocks/MockNotificationListener.vala index 3a95ba4..0ce84b6 100644 --- a/src/testing/mocks/MockNotificationListener.vala +++ b/src/testing/mocks/MockNotificationListener.vala @@ -25,7 +25,7 @@ namespace Mock { public class MockNotificationListener : NotificationListener, GLib.Object { public bool show_notifications { get; set; } public bool play_sound_notifications { get; set; } - public void on_unread_message(IMessage message, IContact c) { + public void on_unread_message(FormattedMessage message, IContact c) { var args = Arguments.builder() .object(message) .object(c) diff --git a/src/testing/mocks/MockToxSession.vala b/src/testing/mocks/MockToxSession.vala index f46153a..5d0b200 100644 --- a/src/testing/mocks/MockToxSession.vala +++ b/src/testing/mocks/MockToxSession.vala @@ -63,6 +63,12 @@ namespace Mock { .create(); mock().actual_call(this, "self_set_user_status", args); } + public void self_set_nospam(uint32 nospam) { + var args = Arguments.builder() + .uint(nospam) + .create(); + mock().actual_call(this, "self_set_nospam", args); + } public UserStatus self_get_user_status() { return (UserStatus) mock().actual_call(this, "self_get_user_status").get_int(); } @@ -105,6 +111,9 @@ namespace Mock { public void friend_add_norequest(uint8[] address) throws ToxError { mock().actual_call(this, "friend_add_norequest").get_throws(); } + public uint32 friend_add_norequest_direct(uint8[] address) throws ToxError { + return mock().actual_call(this, "friend_add_norequest_direct").get_int(); + } public void friend_delete(uint32 friend_number) throws ToxError { var args = Arguments.builder() .uint(friend_number) @@ -118,6 +127,13 @@ namespace Mock { .create(); mock().actual_call(this, "friend_send_message", args).get_throws(); } + public uint32 friend_send_message_direct(uint32 friend_number, string message) throws ToxError { + var args = Arguments.builder() + .uint(friend_number) + .string(message) + .create(); + return mock().actual_call(this, "friend_send_message_direct", args).get_int(); + } public string friend_get_name(uint32 friend_number) throws ToxError { var args = Arguments.builder() .uint(friend_number) diff --git a/src/tox/ConferenceMessage.vala b/src/tox/ConferenceMessage.vala index 257eff0..cb97f3b 100644 --- a/src/tox/ConferenceMessage.vala +++ b/src/tox/ConferenceMessage.vala @@ -20,43 +20,40 @@ */ namespace Venom { - public class ConferenceMessage : IMessage, Object { - public GLib.DateTime timestamp { get; protected set; } - public MessageDirection message_direction { get; protected set; } - public bool important { get; set; } - public bool is_action { get; set; } - public bool received { get; set; } + public class ConferenceMessage : Message, FormattedMessage, GLib.Object { + public int id { get; set; } + public int peers_index { get; set; } + public DateTime timestamp { get; set; } + public MessageSender sender { get; set; } + public string message { get; set; } + public bool is_action { get; set; } + public TransmissionState state { get; set; } - public string message { get; protected set; } - public string peer_name { get; protected set; } - public string peer_key { get; protected set; } + public string peer_name { get; set; } + public string peer_key { get; set; } - public unowned IContact from { get; protected set; } - public unowned IContact to { get; protected set; } + public unowned IContact conference { get; set; } - private unowned IContact conference; - - private ConferenceMessage(IContact conference, MessageDirection direction, string message, GLib.DateTime timestamp) { + public ConferenceMessage(IContact conference, MessageSender sender, string message, GLib.DateTime timestamp, bool is_action) { this.conference = conference; - this.message_direction = direction; + this.sender = sender; this.message = message; this.timestamp = timestamp; - this.important = false; - this.is_action = false; + this.is_action = is_action; } public ConferenceMessage.outgoing(IContact conference, string message, GLib.DateTime timestamp = new GLib.DateTime.now_local()) { - this(conference, MessageDirection.OUTGOING, message, timestamp); + this(conference, MessageSender.LOCAL, message, timestamp, false); } public ConferenceMessage.incoming(IContact conference, string peer_key, string peer_name, string message, GLib.DateTime timestamp = new GLib.DateTime.now_local()) { - this(conference, MessageDirection.INCOMING, message, timestamp); + this(conference, MessageSender.REMOTE, message, timestamp, false); this.peer_key = peer_key; this.peer_name = peer_name; } public string get_sender_plain() { - if (message_direction == MessageDirection.OUTGOING) { + if (sender == MessageSender.LOCAL) { return _("me"); } else { return peer_name; @@ -88,14 +85,14 @@ namespace Venom { } public Gdk.Pixbuf get_sender_image() { - if (message_direction == MessageDirection.OUTGOING) { + if (sender == MessageSender.LOCAL) { return pixbuf_from_resource(R.icons.default_contact); } var pub_key = Tools.hexstring_to_bin(peer_key); return Identicon.generate_pixbuf(pub_key); } - public bool equals_sender(IMessage m) { + public bool equals_sender(Message m) { return m is ConferenceMessage && ((ConferenceMessage) m).peer_key == peer_key; } } diff --git a/src/tox/ContactDatabase.vala b/src/tox/ContactDatabase.vala deleted file mode 100644 index 8589be8..0000000 --- a/src/tox/ContactDatabase.vala +++ /dev/null @@ -1,122 +0,0 @@ -/* - * ContactDatabase.vala - * - * Copyright (C) 2013-2017 Venom authors and contributors - * - * This file is part of Venom. - * - * Venom is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Venom is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with Venom. If not, see . - */ - -namespace Venom { - public class SqliteContactDatabase : IContactDatabase, Object { - private const string QUERY_TABLE_CONTACTS = """ - CREATE TABLE IF NOT EXISTS Contacts ( - id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, - key TEXT NOT NULL UNIQUE, - note TEXT NOT NULL, - alias TEXT NOT NULL, - isblocked INTEGER NOT NULL, - ingroup TEXT NOT NULL - ); - """; - private enum ContactColumn { - ID, - KEY, - NOTE, - ALIAS, - ISBLOCKED, - GROUP - } - - private const string TABLE_KEY = "$KEY"; - private const string TABLE_NOTE = "$NOTE"; - private const string TABLE_ALIAS = "$ALIAS"; - private const string TABLE_ISBLOCKED = "$ISBLOCKED"; - private const string TABLE_GROUP = "$GROUP"; - - private static string STATEMENT_INSERT_CONTACT = "INSERT OR REPLACE INTO Contacts (key, note, alias, isblocked, ingroup) VALUES (%s, %s, %s, %s, %s);".printf(TABLE_KEY, TABLE_NOTE, TABLE_ALIAS, TABLE_ISBLOCKED, TABLE_GROUP); - private static string STATEMENT_SELECT_CONTACT = "SELECT * FROM Contacts WHERE key = %s".printf(TABLE_KEY); - private static string STATEMENT_DELETE_CONTACT = "DELETE FROM Contacts WHERE key = %s".printf(TABLE_KEY); - - private IDatabaseStatement insertStatement; - private IDatabaseStatement selectStatement; - private IDatabaseStatement deleteStatement; - private ILogger logger; - - public SqliteContactDatabase(IDatabaseStatementFactory statementFactory, ILogger logger) throws DatabaseStatementError { - this.logger = logger; - statementFactory - .createStatement(QUERY_TABLE_CONTACTS) - .step(); - - insertStatement = statementFactory.createStatement(STATEMENT_INSERT_CONTACT); - selectStatement = statementFactory.createStatement(STATEMENT_SELECT_CONTACT); - deleteStatement = statementFactory.createStatement(STATEMENT_DELETE_CONTACT); - - logger.d("SqliteContactDatabase created."); - } - - ~SqliteContactDatabase() { - logger.d("SqliteContactDatabase destroyed."); - } - - public void loadContactData(string userId, IContactData contactData) { - try { - selectStatement.bind_text(TABLE_KEY, userId); - - if (selectStatement.step() == Sqlite.ROW) { - var note = selectStatement.column_text(ContactColumn.NOTE); - var alias = selectStatement.column_text(ContactColumn.ALIAS); - var isBlocked = selectStatement.column_bool(ContactColumn.ISBLOCKED); - var group = selectStatement.column_text(ContactColumn.GROUP); - contactData.saveContactData(note, alias, isBlocked, group); - } - } catch (DatabaseStatementError e) { - logger.e("Error reading contact information from sqlite database: " + e.message); - } - - selectStatement.reset(); - } - - public void saveContactData(string userId, string note, string alias, bool isBlocked, string group) { - try { - insertStatement.builder() - .bind_text(TABLE_KEY, userId) - .bind_text(TABLE_NOTE, note) - .bind_text(TABLE_ALIAS, alias) - .bind_bool(TABLE_ISBLOCKED, isBlocked) - .bind_text(TABLE_GROUP, group) - .step(); - } catch (DatabaseStatementError e) { - logger.e("Error writing contact information to sqlite database: " + e.message); - } finally { - insertStatement.reset(); - } - } - - public void deleteContactData(string userId) { - try { - deleteStatement.builder() - .bind_text(TABLE_KEY, userId) - .step(); - } catch (DatabaseStatementError e) { - logger.e("Error deleting contact information in sqlite database: " + e.message); - } finally { - deleteStatement.reset(); - } - } - - } -} diff --git a/src/tox/DhtNode.vala b/src/tox/DhtNode.vala index 50261b4..00d27df 100644 --- a/src/tox/DhtNode.vala +++ b/src/tox/DhtNode.vala @@ -1,7 +1,7 @@ /* * DhtNode.vala * - * Copyright (C) 2013-2017 Venom authors and contributors + * Copyright (C) 2013-2018 Venom authors and contributors * * This file is part of Venom. * @@ -20,13 +20,8 @@ */ namespace Venom { - public class DhtNodeFactory : IDhtNodeFactory, Object { - public IDhtNode createDhtNode(string key, string address, uint port, bool is_blocked, string owner, string location) { - return new DhtNode(key, address, port, is_blocked, owner, location); - } - } - - public class DhtNode : IDhtNode, Object { + public class DhtNode : GLib.Object { + public int id { get; set; } public string pub_key { get; set; } public string host { get; set; } public uint port { get; set; } @@ -34,7 +29,10 @@ namespace Venom { public string maintainer { get; set; } public string location { get; set; } - public DhtNode(string pub_key, string host, uint port = 33445, bool is_blocked = false, string maintainer = "", string location = "") { + public DhtNode() { + } + + public DhtNode.with_params(string pub_key, string host, uint port = 33445, bool is_blocked = false, string maintainer = "", string location = "") { this.pub_key = pub_key; this.host = host; this.port = port; @@ -42,5 +40,9 @@ namespace Venom { this.maintainer = maintainer; this.location = location; } + + public string to_string() { + return "%s:%u %s - %s / %s (blocked: %s)".printf(host, port, pub_key, maintainer, location, is_blocked ? "true" : "false"); + } } } diff --git a/src/tox/FriendRequest.vala b/src/tox/FriendRequest.vala index a035525..1da493c 100644 --- a/src/tox/FriendRequest.vala +++ b/src/tox/FriendRequest.vala @@ -1,7 +1,7 @@ /* * FriendRequest.vala * - * Copyright (C) 2018 Venom authors and contributors + * Copyright (C) 2018 Venom authors and contributors * * This file is part of Venom. * @@ -21,10 +21,14 @@ namespace Venom { public class FriendRequest : GLib.Object { + public int db_id { get; set; } public string id { get; set; } public string message { get; set; } public DateTime timestamp { get; set; } + public FriendRequest.empty() { + } + public FriendRequest(string id, string message) { this.id = id; this.message = message; diff --git a/src/tox/JsonWebDhtNodeDatabase.vala b/src/tox/JsonWebDhtNodeUpdater.vala similarity index 55% rename from src/tox/JsonWebDhtNodeDatabase.vala rename to src/tox/JsonWebDhtNodeUpdater.vala index 1cbf7b2..336ddbd 100644 --- a/src/tox/JsonWebDhtNodeDatabase.vala +++ b/src/tox/JsonWebDhtNodeUpdater.vala @@ -1,7 +1,7 @@ /* * JsonWebDhtNodeDatabase.vala * - * Copyright (C) 2017 Venom authors and contributors + * Copyright (C) 2017-2018 Venom authors and contributors * * This file is part of Venom. * @@ -20,28 +20,14 @@ */ namespace Venom { - public class JsonDhtNode : GLib.Object { - public string ipv4 { get; set; } - public string ipv6 { get; set; } - public int port { get; set; } - //public int[] tcp_ports { get; set; } - public string public_key { get; set; } - public string maintainer { get; set; } - public string location { get; set; } - public bool status_udp { get; set; } - public bool status_tcp { get; set; } - public string version { get; set; } - public string motd { get; set; } - public int last_ping { get; set; } - } - - public class JsonWebDhtNodeDatabase : IDhtNodeDatabase, GLib.Object { - private ILogger logger; - public JsonWebDhtNodeDatabase(ILogger logger) { + public class JsonWebDhtNodeUpdater : GLib.Object { + private Logger logger; + public JsonWebDhtNodeUpdater(Logger logger) { this.logger = logger; } - public List getDhtNodes(IDhtNodeFactory factory) { - var dhtNodes = new List(); + + public Gee.Iterable get_dht_nodes() { + var dht_nodes = new Gee.LinkedList(); var uri = "https://nodes.tox.chat/json"; @@ -60,21 +46,38 @@ namespace Venom { foreach (var node in nodes.get_elements()) { var json_node = Json.gobject_deserialize(typeof(JsonDhtNode), node) as JsonDhtNode; if (json_node.ipv4 != "-") { - dhtNodes.append(factory.createDhtNode(json_node.public_key, json_node.ipv4, json_node.port, false, json_node.maintainer, json_node.location)); + var dht_node = new DhtNode.with_params(json_node.public_key, json_node.ipv4, json_node.port, false, json_node.maintainer, json_node.location); + dht_nodes.add(dht_node); + } + if (json_node.ipv6 != "-") { + var dht_node = new DhtNode.with_params(json_node.public_key, json_node.ipv6, json_node.port, false, json_node.maintainer, json_node.location); + dht_nodes.add(dht_node); } //FIXME allow multiple addresses per pubkey in node database // if (json_node.ipv6 != "-") { - // dhtNodes.append(factory.createDhtNode(json_node.public_key, json_node.ipv6, json_node.port, false, json_node.maintainer, json_node.location)); + // dht_nodes.append(factory.createDhtNode(json_node.public_key, json_node.ipv6, json_node.port, false, json_node.maintainer, json_node.location)); // } } } catch (Error e) { logger.e("Failed to load dht nodes from uri: " + e.message); } - return dhtNodes; + return dht_nodes; } - public void insertDhtNode(string key, string address, uint port, bool isBlocked, string owner, string location) {} - public void deleteDhtNode(string key) {} + private class JsonDhtNode : GLib.Object { + public string ipv4 { get; set; } + public string ipv6 { get; set; } + public int port { get; set; } + //public int[] tcp_ports { get; set; } + public string public_key { get; set; } + public string maintainer { get; set; } + public string location { get; set; } + public bool status_udp { get; set; } + public bool status_tcp { get; set; } + public string version { get; set; } + public string motd { get; set; } + public int last_ping { get; set; } + } } } diff --git a/src/tox/MessageDatabase.vala b/src/tox/MessageDatabase.vala deleted file mode 100644 index c7c8b77..0000000 --- a/src/tox/MessageDatabase.vala +++ /dev/null @@ -1,128 +0,0 @@ -/* - * MessageDatabase.vala - * - * Copyright (C) 2013-2017 Venom authors and contributors - * - * This file is part of Venom. - * - * Venom is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Venom is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with Venom. If not, see . - */ - -namespace Venom { - public class SqliteMessageDatabase : IMessageDatabase, Object { - private enum HistoryColumn { - ID, - USER, - CONTACT, - MESSAGE, - TIME, - SENDER - } - - private const string TABLE_USER = "$USER"; - private const string TABLE_CONTACT = "$CONTACT"; - private const string TABLE_MESSAGE = "$MESSAGE"; - private const string TABLE_TIME = "$TIME"; - private const string TABLE_SENDER = "$SENDER"; - - private string STATEMENT_INSERT_HISTORY = "INSERT INTO History (userHash, contactHash, message, timestamp, issent) VALUES (%s, %s, %s, %s, %s);".printf(TABLE_USER, TABLE_CONTACT, TABLE_MESSAGE, TABLE_TIME, TABLE_SENDER); - private string STATEMENT_SELECT_HISTORY = "SELECT * FROM History WHERE userHash = %s AND contactHash = %s;".printf(TABLE_USER, TABLE_CONTACT); - private string STATEMENT_SANITIZE_DATABASE = "DELETE FROM History WHERE timestamp < %s;".printf(TABLE_TIME); - - private const string QUERY_TABLE_HISTORY = """ - CREATE TABLE IF NOT EXISTS History ( - id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, - userHash TEXT NOT NULL, - contactHash TEXT NOT NULL, - message TEXT NOT NULL, - timestamp INTEGER NOT NULL, - issent INTEGER NOT NULL - ); - """; - - private ILogger logger; - - private IDatabaseStatement insertStatement; - private IDatabaseStatement selectStatement; - private IDatabaseStatement sanitizeStatement; - - public SqliteMessageDatabase(IDatabaseStatementFactory statementFactory, ILogger logger) throws DatabaseStatementError { - this.logger = logger; - - statementFactory - .createStatement(QUERY_TABLE_HISTORY) - .step(); - - insertStatement = statementFactory.createStatement(STATEMENT_INSERT_HISTORY); - selectStatement = statementFactory.createStatement(STATEMENT_SELECT_HISTORY); - sanitizeStatement = statementFactory.createStatement(STATEMENT_SANITIZE_DATABASE); - logger.d("SQLite database created."); - } - - ~SqliteMessageDatabase() { - logger.d("SQLite database closed."); - } - - public void insertMessage(string userId, string contactId, string message, DateTime time, bool outgoing) { - try { - insertStatement.builder() - .bind_text(TABLE_USER, userId) - .bind_text(TABLE_CONTACT, contactId) - .bind_text(TABLE_MESSAGE, message) - .bind_int64(TABLE_TIME, time.to_unix()) - .bind_bool(TABLE_SENDER, outgoing) - .step(); - } catch (DatabaseStatementError e) { - logger.e("Error writing message to database: " + e.message); - } finally { - insertStatement.reset(); - } - } - - public List retrieveMessages(string userId, string contactId, ILoggedMessageFactory messageFactory) { - var messages = new List(); - try { - selectStatement.builder() - .bind_text(TABLE_USER, userId) - .bind_text(TABLE_CONTACT, contactId); - - while (selectStatement.step() == DatabaseResult.ROW) { - var messageString = selectStatement.column_text(HistoryColumn.MESSAGE); - var timestamp = selectStatement.column_int64(HistoryColumn.TIME); - var outgoing = selectStatement.column_bool(HistoryColumn.SENDER); - var sendTime = new DateTime.from_unix_local(timestamp); - var message = messageFactory.createLoggedMessage(userId, contactId, messageString, sendTime, outgoing); - messages.append(message); - } - } catch (DatabaseStatementError e) { - logger.e("Error retrieving messages from database: " + e.message); - } finally { - selectStatement.reset(); - } - return messages; - } - - public void deleteMessagesBefore(DateTime date) { - try { - sanitizeStatement.builder() - .bind_int64(TABLE_TIME, date.to_unix()) - .step(); - } catch (DatabaseStatementError e) { - logger.e("Error sanitizing database: " + e.message); - } finally { - sanitizeStatement.reset(); - } - } - } -} diff --git a/src/tox/SqliteContactRepository.vala b/src/tox/SqliteContactRepository.vala new file mode 100644 index 0000000..49419dc --- /dev/null +++ b/src/tox/SqliteContactRepository.vala @@ -0,0 +1,130 @@ +/* + * SqliteContactRepository.vala + * + * Copyright (C) 2013-2018 Venom authors and contributors + * + * This file is part of Venom. + * + * Venom is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Venom is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Venom. If not, see . + */ + +namespace Venom { + public class SqliteContactRepository : ContactRepository, Object { + private const string CREATE_TABLES = """ + CREATE TABLE IF NOT EXISTS peers ( + id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + key TEXT NOT NULL UNIQUE + ); + CREATE TABLE IF NOT EXISTS peer_settings ( + peers_index INTEGER PRIMARY KEY NOT NULL, + alias TEXT NOT NULL, + auto_accept_ci INTEGER NOT NULL, + auto_accept_ft INTEGER NOT NULL, + ft_directory TEXT NOT NULL, + notifications INTEGER NOT NULL + ); + """; + + private enum PeersColumn { ID, KEY } + private enum PeerSettingsColumn { PEER_KEY, ALIAS, AUTO_ACCEPT_CI, AUTO_ACCEPT_FT, FT_DIRECTORY, NOTIFICATIONS } + + private Logger logger; + + private SqliteStatementFactory statementFactory; + + public SqliteContactRepository(DatabaseStatementFactory statementFactory, Logger logger) throws DatabaseStatementError, DatabaseError { + this.logger = logger; + + this.statementFactory = (SqliteStatementFactory) statementFactory; + this.statementFactory.query_database(CREATE_TABLES); + + logger.d("SqliteContactRepository created."); + } + + public void create(IContact contact) { + logger.d("ContactRepository create"); + var c = contact as Contact; + var id = get_id(c); + if (id >= 0) { + logger.i("Found contact in database, restoring contact settings"); + c.db_id = id; + read(contact); + } else { + logger.i("Creating contact in database"); + statementFactory.create_statement("INSERT INTO peers (key) VALUES ($KEY);") + .builder() + .bind_text("$KEY", c.tox_id) + .step(); + c.db_id = (int) statementFactory.last_insert_rowid(); + update(contact); + } + } + + private int get_id(Contact contact) { + var key = contact.tox_id; + var query = statementFactory.query_database(@"SELECT (id) FROM peers WHERE key='$key';"); + if (query.rows != null && query.rows.length >= 1 && query.rows[0].n_columns > 0) { + return int.parse(query.rows[0].values[0]); + } + return -1; + } + + public void read(IContact contact) { + logger.d("ContactRepository read"); + var c = contact as Contact; + var stmt = statementFactory.create_statement("SELECT * FROM peer_settings WHERE peers_index = $PEER_INDEX"); + stmt.bind_int("$PEER_INDEX", c.db_id); + + if (stmt.step() == DatabaseResult.ROW) { + c.alias = stmt.column_text(PeerSettingsColumn.ALIAS); + c.auto_conference = stmt.column_bool(PeerSettingsColumn.AUTO_ACCEPT_CI); + c.auto_filetransfer = stmt.column_bool(PeerSettingsColumn.AUTO_ACCEPT_FT); + c.auto_location = stmt.column_text(PeerSettingsColumn.FT_DIRECTORY); + c._show_notifications = stmt.column_bool(PeerSettingsColumn.NOTIFICATIONS); + } else { + logger.e("Could not read contact"); + } + } + public void update(IContact contact) { + logger.d("ContactRepository update"); + var c = contact as Contact; + statementFactory.create_statement( + """INSERT OR REPLACE INTO peer_settings + (peers_index, alias, auto_accept_ci, auto_accept_ft, ft_directory, notifications) + VALUES ($PEER_INDEX, $ALIAS, $AUTO_CI, $AUTO_FT, $FT_DIR, $NOTIFICATIONS); + """) + .builder() + .bind_int("$PEER_INDEX", c.db_id) + .bind_text("$ALIAS", c.alias) + .bind_bool("$AUTO_CI", c.auto_conference) + .bind_bool("$AUTO_FT", c.auto_filetransfer) + .bind_text("$FT_DIR", c.auto_location) + .bind_bool("$NOTIFICATIONS", c._show_notifications) + .step(); + } + public void delete(IContact contact) { + logger.d("ContactRepository delete"); + var c = contact as Contact; + if (c.db_id >= 0) { + statementFactory.create_statement("DELETE FROM peer_settings WHERE peers_index=$PEER_INDEX;") + .builder() + .bind_int("$PEER_INDEX", c.db_id) + .step(); + } + } + ~SqliteContactRepository() { + logger.d("SqliteContactRepository destroyed."); + } + } +} diff --git a/src/tox/SqliteDhtNodeDatabase.vala b/src/tox/SqliteDhtNodeDatabase.vala deleted file mode 100644 index f451932..0000000 --- a/src/tox/SqliteDhtNodeDatabase.vala +++ /dev/null @@ -1,130 +0,0 @@ -/* - * SqliteDhtNodeDatabase.vala - * - * Copyright (C) 2017-2018 Venom authors and contributors - * - * This file is part of Venom. - * - * Venom is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Venom is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with Venom. If not, see . - */ - -namespace Venom { - public class SqliteDhtNodeDatabase : IDhtNodeDatabase, Object { - private const string CREATE_TABLE_NODES = """ - CREATE TABLE IF NOT EXISTS Nodes ( - id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, - key TEXT NOT NULL UNIQUE, - address TEXT NOT NULL, - port INTEGER NOT NULL, - isblocked INTEGER NOT NULL, - owner TEXT NOT NULL, - location TEXT NOT NULL - ); - """; - private enum NodeColumn { - ID, - KEY, - ADDRESS, - PORT, - ISBLOCKED, - OWNER, - LOCATION - } - - private const string TABLE_KEY = "$KEY"; - private const string TABLE_ADDRESS = "$ADDRESS"; - private const string TABLE_PORT = "$PORT"; - private const string TABLE_ISBLOCKED = "$ISBLOCKED"; - private const string TABLE_OWNER = "$OWNER"; - private const string TABLE_LOCATION = "$LOCATION"; - - private static string STATEMENT_INSERT_NODE = "INSERT OR REPLACE INTO Nodes (key, address, port, isblocked, owner, location) VALUES (%s, %s, %s, %s, %s, %s);".printf(TABLE_KEY, TABLE_ADDRESS, TABLE_PORT, TABLE_ISBLOCKED, TABLE_OWNER, TABLE_LOCATION); - private static string STATEMENT_SELECT_NODES = "SELECT * FROM Nodes"; - private static string STATEMENT_DELETE_NODE = "DELETE FROM Nodes WHERE key = %s".printf(TABLE_KEY); - - private IDatabaseStatement insertStatement; - private IDatabaseStatement selectStatement; - private IDatabaseStatement deleteStatement; - - private ILogger logger; - - public SqliteDhtNodeDatabase(IDatabaseStatementFactory statementFactory, ILogger logger) throws DatabaseStatementError { - this.logger = logger; - - statementFactory - .createStatement(CREATE_TABLE_NODES) - .step(); - - insertStatement = statementFactory.createStatement(STATEMENT_INSERT_NODE); - selectStatement = statementFactory.createStatement(STATEMENT_SELECT_NODES); - deleteStatement = statementFactory.createStatement(STATEMENT_DELETE_NODE); - - logger.d("SqliteDhtNodeDatabase created."); - } - - ~SqliteDhtNodeDatabase() { - logger.d("SqliteDhtNodeDatabase destroyed."); - } - - public List getDhtNodes(IDhtNodeFactory nodeFactory) { - var dhtNodes = new List(); - try { - while (selectStatement.step() == DatabaseResult.ROW) { - var key = selectStatement.column_text(NodeColumn.KEY); - var address = selectStatement.column_text(NodeColumn.ADDRESS); - var port = selectStatement.column_int(NodeColumn.PORT); - var isBlocked = selectStatement.column_bool(NodeColumn.ISBLOCKED); - var owner = selectStatement.column_text(NodeColumn.OWNER); - var location = selectStatement.column_text(NodeColumn.LOCATION); - var node = nodeFactory.createDhtNode(key, address, port, isBlocked, owner, location); - dhtNodes.append(node); - } - } catch (DatabaseStatementError e) { - logger.e("Could not read dht node from database: " + e.message); - } finally { - selectStatement.reset(); - } - return dhtNodes; - } - - public void insertDhtNode(string key, string address, uint port, bool isBlocked, string owner, string location) { - try { - insertStatement.builder() - .bind_text(TABLE_KEY, key) - .bind_text(TABLE_ADDRESS, address) - .bind_int(TABLE_PORT, (int) port) - .bind_bool(TABLE_ISBLOCKED, isBlocked) - .bind_text(TABLE_OWNER, owner) - .bind_text(TABLE_LOCATION, location) - .step(); - } catch (DatabaseStatementError e) { - logger.e("Could not insert Dht Node into database: " + e.message); - } finally { - insertStatement.reset(); - } - } - - public void deleteDhtNode(string key) { - try { - deleteStatement.builder() - .bind_text(TABLE_KEY, key) - .step(); - } catch (DatabaseStatementError e) { - logger.e("Could not delete Dht Node in database: " + e.message); - } finally { - deleteStatement.reset(); - } - } - } -} diff --git a/src/tox/SqliteDhtNodeRepository.vala b/src/tox/SqliteDhtNodeRepository.vala new file mode 100644 index 0000000..587ef87 --- /dev/null +++ b/src/tox/SqliteDhtNodeRepository.vala @@ -0,0 +1,147 @@ +/* + * SqliteDhtNodeRepository.vala + * + * Copyright (C) 2017-2018 Venom authors and contributors + * + * This file is part of Venom. + * + * Venom is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Venom is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Venom. If not, see . + */ + +namespace Venom { + public class SqliteDhtNodeRepository : DhtNodeRepository, Object { + private const string CREATE_TABLE_NODES = """ + CREATE TABLE IF NOT EXISTS nodes ( + id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + key TEXT NOT NULL, + address TEXT NOT NULL, + port INTEGER NOT NULL, + isblocked INTEGER NOT NULL, + owner TEXT NOT NULL, + location TEXT NOT NULL + ); + """; + private enum NodeColumn { ID, KEY, ADDRESS, PORT, ISBLOCKED, OWNER, LOCATION } + + private Logger logger; + private SqliteStatementFactory statementFactory; + + public SqliteDhtNodeRepository(DatabaseStatementFactory statementFactory, Logger logger) throws DatabaseStatementError { + this.logger = logger; + this.statementFactory = (SqliteStatementFactory) statementFactory; + + statementFactory + .create_statement(CREATE_TABLE_NODES) + .step(); + + logger.d("SqliteDhtNodeRepository created."); + } + + ~SqliteDhtNodeRepository() { + logger.d("SqliteDhtNodeRepository destroyed."); + } + + public void create(DhtNode node) { + var query_stmt = statementFactory.create_statement( + """ + SELECT (id) FROM nodes + WHERE key=$KEY AND address=$ADDRESS AND port=$PORT; + """); + query_stmt.builder() + .bind_text("$KEY", node.pub_key) + .bind_text("$ADDRESS", node.host) + .bind_int("$PORT", (int) node.port); + + if (query_stmt.step() == DatabaseResult.ROW) { + logger.d("Dht Node with key/address/port already known, ignoring."); + node.id = query_stmt.column_int(0); + } else { + statementFactory.create_statement( + """ + INSERT INTO nodes (key, address, port, isblocked, owner, location) + VALUES ($KEY, $ADDRESS, $PORT, $ISBLOCKED, $OWNER, $LOCATION); + """).builder() + .bind_text("$KEY", node.pub_key) + .bind_text("$ADDRESS", node.host) + .bind_int("$PORT", (int) node.port) + .bind_bool("$ISBLOCKED", node.is_blocked) + .bind_text("$OWNER", node.maintainer) + .bind_text("$LOCATION", node.location) + .step(); + node.id = (int) statementFactory.last_insert_rowid(); + } + + } + public void read(DhtNode node) { + var query_stmt = statementFactory.create_statement( + """ + SELECT * FROM nodes + WHERE id=$ID; + """ + ); + query_stmt.bind_int("$ID", node.id); + if (query_stmt.step() == DatabaseResult.ROW) { + node.pub_key = query_stmt.column_text(NodeColumn.KEY); + node.host = query_stmt.column_text(NodeColumn.ADDRESS); + node.port = query_stmt.column_int(NodeColumn.PORT); + node.is_blocked = query_stmt.column_bool(NodeColumn.ISBLOCKED); + node.maintainer = query_stmt.column_text(NodeColumn.OWNER); + node.location = query_stmt.column_text(NodeColumn.LOCATION); + } else { + logger.e("Can not find dht node with id %i".printf(node.id)); + } + } + public void update(DhtNode node) { + statementFactory.create_statement( + """ + UPDATE nodes + SET key=$KEY, address=$ADDRESS, port=$PORT, isblocked=$ISBLOCKED, owner=$OWNER, location=$LOCATION + WHERE id=$ID; + """).builder() + .bind_int("$ID", node.id) + .bind_text("$KEY", node.pub_key) + .bind_text("$ADDRESS", node.host) + .bind_int("$PORT", (int) node.port) + .bind_bool("$ISBLOCKED", node.is_blocked) + .bind_text("$OWNER", node.maintainer) + .bind_text("$LOCATION", node.location) + .step(); + } + public void delete(DhtNode node) { + statementFactory.create_statement( + """ + DELETE FROM nodes + WHERE id=$ID; + """).builder() + .bind_int("$ID", node.id) + .step(); + } + public Gee.Iterable query_all() { + var list = new Gee.LinkedList(); + var query_stmt = statementFactory.create_statement("SELECT * FROM nodes;"); + while (query_stmt.step() == DatabaseResult.ROW) { + var node = new DhtNode(); + node.id = query_stmt.column_int(NodeColumn.ID); + node.pub_key = query_stmt.column_text(NodeColumn.KEY); + node.host = query_stmt.column_text(NodeColumn.ADDRESS); + node.port = query_stmt.column_int(NodeColumn.PORT); + node.is_blocked = query_stmt.column_bool(NodeColumn.ISBLOCKED); + node.maintainer = query_stmt.column_text(NodeColumn.OWNER); + node.location = query_stmt.column_text(NodeColumn.LOCATION); + list.add(node); + } + return list; + } + } +} diff --git a/src/tox/SqliteFriendRequestRepository.vala b/src/tox/SqliteFriendRequestRepository.vala new file mode 100644 index 0000000..d47dc2a --- /dev/null +++ b/src/tox/SqliteFriendRequestRepository.vala @@ -0,0 +1,87 @@ +/* + * FriendRequestRepository.vala + * + * Copyright (C) 2018 Venom authors and contributors + * + * This file is part of Venom. + * + * Venom is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Venom is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Venom. If not, see . + */ + +namespace Venom { + public class SqliteFriendRequestRepository : FriendRequestRepository, Object { + private const string CREATE_TABLE_FRIEND_REQUESTS = """ + CREATE TABLE IF NOT EXISTS friend_requests ( + id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + key TEXT NOT NULL, + message TEXT NOT NULL, + timestamp INT64 NOT NULL + ); + """; + + private enum FriendRequestColumn { ID, KEY, MESSAGE, TIMESTAMP} + + private Logger logger; + private SqliteStatementFactory statement_factory; + + public SqliteFriendRequestRepository(DatabaseStatementFactory statement_factory, Logger logger) throws DatabaseStatementError { + this.logger = logger; + this.statement_factory = (SqliteStatementFactory) statement_factory; + + statement_factory + .create_statement(CREATE_TABLE_FRIEND_REQUESTS) + .step(); + + logger.d("SqliteFriendRequestRepository created."); + } + + ~SqliteFriendRequestRepository() { + logger.d("SqliteFriendRequestRepository destroyed."); + } + public void create(FriendRequest friend_request) { + statement_factory.create_statement( + """ + INSERT INTO friend_requests (key, message, timestamp) + VALUES ($KEY, $MESSAGE, $TIMESTAMP); + """).builder() + .bind_text("$KEY", friend_request.id) + .bind_text("$MESSAGE", friend_request.message) + .bind_int64("$TIMESTAMP", friend_request.timestamp.to_unix()) + .step(); + friend_request.db_id = (int) statement_factory.last_insert_rowid(); + } + public void delete(FriendRequest friend_request) { + statement_factory.create_statement( + """ + DELETE FROM friend_requests + WHERE id=$ID; + """).builder() + .bind_int("$ID", friend_request.db_id) + .step(); + } + public Gee.Iterable query_all() { + var list = new Gee.LinkedList(); + var query_stmt = statement_factory.create_statement("SELECT * FROM friend_requests;"); + while (query_stmt.step() == DatabaseResult.ROW) { + var friend_request = new FriendRequest.empty(); + friend_request.db_id = query_stmt.column_int(FriendRequestColumn.ID); + friend_request.id = query_stmt.column_text(FriendRequestColumn.KEY); + friend_request.message = query_stmt.column_text(FriendRequestColumn.MESSAGE); + friend_request.timestamp = new DateTime.from_unix_local(query_stmt.column_int64(FriendRequestColumn.TIMESTAMP)); + list.add(friend_request); + } + return list; + } + } +} diff --git a/src/tox/SqliteMessageRepository.vala b/src/tox/SqliteMessageRepository.vala new file mode 100644 index 0000000..7d27ed4 --- /dev/null +++ b/src/tox/SqliteMessageRepository.vala @@ -0,0 +1,134 @@ +/* + * MessageRepository.vala + * + * Copyright (C) 2018 Venom authors and contributors + * + * This file is part of Venom. + * + * Venom is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Venom is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Venom. If not, see . + */ + +namespace Venom { + public class SqliteMessageRepository : MessageRepository, Object { + private enum MessageColumn { ID, PEERS_INDEX, MESSAGE, STATE, IS_ACTION, TIMESTAMP, TYPE } + + private const string CREATE_TABLE_HISTORY = """ + CREATE TABLE IF NOT EXISTS messages ( + id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + peers_index INTEGER NOT NULL, + message TEXT NOT NULL, + state INTEGER NOT NULL, + is_action INTEGER NOT NULL, + timestamp INT64 NOT NULL, + type INTEGER NOT NULL + ); + """; + + private Logger logger; + private SqliteStatementFactory statement_factory; + private unowned GLib.HashTable contacts; + + public SqliteMessageRepository(DatabaseStatementFactory statement_factory, Logger logger) throws DatabaseStatementError { + this.statement_factory = (SqliteStatementFactory) statement_factory; + this.logger = logger; + + statement_factory + .create_statement(CREATE_TABLE_HISTORY) + .step(); + + logger.d("SqliteMessageRepository created."); + } + ~SqliteMessageRepository() { + logger.d("SqliteMessageRepository destroyed."); + } + private int get_peers_index(string tox_id) { + var stmt = statement_factory.create_statement(@"SELECT (id) FROM peers WHERE key='$tox_id';"); + if (stmt.step() == DatabaseResult.ROW) { + return stmt.column_int(0); + } + return -1; + } + public void set_contacts(GLib.HashTable contacts) { + this.contacts = contacts; + } + public void create(Message message) { + var m = (ToxMessage) message; + var peers_index = get_peers_index(m.contact.get_id()); + statement_factory.create_statement( + """ + INSERT INTO messages (peers_index, message, state, is_action, timestamp, type) + VALUES ($PEERS_INDEX, $MESSAGE, $STATE, $IS_ACTION, $TIMESTAMP, $TYPE); + """).builder() + .bind_int("$PEERS_INDEX", peers_index) + .bind_text("$MESSAGE", message.message) + .bind_int("$STATE", message.state) + .bind_bool("$IS_ACTION", message.is_action) + .bind_int64("$TIMESTAMP", message.timestamp.to_unix()) + .bind_int("$TYPE", message.sender) + .step(); + message.id = (int) statement_factory.last_insert_rowid(); + } + public void update(Message message) { + statement_factory.create_statement( + """ + UPDATE messages + SET message = $MESSAGE, + state = $STATE, + is_action = $IS_ACTION, + timestamp = $TIMESTAMP, + type = $TYPE + WHERE id = $ID; + """).builder() + .bind_int("$ID", message.id) + .bind_text("$MESSAGE", message.message) + .bind_int("$STATE", message.state) + .bind_bool("$IS_ACTION", message.is_action) + .bind_int64("$TIMESTAMP", message.timestamp.to_unix()) + .bind_int("$TYPE", message.sender) + .step(); + } + public Gee.Iterable query_all_for_contact(IContact contact) { + var id = contact.get_id(); + var stmt = statement_factory.create_statement( + """ + SELECT * FROM ( + SELECT messages.id, peers_index, message, state, is_action, timestamp, type + FROM messages + LEFT JOIN peers on peers.id = messages.peers_index + WHERE peers.key = $KEY + ORDER BY messages.id DESC LIMIT 50 + ) + ORDER BY id ASC; + """); + stmt.bind_text("$KEY", id); + var result = new Gee.LinkedList(); + while(stmt.step() == DatabaseResult.ROW) { + var msg_id = stmt.column_int(MessageColumn.ID); + var peers_index = stmt.column_int(MessageColumn.PEERS_INDEX); + var message = stmt.column_text(MessageColumn.MESSAGE); + var state = (TransmissionState) stmt.column_int(MessageColumn.STATE); + var is_action = stmt.column_bool(MessageColumn.IS_ACTION); + var timestamp = new DateTime.from_unix_local(stmt.column_int64(MessageColumn.TIMESTAMP)); + var type = (MessageSender) stmt.column_int(MessageColumn.TYPE); + var msg = new ToxMessage(contact, type, message, timestamp); + msg.id = msg_id; + msg.peers_index = peers_index; + msg.is_action = is_action; + msg.state = state; + result.add(msg); + } + return result; + } + } +} diff --git a/src/tox/SqliteNospamRepository.vala b/src/tox/SqliteNospamRepository.vala new file mode 100644 index 0000000..b4d1a1d --- /dev/null +++ b/src/tox/SqliteNospamRepository.vala @@ -0,0 +1,84 @@ +/* + * SqliteNospamRepository.vala + * + * Copyright (C) 2018 Venom authors and contributors + * + * This file is part of Venom. + * + * Venom is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Venom is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Venom. If not, see . + */ + +namespace Venom { + public class SqliteNospamRepository : NospamRepository, Object { + private const string CREATE_TABLE_NOSPAMS = """ + CREATE TABLE IF NOT EXISTS nospams ( + id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + nospam INTEGER NOT NULL, + timestamp INT64 NOT NULL + ); + """; + + private enum NospamColumn { ID, NOSPAM, TIMESTAMP } + + private Logger logger; + private SqliteStatementFactory statement_factory; + + public SqliteNospamRepository(DatabaseStatementFactory statement_factory, Logger logger) throws DatabaseStatementError { + this.logger = logger; + this.statement_factory = (SqliteStatementFactory) statement_factory; + + statement_factory + .create_statement(CREATE_TABLE_NOSPAMS) + .step(); + + logger.d("SqliteNospamRepository created."); + } + + ~SqliteNospamRepository() { + logger.d("SqliteNospamRepository destroyed."); + } + public void create(Nospam nospam) { + statement_factory.create_statement( + """ + INSERT INTO nospams (nospam, timestamp) + VALUES ($NOSPAM, $TIMESTAMP); + """).builder() + .bind_int("$NOSPAM", nospam.nospam) + .bind_int64("$TIMESTAMP", nospam.timestamp.to_unix()) + .step(); + nospam.id = (int) statement_factory.last_insert_rowid(); + } + public void delete (Nospam nospam) { + statement_factory.create_statement( + """ + DELETE FROM nospams + WHERE id=$ID; + """).builder() + .bind_int("$ID", nospam.id) + .step(); + } + public Gee.Iterable query_all() { + var list = new Gee.LinkedList(); + var query_stmt = statement_factory.create_statement("SELECT * FROM nospams;"); + while (query_stmt.step() == DatabaseResult.ROW) { + var nospam = new Nospam(); + nospam.id = query_stmt.column_int(NospamColumn.ID); + nospam.nospam = query_stmt.column_int(NospamColumn.NOSPAM); + nospam.timestamp = new DateTime.from_unix_local(query_stmt.column_int64(NospamColumn.TIMESTAMP)); + list.add(nospam); + } + return list; + } + } +} diff --git a/src/tox/DhtNodeDatabase.vala b/src/tox/StaticDhtNodeUpdater.vala similarity index 71% rename from src/tox/DhtNodeDatabase.vala rename to src/tox/StaticDhtNodeUpdater.vala index d9bd1ab..4ce7f78 100644 --- a/src/tox/DhtNodeDatabase.vala +++ b/src/tox/StaticDhtNodeUpdater.vala @@ -1,7 +1,7 @@ /* - * DhtNodeDatabase.vala + * StaticDhtNodeUpdater.vala * - * Copyright (C) 2013-2017 Venom authors and contributors + * Copyright (C) 2013-2018 Venom authors and contributors * * This file is part of Venom. * @@ -20,10 +20,10 @@ */ namespace Venom { - public class StaticDhtNodeDatabase : IDhtNodeDatabase, Object { - public List getDhtNodes(IDhtNodeFactory nodeFactory) { - var nodes = new List(); - nodes.append(nodeFactory.createDhtNode( + public class StaticDhtNodeUpdater : Object { + public Gee.Iterable get_dht_nodes() { + var nodes = new Gee.LinkedList(); + nodes.add(new DhtNode.with_params( "461FA3776EF0FA655F1A05477DF1B3B614F7D6B124F7DB1DD4FE3C08B03B640F", "130.133.110.14", 33445, @@ -31,7 +31,7 @@ namespace Venom { "manolis", "DE" )); - nodes.append(nodeFactory.createDhtNode( + nodes.add(new DhtNode.with_params( "F404ABAA1C99A9D37D61AB54898F56793E1DEF8BD46B1038B9D822E8460FAB67", "node.tox.biribiri.org", 33445, @@ -42,7 +42,5 @@ namespace Venom { return nodes; } - public void insertDhtNode(string key, string address, uint port, bool isBlocked, string owner, string location) {} - public void deleteDhtNode(string key) {} } } diff --git a/src/tox/ToxAdapterConferenceListener.vala b/src/tox/ToxAdapterConferenceListener.vala index d48f1ee..9b79383 100644 --- a/src/tox/ToxAdapterConferenceListener.vala +++ b/src/tox/ToxAdapterConferenceListener.vala @@ -22,7 +22,7 @@ namespace Venom { public class ToxAdapterConferenceListenerImpl : ToxAdapterConferenceListener, ConferenceInviteEntryListener, ConferenceWidgetListener, ConferenceInfoWidgetListener, CreateGroupchatWidgetListener, GLib.Object { private unowned ToxSession session; - private ILogger logger; + private Logger logger; private ObservableList contacts; private ObservableList conference_invites; private NotificationListener notification_listener; @@ -31,7 +31,7 @@ namespace Venom { private GLib.HashTable conferences; private unowned GLib.HashTable friends; - public ToxAdapterConferenceListenerImpl(ILogger logger, ObservableList contacts, ObservableList conference_invites, GLib.HashTable conversations, NotificationListener notification_listener) { + public ToxAdapterConferenceListenerImpl(Logger logger, ObservableList contacts, ObservableList conference_invites, GLib.HashTable conversations, NotificationListener notification_listener) { logger.d("ToxAdapterConferenceListenerImpl created."); this.logger = logger; this.contacts = contacts; @@ -139,7 +139,7 @@ namespace Venom { contacts.append(contact); conferences.@set(conference_number, contact); var conversation = new ObservableList(); - conversation.set_list(new GLib.List()); + conversation.set_list(new GLib.List()); conversations.@set(contact, conversation); } @@ -199,7 +199,7 @@ namespace Venom { var contact = conferences.@get(conference_number) as Conference; var conversation = conversations.@get(contact); var msg = new ConferenceMessage.outgoing(contact, message); - msg.received = true; + msg.state = TransmissionState.RECEIVED; conversation.append(msg); } } diff --git a/src/tox/ToxAdapterFiletransferListener.vala b/src/tox/ToxAdapterFiletransferListener.vala index 1a4d13a..a08bfdd 100644 --- a/src/tox/ToxAdapterFiletransferListener.vala +++ b/src/tox/ToxAdapterFiletransferListener.vala @@ -24,7 +24,7 @@ namespace Venom { private const int MAX_AVATAR_SIZE = 64 * 1024; private unowned ToxSession session; - private ILogger logger; + private Logger logger; private NotificationListener notification_listener; private unowned GLib.HashTable friends; @@ -33,7 +33,7 @@ namespace Venom { private ObservableList transfers; - public ToxAdapterFiletransferListenerImpl(ILogger logger, ObservableList transfers, GLib.HashTable conversations, NotificationListener notification_listener) { + public ToxAdapterFiletransferListenerImpl(Logger logger, ObservableList transfers, GLib.HashTable conversations, NotificationListener notification_listener) { logger.d("ToxAdapterFiletransferListenerImpl created."); this.logger = logger; this.transfers = transfers; diff --git a/src/tox/ToxAdapterFriendListener.vala b/src/tox/ToxAdapterFriendListener.vala index 60e2c06..9052b92 100644 --- a/src/tox/ToxAdapterFriendListener.vala +++ b/src/tox/ToxAdapterFriendListener.vala @@ -20,36 +20,98 @@ */ namespace Venom { + public class FriendDeletedNotification : NotificationAction { + private Contact contact; + private ToxAdapterFriendListenerImpl listener; + public FriendDeletedNotification(Contact contact, ToxAdapterFriendListenerImpl listener) { + this.contact = contact; + this.listener = listener; + message = _("%s has been removed from your contact list.").printf(contact.get_name_string()); + action_message = _("Undo"); + } + + public override void do_action() { + listener.on_friend_undelete(contact); + } + } + public class ToxAdapterFriendListenerImpl : ToxAdapterFriendListener, AddContactWidgetListener, ConversationWidgetListener, FriendInfoWidgetListener, FriendRequestWidgetListener, GLib.Object { private unowned ToxSession session; - private ILogger logger; + private Logger logger; private ObservableList contacts; private NotificationListener notification_listener; + private ContactRepository contact_repository; + private MessageRepository message_repository; + private FriendRequestRepository friend_request_repository; private GLib.HashTable conversations; private GLib.HashTable messages_waiting_for_rr; + private QueuedMessageStorage queued_messages; private unowned GLib.HashTable friends; private Gee.Map tox_friend_requests; private ObservableList friend_requests; private Gee.HashMap friend_avatar_hashes; + private InAppNotification in_app_notification; private UserInfo user_info; private GLib.Bytes avatar_hash; public bool show_typing { get; set; } + public bool enable_logging { get; set; } + + private class QueuedMessageStorage : GLib.Object { + private Gee.Map > messages = new Gee.HashMap >(); + public void offer(uint32 id, Message message) { + Gee.Queue queue; + if (!messages.has_key(id)) { + queue = new Gee.LinkedList(); + messages.@set(id, queue); + } else { + queue = messages.@get(id); + } + queue.offer(message); + } + public void unset(uint32 id) { + messages.unset(id); + } + public Message ? peek(uint32 id) { + if (!messages.has_key(id)) { + return null; + } + return messages.@get(id).peek(); + } + public Message ? poll(uint32 id) { + if (!messages.has_key(id)) { + return null; + } + return messages.@get(id).poll(); + } + } - public ToxAdapterFriendListenerImpl(ILogger logger, UserInfo user_info, ObservableList contacts, ObservableList friend_requests, GLib.HashTable conversations, NotificationListener notification_listener) { + public ToxAdapterFriendListenerImpl(Logger logger, UserInfo user_info, MessageRepository message_repository, + FriendRequestRepository friend_request_repository, ContactRepository contact_repository, + ObservableList contacts, ObservableList friend_requests, GLib.HashTable conversations, + NotificationListener notification_listener, InAppNotification in_app_notification) { logger.d("ToxAdapterFriendListenerImpl created."); this.logger = logger; this.user_info = user_info; + this.message_repository = message_repository; + this.contact_repository = contact_repository; + this.friend_request_repository = friend_request_repository; this.contacts = contacts; this.friend_requests = friend_requests; this.conversations = conversations; this.notification_listener = notification_listener; + this.in_app_notification = in_app_notification; messages_waiting_for_rr = new GLib.HashTable(null, null); + queued_messages = new QueuedMessageStorage(); tox_friend_requests = new Gee.HashMap(); + foreach (var request in friend_requests.get_all()) { + var r = (FriendRequest) request; + tox_friend_requests.@set(r.id, r); + } user_info.info_changed.connect(on_info_changed); avatar_hash = user_info.avatar.hash; @@ -79,7 +141,7 @@ namespace Venom { logger.d("ToxAdapterFriendListenerImpl destroyed."); } - public virtual void attach_to_session(ToxSession session) { + public void attach_to_session(ToxSession session) { this.session = session; session.set_friend_listener(this); @@ -94,38 +156,56 @@ namespace Venom { } } - public virtual void on_remove_friend(IContact c) throws Error { + public void on_remove_friend(IContact c) throws Error { var contact = c as Contact; session.friend_delete(contact.tox_friend_number); } - public virtual void on_send_friend_request(string address, string message) throws Error { + public void on_apply_friend_settings(IContact contact) { + contact_repository.update(contact); + } + + public void on_send_friend_request(string address, string message) throws Error { var bin_address = Tools.hexstring_to_bin(address); session.friend_add(bin_address, message); } - public virtual void on_accept_friend_request(string id) throws Error { + public void on_accept_friend_request(string id) throws Error { var friend_request = tox_friend_requests.@get(id); var public_key = Tools.hexstring_to_bin(id); session.friend_add_norequest(public_key); friend_requests.remove(friend_request); + friend_request_repository.delete (friend_request); tox_friend_requests.unset(id); } - public virtual void on_reject_friend_request(string id) throws Error { + public void on_reject_friend_request(string id) throws Error { var friend_request = tox_friend_requests.@get(id); friend_requests.remove(friend_request); + friend_request_repository.delete (friend_request); tox_friend_requests.unset(id); } - public virtual void on_send_message(IContact c, string message) throws Error { + public void on_send_message(IContact c, string message) throws Error { var contact = c as Contact; - session.friend_send_message(contact.tox_friend_number, message); + if (contact.connected) { + logger.d("on_send_message message sent "); + session.friend_send_message(contact.tox_friend_number, message); + } else { + logger.d("on_send_message message queued "); + var msg = new ToxMessage.outgoing(contact, message); + queued_messages.offer(contact.tox_friend_number, msg); + if (enable_logging) { + message_repository.create(msg); + } + var conversation = conversations.@get(contact); + conversation.append(msg); + } } - public virtual void on_set_typing(IContact c, bool typing) throws Error { + public void on_set_typing(IContact c, bool typing) throws Error { if (!show_typing) { return; } @@ -133,59 +213,68 @@ namespace Venom { session.self_set_typing(contact.tox_friend_number, typing); } - public virtual void on_friend_message(uint32 friend_number, string message_str) { + public void on_friend_message(uint32 friend_number, string message_str) { logger.d("on_friend_message"); var contact = friends.@get(friend_number) as Contact; var conversation = conversations.@get(contact); - var message = new Message.incoming(contact, message_str); + var message = new ToxMessage.incoming(contact, message_str); + if (enable_logging) { + message_repository.create(message); + } notification_listener.on_unread_message(message, contact); contact.unread_messages++; contact.changed(); conversation.append(message); } - public virtual void on_friend_read_receipt(uint32 friend_number, uint32 message_id) { + public void on_friend_read_receipt(uint32 friend_number, uint32 message_id) { var message = messages_waiting_for_rr.@get(message_id); if (message != null) { - message.received = true; + message.state = TransmissionState.RECEIVED; messages_waiting_for_rr.remove(message_id); - - message.message_changed(); + if (enable_logging) { + message_repository.update(message); + } } else { logger.f("Got read receipt for unknown message."); } } - public virtual void on_friend_name_changed(uint32 friend_number, string name) { + public void on_friend_name_changed(uint32 friend_number, string name) { var contact = friends.@get(friend_number) as Contact; contact.name = name; contact.changed(); } - public virtual void on_friend_status_message_changed(uint32 friend_number, string message) { + public void on_friend_status_message_changed(uint32 friend_number, string message) { logger.d("on_friend_status_message_changed"); var contact = friends.@get(friend_number) as Contact; contact.status_message = message; contact.changed(); } - public virtual void on_friend_request(uint8[] public_key, string message) { + public void on_friend_request(uint8[] public_key, string message) { logger.d("on_friend_request"); var id = Tools.bin_to_hexstring(public_key); + if (tox_friend_requests.has_key(id)) { + logger.d("Friend request already in list, ignoring."); + return; + } var request = new FriendRequest(id, message); friend_requests.append(request); + friend_request_repository.create(request); tox_friend_requests.@set(id, request); notification_listener.on_friend_request(request); } - public virtual void on_friend_status_changed(uint32 friend_number, UserStatus status) { + public void on_friend_status_changed(uint32 friend_number, UserStatus status) { logger.d("on_friend_status_changed"); var contact = friends.@get(friend_number) as Contact; contact.user_status = status; contact.changed(); } - public virtual void on_friend_connection_status_changed(uint32 friend_number, bool is_connected) { + public void on_friend_connection_status_changed(uint32 friend_number, bool is_connected) { logger.d("on_friend_connection_status_changed"); var contact = friends.@get(friend_number) as Contact; contact.connected = is_connected; @@ -195,18 +284,60 @@ namespace Venom { if (is_connected) { uint8[] avatar_data; user_info.avatar.pixbuf.save_to_buffer(out avatar_data, "png"); - session.file_send_avatar(contact.tox_friend_number, avatar_data); + session.file_send_avatar(friend_number, avatar_data); + + try { + Message ? message = queued_messages.peek(friend_number); + while (message != null) { + var message_id = session.friend_send_message_direct(friend_number, message.message); + message.state = TransmissionState.SENT; + if (enable_logging) { + message_repository.update(message); + } + + messages_waiting_for_rr.@set(message_id, message); + queued_messages.poll(friend_number); + message = queued_messages.peek(friend_number); + } + } catch (Error e) { + logger.e("on_friend_connection_status_changed error when sending queued messages"); + } } } - public virtual void on_friend_typing_status_changed(uint32 friend_number, bool is_typing) { + public void on_friend_typing_status_changed(uint32 friend_number, bool is_typing) { logger.d("on_friend_typing_status_changed"); var contact = friends.@get(friend_number) as Contact; contact._is_typing = is_typing; contact.changed(); } - public virtual void on_friend_added(uint32 friend_number, uint8[] public_key) { + private void init_friend_in_contacts(Contact contact) { + contact_repository.create(contact); + + if (!conversations.contains(contact)) { + var conversation = new ObservableList(); + conversation.set_list(new GLib.List()); + conversations.@set(contact, conversation); + + if (enable_logging) { + // FIXME move this into SqlSpecification + var messages = ((SqliteMessageRepository) message_repository).query_all_for_contact(contact); + foreach (var msg in messages) { + if (msg.sender == MessageSender.LOCAL && msg.state == TransmissionState.NONE) { + logger.d("on_friend_added restoring queued message..."); + queued_messages.offer(contact.tox_friend_number, msg); + } + conversation.append(msg); + } + } + } + + friends.@set(contact.tox_friend_number, contact); + contacts.append(contact); + } + + public void on_friend_added(uint32 friend_number, uint8[] public_key) { logger.d("on_friend_added"); var str_id = Tools.bin_to_hexstring(public_key); var contact = new Contact(friend_number, str_id); @@ -215,13 +346,7 @@ namespace Venom { contact.status_message = session.friend_get_status_message(friend_number); contact.last_seen = new DateTime.from_unix_local((int64) session.friend_get_last_online(friend_number)); } catch (ToxError e) { - logger.i("Restoring contact information failed"); - } - - if (!conversations.contains(contact)) { - var conversation = new ObservableList(); - conversation.set_list(new GLib.List()); - conversations.@set(contact, conversation); + logger.e("on_friend_added getting contact information failed"); } var filepath = GLib.Path.build_filename(R.constants.avatars_folder(), @"$str_id.png"); @@ -241,24 +366,38 @@ namespace Venom { } } - friends.@set(friend_number, contact); - contacts.append(contact); + init_friend_in_contacts(contact); + } + + public void on_friend_undelete(Contact contact) { + var public_key = Tools.hexstring_to_bin(contact.tox_id); + contact.tox_friend_number = session.friend_add_norequest_direct(public_key); + init_friend_in_contacts(contact); + contact_repository.update(contact); } - public virtual void on_friend_deleted(uint32 friend_number) { + public void on_friend_deleted(uint32 friend_number) { logger.d("on_friend_deleted"); - var contact = friends.@get(friend_number); + var contact = friends.@get(friend_number) as Contact; + contact.connected = false; + contact_repository.delete (contact); conversations.remove(contact); friends.remove(friend_number); contacts.remove(contact); + queued_messages.unset(friend_number); + in_app_notification.show_notification(new FriendDeletedNotification(contact, this)); } - public virtual void on_friend_message_sent(uint32 friend_number, uint32 message_id, string message) { + public void on_friend_message_sent(uint32 friend_number, uint32 message_id, string message) { logger.d("on_friend_message_sent"); var contact = friends.@get(friend_number); var conversation = conversations.@get(contact); - var msg = new Message.outgoing(contact, message); - msg.message_id = message_id; + var msg = new ToxMessage.outgoing(contact, message); + msg.state = TransmissionState.SENT; + msg.tox_id = message_id; + if (enable_logging) { + message_repository.create(msg); + } conversation.append(msg); messages_waiting_for_rr.@set(message_id, msg); } diff --git a/src/tox/ToxAdapterSelfListener.vala b/src/tox/ToxAdapterSelfListener.vala index 861810d..dffb243 100644 --- a/src/tox/ToxAdapterSelfListener.vala +++ b/src/tox/ToxAdapterSelfListener.vala @@ -22,12 +22,12 @@ namespace Venom { public class ToxAdapterSelfListenerImpl : ToxAdapterSelfListener, UserInfoViewListener, GLib.Object { private unowned ToxSession session; - private ILogger logger; + private Logger logger; private UserInfo user_info; private GLib.File avatar_file; private GLib.Cancellable avatar_cancellable; - public ToxAdapterSelfListenerImpl(ILogger logger, UserInfo user_info) { + public ToxAdapterSelfListenerImpl(Logger logger, UserInfo user_info) { logger.d("ToxAdapterSelfListenerImpl created."); this.logger = logger; this.user_info = user_info; @@ -72,6 +72,12 @@ namespace Venom { user_info.info_changed(); } + public virtual void set_self_nospam(int nospam) throws GLib.Error { + session.self_set_nospam((uint32) nospam); + user_info.tox_id = Tools.bin_to_hexstring(session.self_get_address()); + user_info.info_changed(); + } + public virtual void set_self_avatar(Gdk.Pixbuf pixbuf) throws GLib.Error { logger.d("ToxAdapterSelfListenerImpl set_self_avatar"); avatar_cancellable.cancel(); diff --git a/src/tox/ToxContact.vala b/src/tox/ToxContact.vala index 7c2964e..e5cbb5f 100644 --- a/src/tox/ToxContact.vala +++ b/src/tox/ToxContact.vala @@ -1,7 +1,7 @@ /* * ToxContact.vala * - * Copyright (C) 2013-2018 Venom authors and contributors + * Copyright (C) 2013-2018 Venom authors and contributors * * This file is part of Venom. * @@ -22,6 +22,7 @@ namespace Venom { public class Contact : IContact, GLib.Object { // Saved in toxs savefile + public int db_id { get; set; } public string tox_id { get; set; } public uint32 tox_friend_number { get; set; } public string name { get; set; default = ""; } @@ -29,7 +30,6 @@ namespace Venom { public DateTime last_seen { get; set; default = new DateTime.now_local(); } public UserStatus user_status { get; set; default = UserStatus.NONE; } // Saved in venoms savefile - public string note { get; set; default = ""; } public string alias { get; set; default = ""; } public bool is_blocked { get; set; default = false; } public string group { get; set; default = ""; } diff --git a/src/tox/ToxMessage.vala b/src/tox/ToxMessage.vala new file mode 100644 index 0000000..599936f --- /dev/null +++ b/src/tox/ToxMessage.vala @@ -0,0 +1,95 @@ +/* + * ToxMessage.vala + * + * Copyright (C) 2018 Venom authors and contributors + * + * This file is part of Venom. + * + * Venom is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Venom is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Venom. If not, see . + */ + + +namespace Venom { + public class ToxMessage : Message, FormattedMessage, GLib.Object { + public int id { get; set; } + public int peers_index { get; set; } + public DateTime timestamp { get; set; } + public MessageSender sender { get; set; } + public string message { get; set; } + public bool is_action { get; set; } + public TransmissionState state { get; set; } + + public uint32 tox_id { get; set; } + public IContact contact; + + public ToxMessage(IContact contact, MessageSender sender, string message, GLib.DateTime timestamp) { + this.sender = sender; + this.contact = contact; + this.message = message; + this.timestamp = timestamp; + } + + public ToxMessage.outgoing(IContact receiver, string message, GLib.DateTime timestamp = new GLib.DateTime.now_local()) { + this(receiver, MessageSender.LOCAL, message, timestamp); + } + + public ToxMessage.incoming(IContact sender, string message, GLib.DateTime timestamp = new GLib.DateTime.now_local()) { + this(sender, MessageSender.REMOTE, message, timestamp); + } + + public string get_sender_plain() { + if (sender == MessageSender.LOCAL) { + return _("me"); + } else { + return contact.get_name_string(); + } + } + + public string get_sender_full() { + return get_sender_plain(); + } + + public string get_sender_id() { + return contact.get_id(); + } + + public string get_conversation_id() { + return get_sender_id(); + } + + public string get_message_plain() { + return message; + } + + public string get_time_plain() { + return timestamp.format("%c"); + } + + public bool is_conference_message() { + return false; + } + + public Gdk.Pixbuf get_sender_image() { + if (sender == MessageSender.LOCAL) { + return pixbuf_from_resource(R.icons.default_contact); + } else { + return contact.get_image(); + } + } + + public bool equals_sender(Message m) { + return m is ToxMessage && contact == ((ToxMessage)m).contact; + } + } +} \ No newline at end of file diff --git a/src/tox/ToxSession.vala b/src/tox/ToxSession.vala index 3c9013c..2384b65 100644 --- a/src/tox/ToxSession.vala +++ b/src/tox/ToxSession.vala @@ -1,7 +1,7 @@ /* * ToxSession.vala * - * Copyright (C) 2013-2018 Venom authors and contributors + * Copyright (C) 2013-2018 Venom authors and contributors * * This file is part of Venom. * @@ -65,13 +65,16 @@ namespace Venom { public abstract uint8[] self_get_address(); public abstract uint8[] self_get_public_key(); + public abstract void self_set_nospam(uint32 nospam); public abstract void self_get_friend_list_foreach(GetFriendListCallback callback) throws ToxError; public abstract void friend_add(uint8[] address, string message) throws ToxError; + public abstract uint32 friend_add_norequest_direct(uint8[] public_key) throws ToxError; public abstract void friend_add_norequest(uint8[] address) throws ToxError; public abstract void friend_delete(uint32 friend_number) throws ToxError; public abstract void friend_send_message(uint32 friend_number, string message) throws ToxError; + public abstract uint32 friend_send_message_direct(uint32 friend_number, string message) throws ToxError; public abstract string friend_get_name(uint32 friend_number) throws ToxError; public abstract string friend_get_status_message(uint32 friend_number) throws ToxError; @@ -143,26 +146,26 @@ namespace Venom { public Tox handle; private Mutex mutex; - private IDhtNodeDatabase dht_node_database; + private DhtNodeRepository dht_node_repository; private ISettingsDatabase settings_database; - private List dht_nodes = new List(); - private ILogger logger; + private Gee.Iterable dht_nodes = Gee.Collection.empty(); + private Logger logger; private ToxSessionThread sessionThread; private ToxAdapterSelfListener self_listener; private ToxAdapterFriendListener friend_listener; private ToxAdapterConferenceListener conference_listener; private ToxAdapterFiletransferListener filetransfer_listener; - private ToxSessionIO iohandler; + private Profile profile; private GLib.HashTable friends; - public ToxSessionImpl(ToxSessionIO iohandler, IDhtNodeDatabase node_database, ISettingsDatabase settings_database, ILogger logger) throws Error { - this.dht_node_database = node_database; + public ToxSessionImpl(Profile profile, DhtNodeRepository node_database, ISettingsDatabase settings_database, Logger logger) throws Error { + this.dht_node_repository = node_database; this.settings_database = settings_database; this.logger = logger; this.mutex = Mutex(); - this.iohandler = iohandler; + this.profile = profile; var options_error = ToxCore.ErrOptionsNew.OK; var options = new ToxCore.Options(out options_error); @@ -170,7 +173,7 @@ namespace Venom { options.log_callback = on_tox_message; friends = new GLib.HashTable(null, null); - var savedata = iohandler.load_sessiondata(); + var savedata = profile.load_sessiondata(); if (savedata != null) { options.savedata_type = SaveDataType.TOX_SAVE; options.set_savedata_data(savedata); @@ -191,10 +194,12 @@ namespace Venom { if (error == ErrNew.PROXY_BAD_HOST || error == ErrNew.PROXY_BAD_PORT || error == ErrNew.PROXY_NOT_FOUND) { var message = "Proxy could not be used: " + error.to_string(); logger.e(message); + handle = null; throw new ToxError.GENERIC(message); } else if (error != ToxCore.ErrNew.OK) { var message = "Could not create tox instance: " + error.to_string(); logger.e(message); + handle = null; throw new ToxError.GENERIC(message); } @@ -232,7 +237,7 @@ namespace Venom { } if (handle != null) { logger.i("ToxSession saving session data."); - iohandler.save_sessiondata(handle.get_savedata()); + profile.save_sessiondata(handle.get_savedata()); handle = null; } logger.d("ToxSession destroyed."); @@ -283,21 +288,7 @@ namespace Venom { } private void init_dht_nodes() { - var nodeFactory = new DhtNodeFactory(); - dht_nodes = dht_node_database.getDhtNodes(nodeFactory); - logger.d("Items in dht node list: %u".printf(dht_nodes.length())); - if (dht_nodes.length() == 0) { - logger.d("Node database empty, populating from static database."); - var nodeDatabase = new JsonWebDhtNodeDatabase(logger); - var nodes = nodeDatabase.getDhtNodes(nodeFactory); - foreach (var node in nodes) { - dht_node_database.insertDhtNode(node.pub_key, node.host, node.port, node.is_blocked, node.maintainer, node.location); - } - dht_nodes = dht_node_database.getDhtNodes(nodeFactory); - if (dht_nodes.length() == 0) { - logger.e("Node initialisation from static database failed."); - } - } + dht_nodes = dht_node_repository.query_all(); } public virtual unowned GLib.HashTable get_friends() { @@ -580,6 +571,10 @@ namespace Venom { return handle.self_get_public_key(); } + public virtual void self_set_nospam(uint32 nospam) { + handle.self_nospam = nospam; + } + public virtual void self_set_status_message(string status) { var e = ErrSetInfo.OK; if (!handle.self_set_status_message(status, out e)) { @@ -659,13 +654,18 @@ namespace Venom { friend_listener.on_friend_added(friend_number, key); } - public virtual void friend_add_norequest(uint8[] public_key) throws ToxError { + public uint32 friend_add_norequest_direct(uint8[] public_key) throws ToxError { var e = ErrFriendAdd.OK; var friend_number = handle.friend_add_norequest(public_key, out e); if (e != ErrFriendAdd.OK) { logger.i("friend_add failed: " + e.to_string()); throw new ToxError.GENERIC(e.to_string()); } + return friend_number; + } + + public virtual void friend_add_norequest(uint8[] public_key) throws ToxError { + var friend_number = friend_add_norequest_direct(public_key); friend_listener.on_friend_added(friend_number, public_key); } @@ -679,13 +679,18 @@ namespace Venom { } public virtual void friend_send_message(uint32 friend_number, string message) throws ToxError { + var ret = friend_send_message_direct(friend_number, message); + friend_listener.on_friend_message_sent(friend_number, ret, message); + } + + public virtual uint32 friend_send_message_direct(uint32 friend_number, string message) throws ToxError { var e = ErrFriendSendMessage.OK; var ret = handle.friend_send_message(friend_number, MessageType.NORMAL, message, out e); if (e != ErrFriendSendMessage.OK) { logger.i("friend_send_message failed: " + e.to_string()); throw new ToxError.GENERIC(e.to_string()); } - friend_listener.on_friend_message_sent(friend_number, ret, message); + return ret; } public virtual void self_set_typing(uint32 friend_number, bool typing) throws ToxError { diff --git a/src/tox/ToxSessionIO.vala b/src/tox/ToxSessionIO.vala deleted file mode 100644 index 90430e4..0000000 --- a/src/tox/ToxSessionIO.vala +++ /dev/null @@ -1,58 +0,0 @@ -/* - * ToxSessionIO.vala - * - * Copyright (C) 2018 Venom authors and contributors - * - * This file is part of Venom. - * - * Venom is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Venom is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with Venom. If not, see . - */ - -namespace Venom { - - public interface ToxSessionIO : GLib.Object { - public abstract uint8[] ? load_sessiondata(); - public abstract void save_sessiondata(uint8[] sessiondata); - } - - public class ToxSessionIOImpl : ToxSessionIO, GLib.Object { - private ILogger logger; - - public ToxSessionIOImpl(ILogger logger) { - this.logger = logger; - } - - public virtual uint8[] ? load_sessiondata() { - var file = File.new_for_path(R.constants.tox_data_filename()); - uint8[] contents; - string etag_out; - try { - file.load_contents(null, out contents, out etag_out); - } catch (Error e) { - logger.i("could not read tox savefile: " + e.message); - return null; - } - return contents; - } - - public virtual void save_sessiondata(uint8[] sessiondata) { - var file = File.new_for_path(R.constants.tox_data_filename()); - try { - file.replace_contents(sessiondata, null, false, FileCreateFlags.NONE, null, null); - } catch (Error e) { - logger.f("Saving tox session data failed: " + e.message); - } - } - } -} diff --git a/src/tox/ToxSessionThread.vala b/src/tox/ToxSessionThread.vala index 6435384..bbcbfd9 100644 --- a/src/tox/ToxSessionThread.vala +++ b/src/tox/ToxSessionThread.vala @@ -1,7 +1,7 @@ /* * ToxSessionThread.vala * - * Copyright (C) 2018 Venom authors and contributors + * Copyright (C) 2018 Venom authors and contributors * * This file is part of Venom. * @@ -29,16 +29,16 @@ namespace Venom { public class ToxSessionThreadImpl : ToxSessionThread, Object { private unowned ToxSessionImpl session; - private unowned List bootstrapNodes; - private ILogger logger; + private unowned Gee.Iterable bootstrap_nodes; + private Logger logger; private bool running; private bool bootstrapped; private Thread session_thread = null; - public ToxSessionThreadImpl(ToxSessionImpl session, ILogger logger, List bootstrapNodes) { + public ToxSessionThreadImpl(ToxSessionImpl session, Logger logger, Gee.Iterable bootstrap_nodes) { this.session = session; this.logger = logger; - this.bootstrapNodes = bootstrapNodes; + this.bootstrap_nodes = bootstrap_nodes; this.running = false; this.bootstrapped = false; logger.d("ToxSessionThread created."); @@ -56,7 +56,7 @@ namespace Venom { if (!bootstrapped) { logger.d("Connecting to DHT Nodes:"); session.@lock(); - foreach (var dht_node in bootstrapNodes) { + foreach (var dht_node in bootstrap_nodes) { if (dht_node.is_blocked) { continue; } diff --git a/src/ui/add_contact_widget.ui b/src/ui/add_contact_widget.ui index ab4a8e0..9c2dd65 100644 --- a/src/ui/add_contact_widget.ui +++ b/src/ui/add_contact_widget.ui @@ -81,8 +81,9 @@ along with Venom. If not, see . True False - 44 + 40 contact-new-symbolic + 0 page0 diff --git a/src/ui/app_menu.ui b/src/ui/app_menu.ui index c2e366e..95ac2e6 100644 --- a/src/ui/app_menu.ui +++ b/src/ui/app_menu.ui @@ -34,6 +34,10 @@ along with Venom. If not, see . About app.about + + Logout + app.logout + _Quit app.quit diff --git a/src/ui/application_window.ui b/src/ui/application_window.ui index ebe2ff8..4899986 100644 --- a/src/ui/application_window.ui +++ b/src/ui/application_window.ui @@ -121,37 +121,141 @@ along with Venom. If not, see . - + True - True + False - + True - False - vertical + True + + + True + False + vertical + + + False + False + + + + + True + True + + + True + False + + + + + + + + True + False + + - False - False + -1 - - + + + 400 True - True + False + center + start - + True False + 0 + none + + True + False + + + True + False + 6 + + + True + True + True + none + + + True + False + window-close-symbolic + + + + + + False + True + end + 0 + + + + + True + True + True + + + True + False + action + + + + + False + True + end + 1 + + + + + True + False + message + + + False + True + 2 + + + + + + + + - True - False + True diff --git a/src/ui/conference_invite_entry.ui b/src/ui/conference_invite_entry.ui index f92798b..74b9c28 100644 --- a/src/ui/conference_invite_entry.ui +++ b/src/ui/conference_invite_entry.ui @@ -37,9 +37,10 @@ along with Venom. If not, see . True False - center - 44 + start + 40 friend-symbolic + 0 False @@ -51,13 +52,13 @@ along with Venom. If not, see . True False - center + vertical 6 True False - vertical + 6 True @@ -84,6 +85,7 @@ along with Venom. If not, see . False True + end 1 @@ -102,17 +104,10 @@ along with Venom. If not, see . 6 + Accept True True True - Accept invite - - - True - False - object-select-symbolic - - diff --git a/src/ui/contact_list_entry.ui b/src/ui/contact_list_entry.ui index 313e7e0..e865a8a 100644 --- a/src/ui/contact_list_entry.ui +++ b/src/ui/contact_list_entry.ui @@ -53,6 +53,7 @@ along with Venom. If not, see . True False + center vertical @@ -60,6 +61,7 @@ along with Venom. If not, see . False contact_name end + True 0 @@ -74,6 +76,7 @@ along with Venom. If not, see . False contact_status end + True 0 False True - 1 + 4 + + + info + Info + + + + + True + True + in - + + 100 + 80 True - False - Please check your settings and retry + True + False + True - - False - True - 2 - - False - True - 4 + log + Log + 1 - info - Info + True + True + 1 - - - True - True - 1 - - - - - True - True - True - end - + True - False - 6 + True + True + end - - True - False - view-refresh-symbolic - - - False - True - 0 - - - - + True False - Retry + 6 + + + True + False + view-refresh-symbolic + + + False + True + 0 + + + + + True + False + Retry + + + False + True + 1 + + - - False - True - 1 - + + + False + True + end + 3 + - - - False - True - end - 3 - diff --git a/src/ui/friend_request_widget.ui b/src/ui/friend_request_widget.ui index 4c60bf0..e0cbf63 100644 --- a/src/ui/friend_request_widget.ui +++ b/src/ui/friend_request_widget.ui @@ -39,8 +39,9 @@ along with Venom. If not, see . False end start - 44 + 40 friend-symbolic + 0 False @@ -52,22 +53,23 @@ along with Venom. If not, see . True False - 6 + vertical + 7 True False - vertical + center + 6 - + + Accept True - False - contact_id - True - end - 0 + True + True @@ -77,14 +79,13 @@ along with Venom. If not, see . - + + Deny True - False - contact_time - end - 0 + True + True @@ -93,53 +94,29 @@ along with Venom. If not, see . 1 - - - True - False - contact_message - True - 0 - 0 - - - - - - False - True - 2 - - - True + False True - 1 + end + 0 True False - center 6 - + True - True - True - Accept request - - - True - False - object-select-symbolic - - + False + contact_id + True + end + 0 @@ -149,26 +126,20 @@ along with Venom. If not, see . - + True - True - True - Reject request - - - True - False - user-trash-symbolic - - + False + contact_time + end + 0 False True + end 1 @@ -179,6 +150,25 @@ along with Venom. If not, see . 1 + + + True + False + contact_message + True + end + 0 + 0 + + + + + + False + True + 2 + + True diff --git a/src/ui/login_widget.ui b/src/ui/login_widget.ui new file mode 100644 index 0000000..3f18ef1 --- /dev/null +++ b/src/ui/login_widget.ui @@ -0,0 +1,627 @@ + + + + + + diff --git a/src/ui/message_widget.ui b/src/ui/message_widget.ui index 6e1df80..1da110c 100644 --- a/src/ui/message_widget.ui +++ b/src/ui/message_widget.ui @@ -94,6 +94,7 @@ along with Venom. If not, see . + True False True emblem-ok-symbolic diff --git a/src/ui/node_widget.ui b/src/ui/node_widget.ui index 6e5b878..349a0ec 100644 --- a/src/ui/node_widget.ui +++ b/src/ui/node_widget.ui @@ -1,5 +1,5 @@ - + + + + diff --git a/src/ui/settings_widget.ui b/src/ui/settings_widget.ui index 94a9fa5..7ae9cf9 100644 --- a/src/ui/settings_widget.ui +++ b/src/ui/settings_widget.ui @@ -127,6 +127,46 @@ along with Venom. If not, see . + + + 200 + 50 + True + True + + + True + False + 6 + 6 + + + True + False + network-wired-symbolic + + + False + True + 0 + + + + + True + False + Bootstrap nodes + + + False + True + 1 + + + + + + 200 @@ -630,6 +670,7 @@ along with Venom. If not, see . True False + slide-up True @@ -857,6 +898,7 @@ along with Venom. If not, see . True False + slide-up True @@ -1073,154 +1115,27 @@ along with Venom. If not, see . - - - - False - True - 1 - - - - - - - - - True - True - - - True - False - vertical - 6 - - - True - False - 6 - - - True - False - system-search-symbolic - - - False - True - 0 - - - - - True - False - History - - - - - - False - True - 1 - - - + True - False True - - - False - True - end - 2 - - - - - False - True - 0 - - - - - True - False - True - - - True - False - none - + True - True + False + 6 + 6 True False - 6 vertical - - True - True - False - True - history_delete_radio - - - True - False - Keep forever - 0 - - - - - False - True - 0 - - - - - - - - - True - True - - - True - False - 6 - 6 - - + True - True - False - True - True - - - True - False - Remove older than - 0 - - + False + Keep History + 0 False @@ -1229,75 +1144,55 @@ along with Venom. If not, see . - - True - True - 5 - 180 - digits - history_delete_adjustment - True - True - if-valid - 180 - - - False - True - 1 - - - - + True False - days + <small>Store your sent and received messages</small> + True + 0 + False True - 2 + 1 + + False + True + 0 + + + + + True + True + center + + + False + True + end + 1 + - - - - - - False - True - 1 - - - - - True - False - True - True - Delete all previous conversations - - - True - False - user-trash-symbolic False True - 2 + 1 @@ -1693,18 +1588,97 @@ along with Venom. If not, see . 1 + + + True + True + 1 + + + + + True + False + vertical + + + + + + + + + + + + True + True + 2 + + + + + + + + + page1 + page1 + 1 + + + + + True + True + + + True + False + 6 + none + + + True + False + + + True + False + vertical + + + + + + + + + + + + True + True + 0 + + + + + True + False + vertical + 6 True False 6 - + True True True Update bootstrap nodes - app.update-nodes True @@ -1753,7 +1727,7 @@ along with Venom. If not, see . False True - 2 + 0 @@ -1771,7 +1745,7 @@ along with Venom. If not, see . False True - 3 + 1 @@ -1808,9 +1782,9 @@ along with Venom. If not, see . - page1 - page1 - 1 + page2 + page2 + 2 @@ -1908,6 +1882,7 @@ along with Venom. If not, see . True False + slide-up True @@ -2048,6 +2023,7 @@ along with Venom. If not, see . True False + slide-up True @@ -2058,7 +2034,6 @@ along with Venom. If not, see . True False - 6 True @@ -2092,6 +2067,9 @@ along with Venom. If not, see . 1 + False @@ -2176,9 +2154,9 @@ along with Venom. If not, see . - page2 - page2 - 2 + page3 + page3 + 3 @@ -2201,7 +2179,6 @@ along with Venom. If not, see . - diff --git a/src/ui/user_info_widget.ui b/src/ui/user_info_widget.ui index d59809f..9b1a959 100644 --- a/src/ui/user_info_widget.ui +++ b/src/ui/user_info_widget.ui @@ -326,7 +326,7 @@ along with Venom. If not, see . - + True False none @@ -340,23 +340,31 @@ along with Venom. If not, see . True False 6 - vertical 6 - + True - False - ID: - True - label_id - 0 - - - + True + True + Copy to clipboard + end + win.copy_id + True + + + True + False + edit-copy-symbolic + + + False True + end 0 @@ -364,18 +372,19 @@ along with Venom. If not, see . True False + vertical 6 - + True False - ########################### - True - end + ID: + True + label_id 0 - + + + False @@ -384,28 +393,20 @@ along with Venom. If not, see . - + True - True - True - Copy to clipboard - win.copy_id - True - - - True - False - edit-copy-symbolic - - + False + ########################### + True + end + 0 False True - end 1 @@ -413,7 +414,6 @@ along with Venom. If not, see . False True - end 1 @@ -438,6 +438,187 @@ along with Venom. If not, see . 1 + + + True + False + vertical + + + True + True + True + none + + + True + False + center + 6 + + + True + False + Advanced + + + False + True + 0 + + + + + True + False + go-down-symbolic + + + + False + True + 1 + + + + + + + False + True + 0 + + + + + True + False + slide-up + + + True + False + 6 + vertical + 6 + + + True + False + Tox nospam + 0 + + + + + + False + True + 0 + + + + + True + False + + + True + True + 8 + media-playlist-shuffle-symbolic + Generate new random nospam + number + + + True + True + 0 + + + + + True + True + True + Set this nospam + + + True + False + object-select-symbolic + + + + + + False + True + 2 + + + + + + False + True + 1 + + + + + True + False + Previous nospams + 0 + + + + + + False + True + 2 + + + + + 100 + True + False + none + + + + False + True + 3 + + + + + + + False + True + 1 + + + + + False + True + 2 + + Apply @@ -451,7 +632,7 @@ along with Venom. If not, see . False True - 2 + 3 diff --git a/src/ui/venom.gresource.xml b/src/ui/venom.gresource.xml index 54069ff..cb2679a 100644 --- a/src/ui/venom.gresource.xml +++ b/src/ui/venom.gresource.xml @@ -36,8 +36,10 @@ along with Venom. If not, see . file_transfer_widget.ui friend_info_widget.ui friend_request_widget.ui + login_widget.ui message_widget.ui node_widget.ui + nospam_entry.ui peer_entry.ui peer_entry_compact.ui settings_widget.ui diff --git a/src/vapi/sqlcipher.vapi b/src/vapi/sqlcipher.vapi new file mode 100644 index 0000000..d6620db --- /dev/null +++ b/src/vapi/sqlcipher.vapi @@ -0,0 +1,469 @@ +/* sqlite3.vala + * + * Copyright (C) 2007 Jürg Billeter + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Author: + * Jürg Billeter + */ + +[CCode (lower_case_cprefix = "sqlite3_", cheader_filename = "sqlite3.h")] +namespace Sqlite { + /* Database Connection Handle */ + [Compact] + [CCode (free_function = "sqlite3_close", cname = "sqlite3", cprefix = "sqlite3_")] + public class Database { + public int busy_timeout (int ms); + public int changes (); + [CCode (cname = "sqlite3_exec")] + public int _exec (string sql, Callback? callback = null, [CCode (type = "char**")] out unowned string? errmsg = null); + [CCode (cname = "_sqlite3_exec")] + public int exec (string sql, Callback? callback = null, out string? errmsg = null) { + unowned string? sqlite_errmsg; + var ec = this._exec (sql, callback, out sqlite_errmsg); + if (&errmsg != null) { + errmsg = sqlite_errmsg; + } + Sqlite.Memory.free ((void*) sqlite_errmsg); + return ec; + } + public int extended_result_codes (int onoff); + public int get_autocommit (); + public void interrupt (); + public int64 last_insert_rowid (); + public int limit (Sqlite.Limit id, int new_val); + public int total_changes (); + public int complete (string sql); + [CCode (cname = "sqlite3_get_table")] + public int _get_table (string sql, [CCode (array_length = false)] out unowned string[] resultp, out int nrow, out int ncolumn, [CCode (type = "char**")] out unowned string? errmsg = null); + private static void free_table ([CCode (array_length = false)] string[] result); + [CCode (cname = "_sqlite3_get_table")] + public int get_table (string sql, out string[] resultp, out int nrow, out int ncolumn, out string? errmsg = null) { + unowned string? sqlite_errmsg; + unowned string[] sqlite_resultp; + + var ec = this._get_table (sql, out sqlite_resultp, out nrow, out ncolumn, out sqlite_errmsg); + + resultp = new string[(nrow + 1) * ncolumn]; + for (var entry = 0 ; entry < resultp.length ; entry++ ) { + resultp[entry] = sqlite_resultp[entry]; + } + Sqlite.Database.free_table (sqlite_resultp); + + if (&errmsg != null) { + errmsg = sqlite_errmsg; + } + Sqlite.Memory.free ((void*) sqlite_errmsg); + return ec; + } + public static int open (string filename, out Database db); + public static int open_v2 (string filename, out Database db, int flags = OPEN_READWRITE | OPEN_CREATE, string? zVfs = null); + public int errcode (); + public unowned string errmsg (); + public unowned Sqlite.Statement next_stmt (Sqlite.Statement? current); + public int prepare (string sql, int n_bytes, out Statement stmt, out unowned string tail = null); + public int prepare_v2 (string sql, int n_bytes, out Statement stmt, out unowned string tail = null); + public int set_authorizer (AuthorizeCallback? auth); + [CCode (cname = "sqlite3_db_status")] + public int status (Sqlite.DatabaseStatus op, out int pCurrent, out int pHighwater, int resetFlag = 0); + public int table_column_metadata (string db_name, string table_name, string column_name, out string? data_type, out string? collation_sequence, out int? not_null, out int? primary_key, out int? auto_increment); + public void trace (TraceCallback? xtrace); + public void profile (ProfileCallback? xprofile); + public void progress_handler (int n_opcodes, Sqlite.ProgressCallback? progress_handler); + public void commit_hook (CommitCallback? commit_hook); + public void rollback_hook (RollbackCallback? rollback_hook); + public void update_hook (UpdateCallback? update_hook); + public int create_function (string zFunctionName, int nArg, int eTextRep, void * user_data, UserFuncCallback? xFunc, UserFuncCallback? xStep, UserFuncFinishCallback? xFinal); + public int create_function_v2 (string zFunctionName, int nArg, int eTextRep, void * user_data, UserFuncCallback? xFunc, UserFuncCallback? xStep, UserFuncFinishCallback? xFinal, GLib.DestroyNotify? destroy = null); + public int create_collation (string zName, int eTextRep, [CCode (delegate_target_pos = 2.9, type = "int (*)(void *, int, const void *, int, const void *)")] CompareCallback xCompare); + + public int wal_autocheckpoint (int N); + public int wal_checkpoint (string zDb); + public void* wal_hook (WALHookCallback cb, string db_name, int page_count); + } + + [CCode (instance_pos = 0)] + public delegate int AuthorizeCallback (Sqlite.Action action, string? p1, string? p2, string db_name, string? responsible); + [CCode (instance_pos = 0)] + public delegate void TraceCallback (string message); + [CCode (instance_pos = 0)] + public delegate void ProfileCallback (string sql, uint64 time); + public delegate int ProgressCallback (); + public delegate int CommitCallback (); + public delegate void RollbackCallback (); + [CCode (has_target = false)] + public delegate void UserFuncCallback (Sqlite.Context context, [CCode (array_length_pos = 1.1)] Sqlite.Value[] values); + [CCode (has_target = false)] + public delegate void UserFuncFinishCallback (Sqlite.Context context); + [CCode (instance_pos = 0)] + public delegate void UpdateCallback (Sqlite.Action action, string dbname, string table, int64 rowid); + [CCode (instance_pos = 0)] + public delegate int CompareCallback (int alen, void* a, int blen, void* b); + [CCode (instance_pos = 0)] + public delegate int WALHookCallback (Sqlite.Database db, string dbname, int pages); + + public unowned string? compileoption_get (int n); + public int compileoption_used (string option_name); + public static int complete (string sql); + [CCode (sentinel = "")] + public static int config (Sqlite.Config op, ...); + public unowned string libversion (); + public int libversion_number (); + [PrintfFormat] + public void log (int error_code, string format, ...); + public unowned string sourceid (); + public static int status (Sqlite.Status op, out int pCurrent, out int pHighwater, int resetFlag = 0); + public static int threadsafe (); + + [CCode (cname = "SQLITE_VERSION")] + public const string VERSION; + [CCode (cname = "SQLITE_VERSION_NUMBER")] + public const int VERSION_NUMBER; + [CCode (cname = "SQLITE_SOURCE_ID")] + public const string SOURCE_ID; + + /* Dynamically Typed Value Object */ + [Compact] + [CCode (cname = "sqlite3_value")] + public class Value { + [CCode (cname = "sqlite3_value_blob")] + public void* to_blob (); + [CCode (cname = "sqlite3_value_bytes")] + public int to_bytes (); + [CCode (cname = "sqlite3_value_double")] + public double to_double (); + [CCode (cname = "sqlite3_value_int")] + public int to_int (); + [CCode (cname = "sqlite3_value_int64")] + public int64 to_int64 (); + [CCode (cname = "sqlite3_value_text")] + public unowned string to_text (); + [CCode (cname = "sqlite3_value_type")] + public int to_type (); + [CCode (cname = "sqlite3_value_numeric_type")] + public int to_numeric_type (); + } + + [CCode (cname = "sqlite3_callback", instance_pos = 0)] + public delegate int Callback (int n_columns, [CCode (array_length = false)] string[] values, [CCode (array_length = false)] string[] column_names); + + [CCode (cname = "SQLITE_OK")] + public const int OK; + [CCode (cname = "SQLITE_ERROR")] + public const int ERROR; + [CCode (cname = "SQLITE_INTERNAL")] + public const int INTERNAL; + [CCode (cname = "SQLITE_PERM")] + public const int PERM; + [CCode (cname = "SQLITE_ABORT")] + public const int ABORT; + [CCode (cname = "SQLITE_BUSY")] + public const int BUSY; + [CCode (cname = "SQLITE_LOCKED")] + public const int LOCKED; + [CCode (cname = "SQLITE_NOMEM")] + public const int NOMEM; + [CCode (cname = "SQLITE_READONLY")] + public const int READONLY; + [CCode (cname = "SQLITE_INTERRUPT")] + public const int INTERRUPT; + [CCode (cname = "SQLITE_IOERR")] + public const int IOERR; + [CCode (cname = "SQLITE_CORRUPT")] + public const int CORRUPT; + [CCode (cname = "SQLITE_NOTFOUND")] + public const int NOTFOUND; + [CCode (cname = "SQLITE_FULL")] + public const int FULL; + [CCode (cname = "SQLITE_CANTOPEN")] + public const int CANTOPEN; + [CCode (cname = "SQLITE_PROTOCOL")] + public const int PROTOCOL; + [CCode (cname = "SQLITE_EMPTY")] + public const int EMPTY; + [CCode (cname = "SQLITE_SCHEMA")] + public const int SCHEMA; + [CCode (cname = "SQLITE_TOOBIG")] + public const int TOOBIG; + [CCode (cname = "SQLITE_CONSTRAINT")] + public const int CONSTRAINT; + [CCode (cname = "SQLITE_MISMATCH")] + public const int MISMATCH; + [CCode (cname = "SQLITE_MISUSE")] + public const int MISUSE; + [CCode (cname = "SQLITE_NOLFS")] + public const int NOLFS; + [CCode (cname = "SQLITE_AUTH")] + public const int AUTH; + [CCode (cname = "SQLITE_FORMAT")] + public const int FORMAT; + [CCode (cname = "SQLITE_RANGE")] + public const int RANGE; + [CCode (cname = "SQLITE_NOTADB")] + public const int NOTADB; + [CCode (cname = "SQLITE_ROW")] + public const int ROW; + [CCode (cname = "SQLITE_DONE")] + public const int DONE; + [CCode (cname = "SQLITE_OPEN_READONLY")] + public const int OPEN_READONLY; + [CCode (cname = "SQLITE_OPEN_READWRITE")] + public const int OPEN_READWRITE; + [CCode (cname = "SQLITE_OPEN_CREATE")] + public const int OPEN_CREATE; + [CCode (cname = "SQLITE_OPEN_URI")] + public const int OPEN_URI; + [CCode (cname = "SQLITE_OPEN_MEMORY")] + public const int OPEN_MEMORY; + [CCode (cname = "SQLITE_OPEN_NOMUTEX")] + public const int OPEN_NOMUTEX; + [CCode (cname = "SQLITE_OPEN_FULLMUTEX")] + public const int OPEN_FULLMUTEX; + [CCode (cname = "SQLITE_OPEN_SHAREDCACHE")] + public const int OPEN_SHAREDCACHE; + [CCode (cname = "SQLITE_OPEN_PRIVATECACHE")] + public const int OPEN_PRIVATECACHE; + [CCode (cname = "SQLITE_INTEGER")] + public const int INTEGER; + [CCode (cname = "SQLITE_FLOAT")] + public const int FLOAT; + [CCode (cname = "SQLITE_BLOB")] + public const int BLOB; + [CCode (cname = "SQLITE_NULL")] + public const int NULL; + [CCode (cname = "SQLITE3_TEXT")] + public const int TEXT; + [CCode (cname = "SQLITE_MUTEX_FAST")] + public const int MUTEX_FAST; + [CCode (cname = "SQLITE_MUTEX_RECURSIVE")] + public const int MUTEX_RECURSIVE; + [CCode (cname = "SQLITE_UTF8")] + public const int UTF8; + [CCode (cname = "SQLITE_UTF16LE")] + public const int UTF16LE; + [CCode (cname = "SQLITE_UTF16BE")] + public const int UTF16BE; + [CCode (cname = "SQLITE_UTF16")] + public const int UTF16; + [CCode (cname = "SQLITE_ANY")] + public const int ANY; + [CCode (cname = "SQLITE_UTF16_ALIGNED")] + public const int UTF16_ALIGNED; + + [CCode (cname = "int", cprefix = "SQLITE_", has_type_id = false)] + public enum Action { + CREATE_INDEX, + CREATE_TABLE, + CREATE_TEMP_INDEX, + CREATE_TEMP_TABLE, + CREATE_TEMP_TRIGGER, + CREATE_TEMP_VIEW, + CREATE_TRIGGER, + CREATE_VIEW, + DELETE, + DROP_INDEX, + DROP_TABLE, + DROP_TEMP_INDEX, + DROP_TEMP_TABLE, + DROP_TEMP_TRIGGER, + DROP_TEMP_VIEW, + DROP_TRIGGER, + DROP_VIEW, + INSERT, + PRAGMA, + READ, + SELECT, + TRANSACTION, + UPDATE, + ATTACH, + DETACH, + ALTER_TABLE, + REINDEX, + ANALYZE, + CREATE_VTABLE, + DROP_VTABLE, + FUNCTION, + SAVEPOINT, + COPY + } + + [CCode (cname = "int", cprefix = "SQLITE_CONFIG_", has_type_id = false)] + public enum Config { + SINGLETHREAD, + MULTITHREAD, + SERIALIZED, + MALLOC, + GETMALLOC, + SCRATCH, + PAGECACHE, + HEAP, + MEMSTATUS, + MUTEX, + GETMUTEX, + LOOKASIDE, + PCACHE, + GETPCACHE, + LOG, + } + + [CCode (cname = "int", cprefix = "SQLITE_DBSTATUS_", has_type_id = false)] + public enum DatabaseStatus { + LOOKASIDE_USED + } + + [CCode (cname = "int", cprefix = "SQLITE_LIMIT_", has_type_id = false)] + public enum Limit { + LENGTH, + SQL_LENGTH, + COLUMN, + EXPR_DEPTH, + COMPOUND_SELECT, + VDBE_OP, + FUNCTION_ARG, + ATTACHED, + LIKE_PATTERN_LENGTH, + VARIABLE_NUMBER, + TRIGGER_DEPTH + } + + [CCode (cname = "int", cprefix = "SQLITE_STMTSTATUS_", has_type_id = false)] + public enum StatementStatus { + FULLSCAN_STEP, + SORT + } + + [CCode (cname = "int", cprefix = "SQLITE_STATUS_", has_type_id = false)] + public enum Status { + MEMORY_USED, + PAGECACHE_USED, + PAGECACHE_OVERFLOW, + SCRATCH_USED, + SCRATCH_OVERFLOW, + MALLOC_SIZE, + PARSER_STACK, + PAGECACHE_SIZE, + SCRATCH_SIZE + } + + /* SQL Statement Object */ + [Compact] + [CCode (free_function = "sqlite3_finalize", cname = "sqlite3_stmt", cprefix = "sqlite3_")] + public class Statement { + public int bind_parameter_count (); + public int bind_parameter_index (string name); + public unowned string bind_parameter_name (int index); + public int clear_bindings (); + public int column_count (); + public int data_count (); + public unowned Database db_handle (); + public int reset (); + [CCode (cname = "sqlite3_stmt_status")] + public int status (Sqlite.StatementStatus op, int resetFlg = 0); + public int step (); + public int bind_blob (int index, void* value, int n, GLib.DestroyNotify? destroy_notify = null); + public int bind_double (int index, double value); + public int bind_int (int index, int value); + public int bind_int64 (int index, int64 value); + public int bind_null (int index); + [CCode (cname = "sqlite3_bind_text")] + public int _bind_text (int index, string value, int n = -1, GLib.DestroyNotify? destroy_notify = null); + public int bind_text (int index, owned string value, int n = -1, GLib.DestroyNotify destroy_notify = GLib.g_free); + public int bind_value (int index, Value value); + public int bind_zeroblob (int index, int n); + public void* column_blob (int col); + public int column_bytes (int col); + public double column_double (int col); + public int column_int (int col); + public int64 column_int64 (int col); + public unowned string? column_text (int col); + public int column_type (int col); + public unowned Value column_value (int col); + public unowned string column_name (int index); + public unowned string column_database_name (int col); + public unowned string column_table_name (int col); + public unowned string column_origin_name (int col); + public unowned string sql (); + } + + namespace Memory { + [CCode (cname = "sqlite3_malloc")] + public static void* malloc (int n_bytes); + [CCode (cname = "sqlite3_realloc")] + public static void* realloc (void* mem, int n_bytes); + [CCode (cname = "sqlite3_free")] + public static void free (void* mem); + [CCode (cname = "sqlite3_release_memory")] + public static int release (int bytes); + [CCode (cname = "sqlite3_memory_used")] + public static int64 used (); + [CCode (cname = "sqlite3_memory_highwater")] + public static int64 highwater (int reset = 0); + [Version (deprecated_since = "3.7.2", replacement = "Sqlite.Memory.soft_heap_limit64")] + [CCode (cname = "sqlite3_soft_heap_limit")] + public static void soft_heap_limit (int limit); + [CCode (cname = "sqlite3_soft_heap_limit64")] + public static int64 soft_heap_limit64 (int64 limit = -1); + } + + [Compact] + [CCode (cname = "sqlite3_mutex")] + public class Mutex { + [CCode (cname = "sqlite3_mutex_alloc")] + public Mutex (int mutex_type = MUTEX_RECURSIVE); + public void enter (); + public int held (); + public int notheld (); + public int @try (); + public void leave (); + } + + [Compact, CCode (cname = "sqlite3_context", cprefix = "sqlite3_")] + public class Context { + public void result_blob (owned uint8[] data, GLib.DestroyNotify? destroy_notify = GLib.g_free); + public void result_double (double value); + public void result_error (string value, int error_code); + public void result_error_toobig (); + public void result_error_nomem (); + public void result_error_code (int error_code); + public void result_int (int value); + public void result_int64 (int64 value); + public void result_null (); + public void result_text (owned string value, int length = -1, GLib.DestroyNotify? destroy_notify = GLib.g_free); + public void result_value (Sqlite.Value value); + public void result_zeroblob (int n); + + [CCode (simple_generics = true)] + public unowned T user_data (); + [CCode (simple_generics = true)] + public void set_auxdata (int N, owned T data); + [CCode (simple_generics = true)] + public unowned T get_auxdata (int N); + [CCode (cname = "sqlite3_context_db_handle")] + public unowned Database db_handle (); + [CCode (cname = "sqlite3_aggregate_context")] + public void * aggregate (int n_bytes); + } + + [Compact, CCode (cname = "sqlite3_backup", free_function = "sqlite3_backup_finish", cprefix = "sqlite3_backup_")] + public class Backup { + [CCode (cname = "sqlite3_backup_init")] + public Backup (Database dest, string dest_name, Database source, string source_name); + public int step (int nPage); + public int remaining (); + public int pagecount (); + } +} + diff --git a/src/vapi/toxencryptsave.vapi b/src/vapi/toxencryptsave.vapi index 34a623a..8ee7f9c 100644 --- a/src/vapi/toxencryptsave.vapi +++ b/src/vapi/toxencryptsave.vapi @@ -34,7 +34,7 @@ namespace ToxEncryptSave { */ public uint32 pass_encryption_extra_length(); - [CCode(cname = "TOX_ERR_KEY_DERIVATION", cprefix = "TOX_ERR_KEY_DERIVATION_")] + [CCode(cname = "TOX_ERR_KEY_DERIVATION", cprefix = "TOX_ERR_KEY_DERIVATION_", has_type_id = false)] public enum ErrKeyDerivation { /** * The function returned successfully. @@ -51,7 +51,7 @@ namespace ToxEncryptSave { FAILED } - [CCode(cname = "TOX_ERR_ENCRYPTION", cprefix = "TOX_ERR_ENCRYPTION_")] + [CCode(cname = "TOX_ERR_ENCRYPTION", cprefix = "TOX_ERR_ENCRYPTION_", has_type_id = false)] public enum ErrEncryption { /** * The function returned successfully. @@ -73,7 +73,7 @@ namespace ToxEncryptSave { FAILED } - [CCode(cname = "TOX_ERR_DECRYPTION", cprefix = "TOX_ERR_DECRYPTION_")] + [CCode(cname = "TOX_ERR_DECRYPTION", cprefix = "TOX_ERR_DECRYPTION_", has_type_id = false)] public enum ErrDecryption { /** * The function returned successfully. @@ -236,7 +236,7 @@ namespace ToxEncryptSave { } } - [CCode(cname = "TOX_ERR_GET_SALT", cprefix = "TOX_ERR_GET_SALT_")] + [CCode(cname = "TOX_ERR_GET_SALT", cprefix = "TOX_ERR_GET_SALT_", has_type_id = false)] public enum ErrGetSalt { /** * The function returned successfully. diff --git a/src/view/AboutDialog.vala b/src/view/AboutDialog.vala index 6bf5c4c..0d0fa9b 100644 --- a/src/view/AboutDialog.vala +++ b/src/view/AboutDialog.vala @@ -21,9 +21,9 @@ namespace Venom { public class AboutDialog : Gtk.AboutDialog { - private ILogger logger; + private Logger logger; - public AboutDialog(ILogger logger) { + public AboutDialog(Logger logger) { this.logger = logger; logger.d("AboutDialog created."); diff --git a/src/view/AddContactWidget.vala b/src/view/AddContactWidget.vala index 0ce6681..f5f0667 100644 --- a/src/view/AddContactWidget.vala +++ b/src/view/AddContactWidget.vala @@ -36,12 +36,12 @@ namespace Venom { [GtkChild] private Gtk.Box friend_request_item; [GtkChild] private Gtk.Widget custom_title; - private ILogger logger; + private Logger logger; private AddContactViewModel view_model; private ContainerChildBooleanBinding stack_binding; private StackIndexTransform contact_image_stack_transform; - public AddContactWidget(ILogger logger, ApplicationWindow app_window, ObservableList friend_requests_model, AddContactWidgetListener listener, FriendRequestWidgetListener friend_request_listener) { + public AddContactWidget(Logger logger, ApplicationWindow app_window, ObservableList friend_requests_model, AddContactWidgetListener listener, FriendRequestWidgetListener friend_request_listener) { logger.d("AddContactWidget created."); this.logger = logger; view_model = new AddContactViewModel(logger, friend_requests_model, listener); @@ -77,9 +77,9 @@ namespace Venom { } public class FriendRequestWidgetCreator { - private unowned ILogger logger; + private unowned Logger logger; private FriendRequestWidgetListener listener; - public FriendRequestWidgetCreator(ILogger logger, FriendRequestWidgetListener listener) { + public FriendRequestWidgetCreator(Logger logger, FriendRequestWidgetListener listener) { this.logger = logger; this.listener = listener; } diff --git a/src/view/ApplicationWindow.vala b/src/view/ApplicationWindow.vala index 8cee339..7e6905b 100644 --- a/src/view/ApplicationWindow.vala +++ b/src/view/ApplicationWindow.vala @@ -1,7 +1,7 @@ /* * ApplicationWindow.vala * - * Copyright (C) 2013-2018 Venom authors and contributors + * Copyright (C) 2013-2018 Venom authors and contributors * * This file is part of Venom. * @@ -25,6 +25,7 @@ namespace Venom { private const GLib.ActionEntry win_entries[] = { + { "activate", on_status_icon_activate }, { "add_contact", on_add_contact }, { "copy_id", on_copy_id }, { "filetransfer", on_filetransfer }, @@ -45,11 +46,21 @@ namespace Venom { [GtkChild] public Gtk.Box header_start; [GtkChild] public Gtk.Box header_end; - private unowned Factory.IWidgetFactory widget_factory; - private ILogger logger; + [GtkChild] private Gtk.Revealer notification_revealer; + [GtkChild] private Gtk.Button notification_dismiss; + [GtkChild] private Gtk.Button notification_action; + [GtkChild] private Gtk.Label notification_action_message; + [GtkChild] private Gtk.Label notification_message; + + private unowned Factory.WidgetFactory widget_factory; + private Logger logger; private ISettingsDatabase settings_database; - private IContactDatabase contact_database; - private IDhtNodeDatabase node_database; + private ContactRepository contact_repository; + private DhtNodeRepository node_repository; + private NospamRepository nospam_repository; + private MessageRepository message_repository; + private FriendRequestRepository friend_request_repository; + private Profile profile; private ToxSession session; private ToxAdapterFriendListenerImpl friend_listener; private ToxAdapterConferenceListenerImpl conference_listener; @@ -58,6 +69,7 @@ namespace Venom { private NotificationListener notification_listener; private WindowState window_state; private unowned ContactListViewModel contact_list_view_model; + private InAppNotification in_app_notification; private ObservableList contacts; private ObservableList transfers; @@ -67,39 +79,54 @@ namespace Venom { private GLib.HashTable conversations; private UserInfo user_info; - public ApplicationWindow(Gtk.Application application, Factory.IWidgetFactory widget_factory, ToxSession session, - IDhtNodeDatabase node_database, ISettingsDatabase settings_database, IContactDatabase contact_database) { + public ApplicationWindow(Gtk.Application application, Factory.WidgetFactory widget_factory, ToxSession session, Profile profile, + NospamRepository nospam_repository, FriendRequestRepository friend_request_repository, + MessageRepository message_repository, DhtNodeRepository node_repository, + ISettingsDatabase settings_database, ContactRepository contact_repository) { Object(application: application); conversations = new GLib.HashTable(null, null); user_info = new UserInfoImpl(); this.widget_factory = widget_factory; - this.logger = widget_factory.createLogger(); + this.logger = widget_factory.create_logger(); + this.profile = profile; logger.attach_to_glib(); - this.node_database = node_database; + this.node_repository = node_repository; this.settings_database = settings_database; - this.contact_database = contact_database; + this.contact_repository = contact_repository; + this.friend_request_repository = friend_request_repository; + this.nospam_repository = nospam_repository; + this.message_repository = message_repository; contacts = new ObservableList(); contacts.set_list(new GLib.List()); transfers = new ObservableList(); transfers.set_list(new GLib.List()); friend_requests = new ObservableList(); - friend_requests.set_list(new GLib.List()); + friend_requests.set_collection(friend_request_repository.query_all()); conference_invites = new ObservableList(); conference_invites.set_list(new GLib.List()); notification_listener = new NotificationListenerImpl(logger); notification_listener.clear_notifications(); + in_app_notification = new InAppNotification(notification_revealer, notification_dismiss, + notification_action, notification_action_message, notification_message); + + init_dht_nodes(); + + ((SqliteMessageRepository) message_repository).set_contacts(session.get_friends()); + session_listener = new ToxAdapterSelfListenerImpl(logger, user_info); - friend_listener = new ToxAdapterFriendListenerImpl(logger, user_info, contacts, friend_requests, conversations, notification_listener); + friend_listener = new ToxAdapterFriendListenerImpl(logger, user_info, message_repository, friend_request_repository, + contact_repository, contacts, friend_requests, conversations, notification_listener, in_app_notification); conference_listener = new ToxAdapterConferenceListenerImpl(logger, contacts, conference_invites, conversations, notification_listener); filetransfer_listener = new ToxAdapterFiletransferListenerImpl(logger, transfers, conversations, notification_listener); settings_database.bind_property("enable-send-typing", friend_listener, "show-typing", BindingFlags.SYNC_CREATE); + settings_database.bind_property("enable-logging", friend_listener, "enable-logging", BindingFlags.SYNC_CREATE); settings_database.bind_property("enable-notification-sounds", notification_listener, "play-sound-notifications", BindingFlags.SYNC_CREATE); settings_database.bind_property("enable-tray", status_icon, "visible", BindingFlags.SYNC_CREATE); @@ -120,6 +147,7 @@ namespace Venom { filetransfer_listener.attach_to_session(session); status_icon.activate.connect(on_status_icon_activate); + status_icon.popup_menu.connect(on_status_icon_popup_menu); delete_event.connect(on_delete_event); focus_in_event.connect(on_focus_in_event); window_state_event.connect(on_window_state_event); @@ -128,14 +156,64 @@ namespace Venom { show_welcome(); + Gtk.AccelMap.load(profile.accelsfile); + logger.d("ApplicationWindow created."); } ~ApplicationWindow() { logger.d("ApplicationWindow destroyed."); + + Gtk.AccelMap.save(profile.accelsfile); save_window_state(); } + private void add_nodes_to_repository(Gee.Iterable nodes) { + foreach (var node in nodes) { + node_repository.create(node); + } + } + + private void init_dht_nodes() { + var dht_nodes = node_repository.query_all(); + if (!dht_nodes.iterator().next()) { + logger.i("DHT Node database empty, populating from web..."); + add_nodes_to_repository((new StaticDhtNodeUpdater()).get_dht_nodes()); + add_nodes_to_repository((new JsonWebDhtNodeUpdater(logger)).get_dht_nodes()); + + if (!node_repository.query_all().iterator().next()) { + logger.e("Node initialisation from web database failed."); + } + } + } + + private void on_status_icon_popup_menu() { + logger.d("on_status_icon_popup_menu"); + + var menu = new GLib.Menu(); + menu.append(is_active ? _("Hide") : _("Show"), "win.activate"); + + var submenu = new GLib.Menu(); + submenu.append(_("Online"), "win.change_userstatus('online')"); + submenu.append(_("Away"), "win.change_userstatus('away')"); + submenu.append(_("Busy"), "win.change_userstatus('busy')"); + menu.append_submenu(_("Status"), submenu); + + var misc_section = new GLib.Menu(); + misc_section.append(_("Preferences"), "app.preferences"); + misc_section.append(_("About"), "app.about"); + menu.append_section(null, misc_section); + + var quit_section = new GLib.Menu(); + quit_section.append(_("Logout"), "app.logout"); + quit_section.append(_("Quit"), "app.quit"); + menu.append_section(null, quit_section); + + var shell = new Gtk.Menu.from_model(menu); + shell.attach_to_widget(this, null); + shell.popup_at_pointer(); + } + private void on_status_icon_activate() { if (is_active) { hide(); @@ -173,7 +251,7 @@ namespace Venom { private void init_window_state() { try { - var window_state_string = FileIO.load_contents_text(R.constants.window_state_filename()); + var window_state_string = FileIO.load_contents_text(profile.windowstatefile); window_state = WindowState.deserialize(window_state_string); } catch (Error e) { logger.i("Loading window state failed: " + e.message); @@ -196,18 +274,13 @@ namespace Venom { private void save_window_state() { try { var data = WindowState.serialize(window_state); - FileIO.save_contents_text(R.constants.window_state_filename(), data); + FileIO.save_contents_text(profile.windowstatefile, data); } catch (Error e) { logger.e("Saving window state failed: " + e.message); } } private void init_widgets() { - var screen = Gdk.Screen.get_default(); - var css_provider = new Gtk.CssProvider(); - css_provider.load_from_resource("/com/github/naxuroqa/venom/css/custom.css"); - Gtk.StyleContext.add_provider_for_screen(screen, css_provider, Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION); - var gtk_settings = Gtk.Settings.get_default(); settings_database.bind_property("enable-dark-theme", gtk_settings, "gtk-application-prefer-dark-theme", BindingFlags.SYNC_CREATE | BindingFlags.BIDIRECTIONAL); settings_database.bind_property("enable-animations", gtk_settings, "gtk-enable-animations", BindingFlags.SYNC_CREATE | BindingFlags.BIDIRECTIONAL); @@ -243,10 +316,10 @@ namespace Venom { logger.d("ApplicationWindow on_contact_selected"); if (contact is Contact) { var conv = conversations.@get(contact); - switch_content_with(() => { return new ConversationWindow(this, logger, conv, contact, settings_database, friend_listener, filetransfer_listener, filetransfer_listener); }); + switch_content_with(() => { return new ConversationWindow(this, logger, conv, contact, user_info, settings_database, friend_listener, filetransfer_listener, filetransfer_listener); }); } else if (contact is Conference) { var conv = conversations.@get(contact); - switch_content_with(() => { return new ConferenceWindow(this, logger, conv, contact, settings_database, conference_listener); }); + switch_content_with(() => { return new ConferenceWindow(this, logger, conv, contact, user_info, settings_database, conference_listener); }); } } @@ -255,7 +328,7 @@ namespace Venom { } public void show_settings() { - switch_content_with(() => { return widget_factory.createSettingsWidget(this, settings_database, node_database); }); + switch_content_with(() => { return widget_factory.create_settings_widget(this, settings_database, node_repository); }); } public void show_welcome() { @@ -263,7 +336,7 @@ namespace Venom { } private void on_show_user() { - switch_content_with(() => { return new UserInfoWidget(logger, this, user_info, session_listener); }); + switch_content_with(() => { return new UserInfoWidget(logger, this, nospam_repository, user_info, session_listener); }); } public void on_create_groupchat() { @@ -281,6 +354,9 @@ namespace Venom { public void on_show_conference(IContact contact) { switch_content_with(() => { return new ConferenceInfoWidget(logger, this, conference_listener, contact, settings_database); }); } + public void on_add_contact() { + switch_content_with(() => { return new AddContactWidget(logger, this, friend_requests, friend_listener, friend_listener); }); + } private IContact ? find_contact(string contact_id) { for (var i = 0; i < contacts.length(); i++) { @@ -306,9 +382,10 @@ namespace Venom { logger.d(@"on_mute_contact($contact_id)"); var c = find_contact(contact_id); if (c != null && c is Contact) { - (c as Contact)._show_notifications = false; + ((Contact)c)._show_notifications = false; + friend_listener.on_apply_friend_settings(c); } else if (c != null && c is Conference) { - (c as Conference)._show_notifications = false; + ((Conference)c)._show_notifications = false; } else { logger.e(@"Friend with id $contact_id not found."); } @@ -410,14 +487,6 @@ namespace Venom { } } - public void on_add_contact() { - logger.d("on_add_contact()"); - switch_content_with(() => { - var widget = new AddContactWidget(logger, this, friend_requests, friend_listener, friend_listener); - return widget; - }); - } - private void on_copy_id() { logger.d("on_copy_id()"); var clipboard = Gtk.Clipboard.@get(Gdk.SELECTION_CLIPBOARD); @@ -426,11 +495,10 @@ namespace Venom { } private void switch_content_with(owned WidgetProvider widget_provider) { - { - var previous = content_bin.get_child(); - if (previous != null) { - previous.destroy(); - } + var previous = content_bin.get_child(); + if (previous != null) { + previous.destroy(); + previous = null; } var current = widget_provider(); diff --git a/src/view/ConferenceInfoWidget.vala b/src/view/ConferenceInfoWidget.vala index 10e79ce..edb02d1 100644 --- a/src/view/ConferenceInfoWidget.vala +++ b/src/view/ConferenceInfoWidget.vala @@ -32,11 +32,11 @@ namespace Venom { [GtkChild] private Gtk.Box notifications_box; [GtkChild] private Gtk.Revealer notifications_notice; - private ILogger logger; + private Logger logger; private ConferenceInfoViewModel view_model; private unowned ApplicationWindow app_window; - public ConferenceInfoWidget(ILogger logger, ApplicationWindow app_window, ConferenceInfoWidgetListener listener, IContact contact, ISettingsDatabase settings_database) { + public ConferenceInfoWidget(Logger logger, ApplicationWindow app_window, ConferenceInfoWidgetListener listener, IContact contact, ISettingsDatabase settings_database) { logger.d("ConferenceInfoWidget created."); this.logger = logger; this.app_window = app_window; @@ -71,8 +71,8 @@ namespace Venom { } private class ConferencePeerWidgetCreator { - private unowned ILogger logger; - public ConferencePeerWidgetCreator(ILogger logger) { + private unowned Logger logger; + public ConferencePeerWidgetCreator(Logger logger) { this.logger = logger; } public Gtk.Widget create_peer_widget(Object o) { diff --git a/src/view/ConferenceInviteEntry.vala b/src/view/ConferenceInviteEntry.vala index a583b84..47fa31c 100644 --- a/src/view/ConferenceInviteEntry.vala +++ b/src/view/ConferenceInviteEntry.vala @@ -22,7 +22,7 @@ namespace Venom { [GtkTemplate(ui = "/com/github/naxuroqa/venom/ui/conference_invite_entry.ui")] public class ConferenceInviteEntry : Gtk.ListBoxRow { - private ILogger logger; + private Logger logger; private ConferenceInvite invite; private ConferenceInviteEntryListener listener; @@ -33,7 +33,7 @@ namespace Venom { [GtkChild] private Gtk.Button accept; [GtkChild] private Gtk.Button reject; - public ConferenceInviteEntry(ILogger logger, ConferenceInvite invite, ConferenceInviteEntryListener listener) { + public ConferenceInviteEntry(Logger logger, ConferenceInvite invite, ConferenceInviteEntryListener listener) { logger.d("ConferenceInviteEntry created."); this.logger = logger; this.invite = invite; diff --git a/src/view/ConferenceWindow.vala b/src/view/ConferenceWindow.vala index 3983e5d..e84ab2e 100644 --- a/src/view/ConferenceWindow.vala +++ b/src/view/ConferenceWindow.vala @@ -39,22 +39,24 @@ namespace Venom { }; private unowned ApplicationWindow app_window; - private ILogger logger; + private Logger logger; private ObservableList conversation; private ConferenceWidgetListener listener; private IContact contact; + private UserInfo user_info; private TextViewEventHandler text_view_event_handler; private AdjustmentAutoScroller auto_scroller; private Cancellable cancellable; private ObservableList peers_list; private Undo.TextBufferUndoBinding text_buffer_undo_binding; - public ConferenceWindow(ApplicationWindow app_window, ILogger logger, ObservableList conversation, IContact contact, ISettingsDatabase settings, ConferenceWidgetListener listener) { + public ConferenceWindow(ApplicationWindow app_window, Logger logger, ObservableList conversation, IContact contact, UserInfo user_info, ISettingsDatabase settings, ConferenceWidgetListener listener) { this.app_window = app_window; this.logger = logger; this.conversation = conversation; this.listener = listener; this.contact = contact; + this.user_info = user_info; this.cancellable = new Cancellable(); this.text_buffer_undo_binding = new Undo.TextBufferUndoBinding(); @@ -78,7 +80,7 @@ namespace Venom { update_widgets(); var model = new LazyObservableListModel(logger, conversation, cancellable); - var creator = new MessageWidgetCreator(logger, settings, null); + var creator = new MessageWidgetCreator(logger, user_info, settings, null); message_list.bind_model(model, creator.create_message); message_list.set_placeholder(placeholder); @@ -155,8 +157,8 @@ namespace Venom { } private class PeersListCreator { - ILogger logger; - public PeersListCreator(ILogger logger) { + Logger logger; + public PeersListCreator(Logger logger) { this.logger = logger; } public Gtk.Widget create(GLib.Object o) { diff --git a/src/view/ContactListEntry.vala b/src/view/ContactListEntry.vala index c3fd044..c8fea28 100644 --- a/src/view/ContactListEntry.vala +++ b/src/view/ContactListEntry.vala @@ -27,11 +27,11 @@ namespace Venom { [GtkChild] private Gtk.Image contact_image; [GtkChild] private Gtk.Image status_image; - private ILogger logger; + private Logger logger; private ContactListEntryViewModel view_model; private ContextStyleBinding attention_binding; - public ContactListEntry(ILogger logger, IContact contact) { + public ContactListEntry(Logger logger, IContact contact) { logger.d("ContactListEntry created."); this.logger = logger; this.view_model = new ContactListEntryViewModel(logger, contact, false); diff --git a/src/view/ContactListEntryCompact.vala b/src/view/ContactListEntryCompact.vala index 96cd64e..25d6544 100644 --- a/src/view/ContactListEntryCompact.vala +++ b/src/view/ContactListEntryCompact.vala @@ -27,11 +27,11 @@ namespace Venom { [GtkChild] private Gtk.Image contact_image; [GtkChild] private Gtk.Image status_image; - private ILogger logger; + private Logger logger; private ContactListEntryViewModel view_model; private ContextStyleBinding attention_binding; - public ContactListEntryCompact(ILogger logger, IContact contact) { + public ContactListEntryCompact(Logger logger, IContact contact) { logger.d("ContactListEntryCompact created."); this.logger = logger; this.view_model = new ContactListEntryViewModel(logger, contact, true); diff --git a/src/view/ContactListWidget.vala b/src/view/ContactListWidget.vala index eb1913d..c7f4787 100644 --- a/src/view/ContactListWidget.vala +++ b/src/view/ContactListWidget.vala @@ -22,7 +22,7 @@ namespace Venom { [GtkTemplate(ui = "/com/github/naxuroqa/venom/ui/contact_list_widget.ui")] public class ContactListWidget : Gtk.Box { - private ILogger logger; + private Logger logger; private ContactListViewModel view_model; [GtkChild] private Gtk.Box top_bar_box; @@ -41,7 +41,7 @@ namespace Venom { private unowned Gtk.ListBoxRow ? selected_row; private ListModel contact_list_model; - public ContactListWidget(ILogger logger, ApplicationWindow app_window, ObservableList contacts, ObservableList friend_requests, ObservableList conference_invites, ContactListWidgetCallback callback, UserInfo user_info, ISettingsDatabase settings_database) { + public ContactListWidget(Logger logger, ApplicationWindow app_window, ObservableList contacts, ObservableList friend_requests, ObservableList conference_invites, ContactListWidgetCallback callback, UserInfo user_info, ISettingsDatabase settings_database) { logger.d("ContactListWidget created."); this.logger = logger; this.view_model = new ContactListViewModel(logger, contacts, friend_requests, conference_invites, callback, user_info); @@ -126,9 +126,9 @@ namespace Venom { } private class ContactListEntryCreator { - private unowned ILogger logger; + private unowned Logger logger; private ISettingsDatabase settings_database; - public ContactListEntryCreator(ILogger logger, ISettingsDatabase settings_database) { + public ContactListEntryCreator(Logger logger, ISettingsDatabase settings_database) { this.logger = logger; this.settings_database = settings_database; } diff --git a/src/view/ConversationWindow.vala b/src/view/ConversationWindow.vala index 28c50ee..42ec9f5 100644 --- a/src/view/ConversationWindow.vala +++ b/src/view/ConversationWindow.vala @@ -45,21 +45,23 @@ namespace Venom { }; private unowned ApplicationWindow app_window; - private ILogger logger; + private Logger logger; private ObservableList conversation; private ConversationWidgetListener listener; private ConversationWidgetFiletransferListener filetransfer_listener; private bool is_typing; private IContact contact; + private UserInfo user_info; private TextViewEventHandler text_view_event_handler; private AdjustmentAutoScroller auto_scroller; private Cancellable cancellable; private Undo.TextBufferUndoBinding text_buffer_undo_binding; public ConversationWindow(ApplicationWindow app_window, - ILogger logger, + Logger logger, ObservableList conversation, IContact contact, + UserInfo user_info, ISettingsDatabase settings, ConversationWidgetListener listener, ConversationWidgetFiletransferListener filetransfer_listener, @@ -68,6 +70,7 @@ namespace Venom { this.logger = logger; this.conversation = conversation; this.contact = contact; + this.user_info = user_info; this.listener = listener; this.filetransfer_listener = filetransfer_listener; this.cancellable = new Cancellable(); @@ -95,7 +98,7 @@ namespace Venom { update_widgets(); var model = new LazyObservableListModel(logger, conversation, cancellable); - var creator = new MessageWidgetCreator(logger, settings, file_transfer_entry_listener); + var creator = new MessageWidgetCreator(logger, user_info, settings, file_transfer_entry_listener); message_list.bind_model(model, creator.create_message); message_list.set_placeholder(placeholder); @@ -302,18 +305,20 @@ namespace Venom { } public class MessageWidgetCreator { - private unowned ILogger logger; + private unowned Logger logger; + private UserInfo user_info; private ISettingsDatabase settings; private FileTransferEntryListener? file_transfer_entry_listener; - public MessageWidgetCreator(ILogger logger, ISettingsDatabase settings, FileTransferEntryListener? file_transfer_entry_listener) { + public MessageWidgetCreator(Logger logger, UserInfo user_info, ISettingsDatabase settings, FileTransferEntryListener? file_transfer_entry_listener) { this.logger = logger; + this.user_info = user_info; this.settings = settings; this.file_transfer_entry_listener = file_transfer_entry_listener; } public Gtk.Widget create_message(GLib.Object object) { - if (object is IMessage) { - return new MessageWidget(logger, (IMessage) object, settings); + if (object is Message) { + return new MessageWidget(logger, (Message) object, user_info, settings); } else if (file_transfer_entry_listener != null && object is FileTransfer) { return new FileTransferEntryInline(logger, (FileTransfer) object, file_transfer_entry_listener); } else { diff --git a/src/view/CreateGroupchatWidget.vala b/src/view/CreateGroupchatWidget.vala index 2a391e6..3cb10d1 100644 --- a/src/view/CreateGroupchatWidget.vala +++ b/src/view/CreateGroupchatWidget.vala @@ -36,11 +36,11 @@ namespace Venom { [GtkChild] private Gtk.Button accept_all; [GtkChild] private Gtk.Button reject_all; - private ILogger logger; + private Logger logger; private CreateGroupchatViewModel view_model; private ContainerChildBooleanBinding stack_binding; - public CreateGroupchatWidget(ILogger logger, ApplicationWindow app_window, ObservableList conference_invites_model, CreateGroupchatWidgetListener listener, ConferenceInviteEntryListener entry_listener) { + public CreateGroupchatWidget(Logger logger, ApplicationWindow app_window, ObservableList conference_invites_model, CreateGroupchatWidgetListener listener, ConferenceInviteEntryListener entry_listener) { logger.d("CreateGroupChatWidget created."); this.logger = logger; this.view_model = new CreateGroupchatViewModel(logger, conference_invites_model, listener, entry_listener); @@ -80,9 +80,9 @@ namespace Venom { } private class ConferenceInviteEntryCreator { - private ILogger logger; + private Logger logger; private ConferenceInviteEntryListener listener; - public ConferenceInviteEntryCreator(ILogger logger, ConferenceInviteEntryListener listener) { + public ConferenceInviteEntryCreator(Logger logger, ConferenceInviteEntryListener listener) { this.logger = logger; this.listener = listener; } diff --git a/src/view/ErrorWidget.vala b/src/view/ErrorWidget.vala index 39c18fb..3a08cc8 100644 --- a/src/view/ErrorWidget.vala +++ b/src/view/ErrorWidget.vala @@ -21,16 +21,26 @@ namespace Venom { [GtkTemplate(ui = "/com/github/naxuroqa/venom/ui/error_widget.ui")] - public class ErrorWidget : Gtk.Box { + public class ErrorWidget : Gtk.ApplicationWindow { [GtkChild] private Gtk.Stack stack; [GtkChild] private Gtk.Label message; [GtkChild] private Gtk.Button retry; + [GtkChild] private Gtk.TextView log_view; public signal void on_retry(); - public ErrorWidget(Gtk.ApplicationWindow application_window, string error_message) { + private Logger logger; + + public ErrorWidget(Gtk.Application application, Logger logger, string error_message) { + Object(application: application); + + this.logger = logger; message.label = error_message; retry.clicked.connect(() => { on_retry(); }); + + Gtk.TextIter iter; + log_view.buffer.get_start_iter(out iter); + log_view.buffer.insert_markup(ref iter, logger.get_log(), -1); } public void add_page(Gtk.Widget widget, string name, string title) { @@ -38,6 +48,7 @@ namespace Venom { scrolled_window.get_style_context().add_class("frame"); scrolled_window.add(widget); stack.add_titled(scrolled_window, name, title); + scrolled_window.show_all(); } } } diff --git a/src/view/FileTransferEntry.vala b/src/view/FileTransferEntry.vala index e7a5c11..c7ba3eb 100644 --- a/src/view/FileTransferEntry.vala +++ b/src/view/FileTransferEntry.vala @@ -30,11 +30,11 @@ namespace Venom { [GtkChild] private Gtk.Button stop_transfer; [GtkChild] private Gtk.Button remove_transfer; - private ILogger logger; + private Logger logger; private FileTransferEntryViewModel view_model; private FileTransferExternalCommands external_commands; - public FileTransferEntry(ILogger logger, FileTransfer file_transfer, FileTransferEntryListener listener) { + public FileTransferEntry(Logger logger, FileTransfer file_transfer, FileTransferEntryListener listener) { logger.d("FileTransferEntry created."); this.logger = logger; @@ -65,8 +65,8 @@ namespace Venom { } public class FileTransferExternalCommands : GLib.Object { - private ILogger logger; - public FileTransferExternalCommands(ILogger logger) { + private Logger logger; + public FileTransferExternalCommands(Logger logger) { this.logger = logger; } diff --git a/src/view/FileTransferEntryInline.vala b/src/view/FileTransferEntryInline.vala index 8f64519..3d5dc5e 100644 --- a/src/view/FileTransferEntryInline.vala +++ b/src/view/FileTransferEntryInline.vala @@ -30,11 +30,11 @@ namespace Venom { [GtkChild] private Gtk.Button stop_transfer; [GtkChild] private Gtk.Button remove_transfer; - private ILogger logger; + private Logger logger; private FileTransferEntryViewModel view_model; private FileTransferExternalCommands external_commands; - public FileTransferEntryInline(ILogger logger, FileTransfer file_transfer, FileTransferEntryListener listener) { + public FileTransferEntryInline(Logger logger, FileTransfer file_transfer, FileTransferEntryListener listener) { logger.d("FileTransferEntryInline created."); this.logger = logger; diff --git a/src/view/FileTransferWidget.vala b/src/view/FileTransferWidget.vala index 9e8caa9..b164d20 100644 --- a/src/view/FileTransferWidget.vala +++ b/src/view/FileTransferWidget.vala @@ -22,14 +22,14 @@ namespace Venom { [GtkTemplate(ui = "/com/github/naxuroqa/venom/ui/file_transfer_widget.ui")] public class FileTransferWidget : Gtk.Box { - private ILogger logger; + private Logger logger; private ObservableList transfers; private FileTransferEntryListener listener; [GtkChild] private Gtk.ListBox file_transfers; [GtkChild] private Gtk.Widget placeholder; - public FileTransferWidget(ILogger logger, ApplicationWindow app_window, ObservableList transfers, FileTransferEntryListener listener) { + public FileTransferWidget(Logger logger, ApplicationWindow app_window, ObservableList transfers, FileTransferEntryListener listener) { logger.d("FileTransferWidget created."); this.logger = logger; this.transfers = transfers; @@ -48,9 +48,9 @@ namespace Venom { } private class FileTransferEntryCreator { - private unowned ILogger logger; + private unowned Logger logger; private unowned FileTransferEntryListener listener; - public FileTransferEntryCreator(ILogger logger, FileTransferEntryListener listener) { + public FileTransferEntryCreator(Logger logger, FileTransferEntryListener listener) { this.logger = logger; this.listener = listener; } diff --git a/src/view/FriendInfoWidget.vala b/src/view/FriendInfoWidget.vala index 2534f04..2f2260c 100644 --- a/src/view/FriendInfoWidget.vala +++ b/src/view/FriendInfoWidget.vala @@ -39,11 +39,11 @@ namespace Venom { [GtkChild] private Gtk.Button remove_button; [GtkChild] private Gtk.Button apply; - private ILogger logger; + private Logger logger; private unowned ApplicationWindow app_window; private FriendInfoViewModel view_model; - public FriendInfoWidget(ILogger logger, ApplicationWindow app_window, FriendInfoWidgetListener listener, IContact contact, ISettingsDatabase settings_database) { + public FriendInfoWidget(Logger logger, ApplicationWindow app_window, FriendInfoWidgetListener listener, IContact contact, ISettingsDatabase settings_database) { logger.d("FriendInfoWidget created."); this.logger = logger; this.app_window = app_window; diff --git a/src/view/FriendRequestWidget.vala b/src/view/FriendRequestWidget.vala index b392e85..a7c62b5 100644 --- a/src/view/FriendRequestWidget.vala +++ b/src/view/FriendRequestWidget.vala @@ -1,7 +1,7 @@ /* * FriendRequestWidget.vala * - * Copyright (C) 2018 Venom authors and contributors + * Copyright (C) 2018 Venom authors and contributors * * This file is part of Venom. * @@ -22,7 +22,7 @@ namespace Venom { [GtkTemplate(ui = "/com/github/naxuroqa/venom/ui/friend_request_widget.ui")] public class FriendRequestWidget : Gtk.ListBoxRow { - private ILogger logger; + private Logger logger; private FriendRequest friend_request; private FriendRequestWidgetListener listener; @@ -34,17 +34,18 @@ namespace Venom { [GtkChild] private Gtk.Button accept; [GtkChild] private Gtk.Button reject; - public FriendRequestWidget(ILogger logger, FriendRequest friend_request, FriendRequestWidgetListener listener) { + public FriendRequestWidget(Logger logger, FriendRequest friend_request, FriendRequestWidgetListener listener) { logger.d("FriendRequestWidget created."); this.logger = logger; this.friend_request = friend_request; this.listener = listener; contact_id.label = friend_request.id; - contact_message.label = friend_request.message; + contact_message.label = _("“%s”").printf(friend_request.message); contact_time.label = TimeStamp.get_pretty_timestamp(friend_request.timestamp); + contact_time.tooltip_text = friend_request.timestamp.format("%c"); var pub_key = Tools.hexstring_to_bin(friend_request.id); - contact_image.pixbuf = round_corners(Identicon.generate_pixbuf(pub_key).scale_simple(44, 44, Gdk.InterpType.BILINEAR)); + contact_image.pixbuf = round_corners(Identicon.generate_pixbuf(pub_key, 40)); accept.clicked.connect(on_accept_clicked); reject.clicked.connect(on_reject_clicked); diff --git a/src/view/InAppNotification.vala b/src/view/InAppNotification.vala new file mode 100644 index 0000000..96a1066 --- /dev/null +++ b/src/view/InAppNotification.vala @@ -0,0 +1,99 @@ +/* + * InAppNotification.vala + * + * Copyright (C) 2018 Venom authors and contributors + * + * This file is part of Venom. + * + * Venom is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Venom is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Venom. If not, see . + */ + +namespace Venom { + public class InAppNotification : GLib.Object { + private const int TIMEOUT = 5; + + private NotificationAction? current_action; + + private Gtk.Revealer revealer; + private Gtk.Button dismiss; + private Gtk.Button action; + private Gtk.Label action_message; + private Gtk.Label message; + + uint timeout_source = 0; + + public InAppNotification(Gtk.Revealer revealer, + Gtk.Button dismiss, + Gtk.Button action, + Gtk.Label action_message, + Gtk.Label message) { + this.revealer = revealer; + this.dismiss = dismiss; + this.action = action; + this.action_message = action_message; + this.message = message; + + dismiss.clicked.connect(dismiss_notification); + action.clicked.connect(on_action_clicked); + } + + ~InAppNotification() { + remove_callback(); + } + + public void show_notification(NotificationAction action) { + this.current_action = action; + message.label = action.message; + action_message.label = action.action_message; + + revealer.set_reveal_child(true); + remove_callback(); + timeout_source = GLib.Timeout.add_seconds(TIMEOUT, timeout_callback); + } + + private void remove_callback() { + if (timeout_source > 0) { + GLib.Source.remove(timeout_source); + timeout_source = 0; + } + } + + private bool timeout_callback() { + timeout_source = 0; + dismiss_notification(); + return GLib.Source.REMOVE; + } + + public void dismiss_notification() { + current_action = null; + revealer.set_reveal_child(false); + remove_callback(); + } + + private void on_action_clicked() { + if (current_action != null) { + current_action.do_action(); + } + dismiss_notification(); + } + } + + public abstract class NotificationAction : GLib.Object { + public string message { get; set; } + public string action_message { get; set; } + + public virtual void do_action() { + } + } +} diff --git a/src/view/LoginWidget.vala b/src/view/LoginWidget.vala new file mode 100644 index 0000000..b94bf0d --- /dev/null +++ b/src/view/LoginWidget.vala @@ -0,0 +1,258 @@ +/* + * LoginWidget.vala + * + * Copyright (C) 2018 Venom authors and contributors + * + * This file is part of Venom. + * + * Venom is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Venom is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Venom. If not, see . + */ + +namespace Venom { + [GtkTemplate(ui = "/com/github/naxuroqa/venom/ui/login_widget.ui")] + public class LoginWidget : Gtk.ApplicationWindow { + private Logger logger; + private ContextStyleBinding accounts_arrow_binding; + private Profile selected_profile; + private GlobalSettings global_settings; + + [GtkChild] private Gtk.Widget login_page; + [GtkChild] private Gtk.Button login_button; + [GtkChild] private Gtk.Entry login_password_entry; + [GtkChild] private Gtk.Switch login_automatically_switch; + + [GtkChild] private Gtk.ToggleButton accounts_toggle; + [GtkChild] private Gtk.Revealer accounts_revealer; + [GtkChild] private Gtk.Image accounts_arrow; + [GtkChild] private Gtk.Stack stack; + [GtkChild] private Gtk.ListBox profiles_listbox; + + [GtkChild] private Gtk.Label selected_profile_label; + [GtkChild] private Gtk.Stack login_password_stack; + + [GtkChild] private Gtk.Entry create_username; + [GtkChild] private Gtk.Revealer create_username_revealer; + [GtkChild] private Gtk.Label create_username_error; + + [GtkChild] private Gtk.Entry create_password; + [GtkChild] private Gtk.Revealer create_password_revealer; + [GtkChild] private Gtk.Label create_password_error; + + [GtkChild] private Gtk.Entry create_password_repeat; + [GtkChild] private Gtk.Revealer create_password_repeat_revealer; + [GtkChild] private Gtk.Label create_password_repeat_error; + + [GtkChild] private Gtk.Widget create_page; + [GtkChild] private Gtk.Button create_button; + + public LoginWidget(Gtk.Application application, GlobalSettings global_settings, Logger logger) { + Object(application: application); + logger.d("LoginWidget created."); + this.logger = logger; + this.global_settings = global_settings; + + var path = R.constants.default_profile_dir(); + var profiles = Profile.scan_profiles(logger, path); + var it = profiles.iterator(); + if (it.next()) { + var profile_to_select = it.@get(); + if (global_settings.last_used_profile.length > 0 && profile_to_select.name != global_settings.last_used_profile) { + while(it.next()) { + var prof = it.@get(); + if (prof.name == global_settings.last_used_profile) { + profile_to_select = prof; + break; + } + } + } + set_selected_profile(profile_to_select); + update_profiles_list(profiles); + } else { + login_page.visible = false; + } + + accounts_toggle.bind_property("active", accounts_revealer, "reveal_child", GLib.BindingFlags.SYNC_CREATE); + accounts_arrow_binding = new ContextStyleBinding(accounts_arrow, "flip"); + accounts_toggle.bind_property("active", accounts_arrow_binding, "enable", GLib.BindingFlags.SYNC_CREATE); + + stack.set_focus_child.connect(on_set_focus_child); + login_button.clicked.connect(login); + create_button.clicked.connect(create); + + create_username.notify["is-focus"].connect(() => { + if (!create_username.is_focus) { + is_create_username_valid(); + } + }); + create_password.notify["is-focus"].connect(() => { + if (!create_password.is_focus) { + is_create_password_valid(); + } + }); + create_password_repeat.notify["is-focus"].connect(() => { + if (!create_password_repeat.is_focus) { + is_create_password_repeat_valid(); + } + }); + + profiles_listbox.row_selected.connect(on_login_row_selected); + } + + private void on_login_row_selected(Gtk.ListBoxRow? row) { + logger.d("on_login_row_selected"); + if (row != null) { + var entry = (ProfileEntry) row; + set_selected_profile(entry.profile); + } + } + + private void set_selected_profile(Profile profile) { + selected_profile = profile; + selected_profile_label.label = profile.name; + if (profile.is_encrypted) { + login_password_stack.visible_child_name = "pass"; + } else { + login_password_stack.visible_child_name = "auto"; + } + } + + private void update_profiles_list(Gee.Iterable profiles) { + profiles_listbox.foreach ((element) => profiles_listbox.remove(element)); + foreach (var profile in profiles) { + var row = new ProfileEntry(logger, profile); + profiles_listbox.add(row); + if (selected_profile == profile) { + profiles_listbox.select_row(row); + } + } + } + + private void on_set_focus_child(Gtk.Widget? child) { + if (child == login_page) { + login_button.grab_default(); + } else if (child == create_page) { + create_button.grab_default(); + } + } + + private void login() { + logger.d("login clicked"); + + try { + if (selected_profile.is_encrypted) { + selected_profile.load(login_password_entry.text); + global_settings.auto_login = false; + } else { + global_settings.auto_login = login_automatically_switch.active; + } + + global_settings.last_used_profile = selected_profile.name; + + var app = GLib.Application.get_default() as Application; + app.try_show_main_window(selected_profile); + } catch (Error e) { + logger.e("Cannot load profile: " + e.message); + login_password_entry.secondary_icon_name = "dialog-error-symbolic"; + login_password_entry.secondary_icon_tooltip_text = _("Wrong password"); + } + } + + private bool is_create_username_valid() { + if (create_username.text == "") { + create_username_error.label = _("Username can not be empty"); + } else if (!Profile.is_username_available(R.constants.default_profile_dir(), create_username.text)) { + create_username_error.label = _("Username is already taken"); + } else { + create_username_revealer.reveal_child = false; + create_username.secondary_icon_name = "emblem-ok-symbolic"; + return true; + } + + create_username.secondary_icon_name = ""; + create_username_revealer.reveal_child = true; + return false; + } + + private bool is_create_password_valid() { + if (create_password.text.length > 0 && create_password.text.length < 6) { + create_password_error.label = _("Password must be at least 6 characters long"); + create_password_revealer.reveal_child = true; + create_password.secondary_icon_name = ""; + return false; + } + + create_password.secondary_icon_name = "emblem-ok-symbolic"; + create_password_revealer.reveal_child = false; + return true; + } + + private bool is_create_password_repeat_valid() { + if (create_password.text != create_password_repeat.text) { + create_password_repeat_error.label = _("Passwords must match"); + create_password_repeat_revealer.reveal_child = true; + create_password_repeat.secondary_icon_name = ""; + return false; + } + + create_password_repeat.secondary_icon_name = "emblem-ok-symbolic"; + create_password_repeat_revealer.reveal_child = false; + return true; + } + + private void create() { + logger.d("create clicked"); + + if (is_create_username_valid() && is_create_password_valid() && is_create_password_repeat_valid()) { + try { + var profile = Profile.create(R.constants.default_profile_dir(), create_username.text, create_password.text); + + global_settings.last_used_profile = profile.name; + global_settings.auto_login = false; + + var app = GLib.Application.get_default() as Application; + app.try_show_main_window(profile); + } catch (Error e) { + create_username_error.label = _("Creating profile failed: ") + e.message; + create_username_revealer.reveal_child = true; + } + } + } + + ~LoginWidget() { + logger.d("LoginWidget destroyed."); + } + + private class ProfileEntry : Gtk.ListBoxRow { + private Logger logger; + public Profile profile; + + public ProfileEntry(Logger logger, Profile profile) { + this.logger = logger; + this.profile = profile; + + var box = new Gtk.Box(Gtk.Orientation.HORIZONTAL, 6); + box.pack_start(new Gtk.Image.from_icon_name("avatar-default-symbolic", Gtk.IconSize.BUTTON), false, false); + box.pack_start(new Gtk.Label(profile.name), false, false); + if (profile.is_encrypted) { + var image = new Gtk.Image.from_icon_name("security-high-symbolic", Gtk.IconSize.BUTTON); + image.tooltip_text = _("Profile is encrypted"); + box.pack_end(image, false, false); + } + box.border_width = 6; + add(box); + show_all(); + } + } + } +} diff --git a/src/view/MessageWidget.vala b/src/view/MessageWidget.vala index 86a5a28..75814b1 100644 --- a/src/view/MessageWidget.vala +++ b/src/view/MessageWidget.vala @@ -28,15 +28,15 @@ namespace Venom { [GtkChild] private Gtk.Label message; [GtkChild] private Gtk.Image sent; - private ILogger logger; + private Logger logger; private MessageViewModel view_model; private UriTransform uri_transform; private PangoTransform pango_transform; private ContextStyleBinding dim_binding; - public MessageWidget(ILogger logger, IMessage message_content, ISettingsDatabase settings) { + public MessageWidget(Logger logger, Message message_content, UserInfo user_info, ISettingsDatabase settings) { this.logger = logger; - this.view_model = new MessageViewModel(logger, message_content, settings); + this.view_model = new MessageViewModel(logger, message_content, user_info, settings); this.uri_transform = new UriTransform(logger); this.pango_transform = new PangoTransform(); this.dim_binding = new ContextStyleBinding(sent, "dim-label"); @@ -52,6 +52,7 @@ namespace Venom { view_model.bind_property("sent-visible", sent, "visible", GLib.BindingFlags.SYNC_CREATE); view_model.bind_property("sent-tooltip", sent, "tooltip-text", GLib.BindingFlags.SYNC_CREATE); view_model.bind_property("sent-dim", dim_binding, "enable", GLib.BindingFlags.SYNC_CREATE); + view_model.bind_property("sent-visible", sent, "visible", GLib.BindingFlags.SYNC_CREATE); logger.d("MessageWidget created."); } diff --git a/src/view/NodeWidget.vala b/src/view/NodeWidget.vala index 5fd61fb..d5af9da 100644 --- a/src/view/NodeWidget.vala +++ b/src/view/NodeWidget.vala @@ -1,7 +1,7 @@ /* * NodeWidget.vala * - * Copyright (C) 2013-2018 Venom authors and contributors + * Copyright (C) 2013-2018 Venom authors and contributors * * This file is part of Venom. * @@ -22,23 +22,18 @@ namespace Venom { [GtkTemplate(ui = "/com/github/naxuroqa/venom/ui/node_widget.ui")] public class NodeWidget : Gtk.ListBoxRow { - [GtkChild] - private Gtk.Label host; - [GtkChild] - private Gtk.Label public_key; - [GtkChild] - private Gtk.Label maintainer; - [GtkChild] - private Gtk.Label location; - [GtkChild] - private Gtk.Switch enabled; + [GtkChild] private Gtk.Label host; + [GtkChild] private Gtk.Label public_key; + [GtkChild] private Gtk.Label maintainer; + [GtkChild] private Gtk.Label location; + [GtkChild] private Gtk.Switch enabled; - public signal void node_changed(IDhtNode node); + public signal void node_changed(DhtNode node); - private ILogger logger; - private IDhtNode node; + private Logger logger; + private DhtNode node; - public NodeWidget(ILogger logger, IDhtNode node) { + public NodeWidget(Logger logger, DhtNode node) { this.logger = logger; this.node = node; diff --git a/src/view/NospamEntry.vala b/src/view/NospamEntry.vala new file mode 100644 index 0000000..6ffea90 --- /dev/null +++ b/src/view/NospamEntry.vala @@ -0,0 +1,58 @@ +/* + * NospamEntry.vala + * + * Copyright (C) 2018 Venom authors and contributors + * + * This file is part of Venom. + * + * Venom is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Venom is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Venom. If not, see . + */ + +namespace Venom { + [GtkTemplate(ui = "/com/github/naxuroqa/venom/ui/nospam_entry.ui")] + public class NospamEntry : Gtk.ListBoxRow { + [GtkChild] private Gtk.Label nospam; + [GtkChild] private Gtk.Label timestamp; + [GtkChild] private Gtk.Button remove; + + public signal void remove_clicked(Nospam item); + public signal void row_activated(Nospam item); + + private Logger logger; + private Nospam item; + + public NospamEntry(Logger logger, Nospam item) { + this.logger = logger; + this.item = item; + + nospam.label = "0x%.8X".printf(item.nospam); + timestamp.label = item.timestamp.format("%c"); + + remove.clicked.connect(on_remove_clicked); + logger.d("NospamEntry created."); + } + + public void on_row_clicked() { + row_activated(item); + } + + private void on_remove_clicked() { + remove_clicked(item); + } + + ~NospamEntry() { + logger.d("NospamEntry destroyed."); + } + } +} diff --git a/src/view/PeerEntry.vala b/src/view/PeerEntry.vala index 05ca43b..a0f1b14 100644 --- a/src/view/PeerEntry.vala +++ b/src/view/PeerEntry.vala @@ -28,9 +28,9 @@ namespace Venom { [GtkChild] private Gtk.Image peer_known; [GtkChild] private Gtk.Image peer_self; - private ILogger logger; + private Logger logger; - public PeerEntry(ILogger logger, ConferencePeer peer) { + public PeerEntry(Logger logger, ConferencePeer peer) { this.logger = logger; peer_name.label = peer.peer_name; peer_key.label = peer.peer_key; @@ -53,9 +53,9 @@ namespace Venom { [GtkChild] private Gtk.Image peer_known; [GtkChild] private Gtk.Image peer_self; - private ILogger logger; + private Logger logger; - public PeerEntryCompact(ILogger logger, ConferencePeer peer) { + public PeerEntryCompact(Logger logger, ConferencePeer peer) { this.logger = logger; peer_name.label = peer.peer_name; peer_known.visible = peer.is_known; diff --git a/src/view/SettingsWidget.vala b/src/view/SettingsWidget.vala index f1e1129..b7592b3 100644 --- a/src/view/SettingsWidget.vala +++ b/src/view/SettingsWidget.vala @@ -23,12 +23,11 @@ namespace Venom { [GtkTemplate(ui = "/com/github/naxuroqa/venom/ui/settings_widget.ui")] public class SettingsWidget : Gtk.Box { private ISettingsDatabase settingsDatabase; - private IDhtNodeDatabase nodeDatabase; - private ILogger logger; + private DhtNodeRepository node_repository; + private Logger logger; [GtkChild] private Gtk.Stack stack; [GtkChild] private Gtk.ListBox sidebar; - [GtkChild] private Gtk.ListBox node_list_box; [GtkChild] private Gtk.Switch enable_dark_theme; [GtkChild] private Gtk.Switch enable_animations; @@ -47,9 +46,6 @@ namespace Venom { [GtkChild] private Gtk.Switch enable_show_typing; [GtkChild] private Gtk.Switch keep_history; - [GtkChild] private Gtk.Revealer history_revealer; - [GtkChild] private Gtk.RadioButton history_keep_radio; - [GtkChild] private Gtk.SpinButton history_delete_spinbutton; [GtkChild] private Gtk.Switch enable_udp; [GtkChild] private Gtk.Switch enable_ipv6; @@ -63,15 +59,18 @@ namespace Venom { [GtkChild] private Gtk.Entry custom_proxy_host; [GtkChild] private Gtk.SpinButton custom_proxy_port; + [GtkChild] private Gtk.ListBox node_list_box; + [GtkChild] private Gtk.Button update_nodes; + private ObservableList dht_nodes; private ObservableListModel list_model; private StackIndexTransform stack_transform; - public SettingsWidget(ILogger logger, ApplicationWindow? app_window, ISettingsDatabase settingsDatabase, IDhtNodeDatabase nodeDatabase) { + public SettingsWidget(Logger logger, ApplicationWindow? app_window, ISettingsDatabase settingsDatabase, DhtNodeRepository node_repository) { logger.d("SettingsWidget created."); this.logger = logger; this.settingsDatabase = settingsDatabase; - this.nodeDatabase = nodeDatabase; + this.node_repository = node_repository; if (app_window != null) { app_window.reset_header_bar(); @@ -98,10 +97,7 @@ namespace Venom { settingsDatabase.bind_property("enable-send-typing", enable_show_typing, "active", BindingFlags.SYNC_CREATE | BindingFlags.BIDIRECTIONAL); - settingsDatabase.bind_property("enable-logging", keep_history, "active", BindingFlags.SYNC_CREATE | BindingFlags.BIDIRECTIONAL); - settingsDatabase.bind_property("enable-logging", history_revealer, "reveal-child", BindingFlags.SYNC_CREATE); - settingsDatabase.bind_property("enable-infinite-log", history_keep_radio, "active", BindingFlags.SYNC_CREATE | BindingFlags.BIDIRECTIONAL); - settingsDatabase.bind_property("days-to-log", history_delete_spinbutton, "value", BindingFlags.SYNC_CREATE | BindingFlags.BIDIRECTIONAL); + settingsDatabase.bind_property("enable-logging", keep_history, "active", BindingFlags.SYNC_CREATE | BindingFlags.BIDIRECTIONAL); settingsDatabase.bind_property("enable-udp", enable_udp, "active", BindingFlags.SYNC_CREATE | BindingFlags.BIDIRECTIONAL); settingsDatabase.bind_property("enable-ipv6", enable_ipv6, "active", BindingFlags.SYNC_CREATE | BindingFlags.BIDIRECTIONAL); @@ -115,33 +111,49 @@ namespace Venom { settingsDatabase.bind_property("custom-proxy-host", custom_proxy_host, "text", BindingFlags.SYNC_CREATE | BindingFlags.BIDIRECTIONAL); settingsDatabase.bind_property("custom-proxy-port", custom_proxy_port, "value", BindingFlags.SYNC_CREATE | BindingFlags.BIDIRECTIONAL); - var dhtNodeFactory = new DhtNodeFactory(); + update_nodes.clicked.connect(update_nodes_from_web); + dht_nodes = new ObservableList(); - dht_nodes.set_list(nodeDatabase.getDhtNodes(dhtNodeFactory)); - list_model = new ObservableListModel(dht_nodes); - var creator = new SettingsDhtNodeCreator(logger, this); - node_list_box.bind_model(list_model, creator.create_dht_node); + reset_node_list(); } ~SettingsWidget() { logger.d("SettingsWidget destroyed."); } - public void on_node_changed(IDhtNode node) { + private void update_nodes_from_web() { + logger.d("update_nodes_from_web"); + var updater = new JsonWebDhtNodeUpdater(logger); + foreach (var node in updater.get_dht_nodes()) { + node_repository.create(node); + } + reset_node_list(); + } + + private void reset_node_list() { + dht_nodes.set_collection(node_repository.query_all().order_by((a, b) => { + return strcmp(a.location, b.location); + })); + list_model = new ObservableListModel(dht_nodes); + var creator = new SettingsDhtNodeCreator(logger, this); + node_list_box.bind_model(list_model, creator.create_dht_node); + } + + public void on_node_changed(DhtNode node) { logger.d("on_node_changed"); - nodeDatabase.insertDhtNode(node.pub_key, node.host, node.port, node.is_blocked, node.maintainer, node.location); + node_repository.update(node); } private class SettingsDhtNodeCreator { - private unowned ILogger logger; + private unowned Logger logger; private unowned SettingsWidget settings_widget; - public SettingsDhtNodeCreator(ILogger logger, SettingsWidget settings_widget) { + public SettingsDhtNodeCreator(Logger logger, SettingsWidget settings_widget) { this.logger = logger; this.settings_widget = settings_widget; } public Gtk.Widget create_dht_node(GLib.Object o) { - var node = o as IDhtNode; + var node = o as DhtNode; var widget = new NodeWidget(logger, node); widget.node_changed.connect(settings_widget.on_node_changed); return widget; diff --git a/src/view/UserInfoWidget.vala b/src/view/UserInfoWidget.vala index 9e973ef..f1f15ce 100644 --- a/src/view/UserInfoWidget.vala +++ b/src/view/UserInfoWidget.vala @@ -1,7 +1,7 @@ /* * UserInfoWidget.vala * - * Copyright (C) 2013-2018 Venom authors and contributors + * Copyright (C) 2013-2018 Venom authors and contributors * * This file is part of Venom. * @@ -31,13 +31,25 @@ namespace Venom { [GtkChild] private Gtk.FlowBox avatars; [GtkChild] private Gtk.Button apply; - private ILogger logger; + [GtkChild] private Gtk.ToggleButton toggle_advanced; + [GtkChild] private Gtk.Image toggle_arrow; + [GtkChild] private Gtk.Revealer revealer_advanced; + [GtkChild] private Gtk.Button button_nospam; + [GtkChild] private Gtk.Entry entry_nospam; + [GtkChild] private Gtk.ListBox listbox_nospam; + + private Logger logger; private UserInfoViewModel view_model; + private ObservableList nospams; + private ObservableListModel nospams_model; + private ContextStyleBinding toggle_arrow_binding; + private NospamRepository nospam_repository; - public UserInfoWidget(ILogger logger, ApplicationWindow app_window, UserInfo user_info, UserInfoViewListener listener) { + public UserInfoWidget(Logger logger, ApplicationWindow app_window, NospamRepository nospam_repository, UserInfo user_info, UserInfoViewListener listener) { logger.d("UserInfoWidget created."); this.logger = logger; - this.view_model = new UserInfoViewModel(logger, user_info, listener); + this.nospam_repository = nospam_repository; + this.view_model = new UserInfoViewModel(logger, nospam_repository, user_info, listener); app_window.reset_header_bar(); view_model.bind_property("username", app_window.header_bar, "title", GLib.BindingFlags.SYNC_CREATE); @@ -48,8 +60,22 @@ namespace Venom { view_model.bind_property("avatar", avatar, "pixbuf", GLib.BindingFlags.SYNC_CREATE | GLib.BindingFlags.BIDIRECTIONAL); view_model.bind_property("tox-id", label_id, "label", GLib.BindingFlags.SYNC_CREATE); - var creator = new AvatarChildCreator(logger); - avatars.bind_model(view_model.get_avatars_model(), creator.create); + view_model.bind_property("tox-nospam", entry_nospam, "text", GLib.BindingFlags.SYNC_CREATE | GLib.BindingFlags.BIDIRECTIONAL); + entry_nospam.icon_press.connect(view_model.on_random_nospam); + + toggle_advanced.bind_property("active", revealer_advanced, "reveal_child", GLib.BindingFlags.SYNC_CREATE); + toggle_arrow_binding = new ContextStyleBinding(toggle_arrow, "flip"); + toggle_advanced.bind_property("active", toggle_arrow_binding, "enable", GLib.BindingFlags.SYNC_CREATE); + button_nospam.clicked.connect(view_model.on_set_nospam_clicked); + + listbox_nospam.row_activated.connect((row) => { ((NospamEntry) row).on_row_clicked(); }); + + nospams = new ObservableList(); + reset_nospam_model(); + view_model.reset_nospams.connect(reset_nospam_model); + + var avatar_creator = new AvatarChildCreator(logger); + avatars.bind_model(view_model.get_avatars_model(), avatar_creator.create); avatars.child_activated.connect(on_flowbox_activated); var imagefilter = new Gtk.FileFilter(); @@ -62,6 +88,17 @@ namespace Venom { apply.clicked.connect(view_model.on_apply_clicked); } + private void reset_nospam_model() { + var nospam_traversable = nospam_repository.query_all() + .order_by((a, b) => { + return ((Nospam)b).timestamp.compare(((Nospam)a).timestamp); + }); + nospams.set_collection(nospam_traversable); + nospams_model = new ObservableListModel(nospams); + var nospam_creator = new NospamEntryCreator(logger, view_model); + listbox_nospam.bind_model(nospams_model, nospam_creator.create); + } + private void on_flowbox_activated(Gtk.FlowBoxChild child) { view_model.set_file((child as AvatarChild).file); } @@ -81,11 +118,27 @@ namespace Venom { logger.d("UserInfoWidget destroyed."); } + private class NospamEntryCreator { + private unowned Logger logger; + private unowned UserInfoViewModel vm; + public NospamEntryCreator(Logger logger, UserInfoViewModel vm) { + this.logger = logger; + this.vm = vm; + } + + public Gtk.Widget create(GLib.Object o) { + var e = new NospamEntry(logger, o as Nospam); + e.remove_clicked.connect(vm.on_remove_nospam_clicked); + e.row_activated.connect(vm.on_select_nospam); + return e; + } + } + private class AvatarChild : Gtk.FlowBoxChild { public File file { get; set; } - private ILogger logger; - public AvatarChild(ILogger logger, GLib.File file) { + private Logger logger; + public AvatarChild(Logger logger, GLib.File file) { this.logger = logger; this.file = file; @@ -101,8 +154,8 @@ namespace Venom { } private class AvatarChildCreator { - private ILogger logger; - public AvatarChildCreator(ILogger logger) { + private Logger logger; + public AvatarChildCreator(Logger logger) { this.logger = logger; } public Gtk.Widget create(GLib.Object o) { diff --git a/src/view/WelcomeWidget.vala b/src/view/WelcomeWidget.vala index 6354ee4..21a8ce9 100644 --- a/src/view/WelcomeWidget.vala +++ b/src/view/WelcomeWidget.vala @@ -22,7 +22,7 @@ namespace Venom { [GtkTemplate(ui = "/com/github/naxuroqa/venom/ui/welcome_widget.ui")] public class WelcomeWidget : Gtk.Box { - private ILogger logger; + private Logger logger; private GLib.Rand rand = new GLib.Rand(); [GtkChild] private Gtk.Button link_learn_more; @@ -31,7 +31,7 @@ namespace Venom { [GtkChild] private Gtk.Label title; [GtkChild] private Gtk.Label content; - public WelcomeWidget(ILogger logger, ApplicationWindow app_window) { + public WelcomeWidget(Logger logger, ApplicationWindow app_window) { logger.d("WelcomeWidget created."); this.logger = logger; diff --git a/src/viewmodel/AddContactViewModel.vala b/src/viewmodel/AddContactViewModel.vala index 52955bf..489d599 100644 --- a/src/viewmodel/AddContactViewModel.vala +++ b/src/viewmodel/AddContactViewModel.vala @@ -29,11 +29,11 @@ namespace Venom { public Gdk.Pixbuf contact_image { get; set; } public bool contact_image_visible { get; set; } - private ILogger logger; + private Logger logger; private AddContactWidgetListener listener; private ObservableList friend_requests; - public AddContactViewModel(ILogger logger, ObservableList friend_requests, AddContactWidgetListener listener) { + public AddContactViewModel(Logger logger, ObservableList friend_requests, AddContactWidgetListener listener) { logger.d("AddContactViewModel created."); this.logger = logger; this.friend_requests = friend_requests; @@ -52,8 +52,8 @@ namespace Venom { contact_id_error_visible = false; var id = Tools.hexstring_to_bin(contact_id); if (id.length == ToxCore.address_size()) { - var pixbuf = Identicon.generate_pixbuf(id[0 : ToxCore.public_key_size()]); - contact_image = round_corners(pixbuf.scale_simple(44, 44, Gdk.InterpType.BILINEAR)); + var pixbuf = Identicon.generate_pixbuf(id[0 : ToxCore.public_key_size()], 40); + contact_image = round_corners(pixbuf); contact_image_visible = true; } else { contact_image_visible = false; diff --git a/src/viewmodel/ConferenceInfoViewModel.vala b/src/viewmodel/ConferenceInfoViewModel.vala index 055f0f6..57d3fb4 100644 --- a/src/viewmodel/ConferenceInfoViewModel.vala +++ b/src/viewmodel/ConferenceInfoViewModel.vala @@ -28,13 +28,13 @@ namespace Venom { public bool show_notifications { get; set; } public signal void leave_view(); - private ILogger logger; + private Logger logger; private ConferenceInfoWidgetListener listener; private Conference contact; private ObservableList peers_list; - public ConferenceInfoViewModel(ILogger logger, ConferenceInfoWidgetListener listener, Conference contact) { + public ConferenceInfoViewModel(Logger logger, ConferenceInfoWidgetListener listener, Conference contact) { logger.d("ConferenceInfoViewModel created."); this.logger = logger; this.contact = contact; diff --git a/src/viewmodel/ContactListEntryViewModel.vala b/src/viewmodel/ContactListEntryViewModel.vala index fbd711f..2f94c37 100644 --- a/src/viewmodel/ContactListEntryViewModel.vala +++ b/src/viewmodel/ContactListEntryViewModel.vala @@ -21,7 +21,7 @@ namespace Venom { public class ContactListEntryViewModel : GLib.Object { - private ILogger logger; + private Logger logger; private IContact contact; private bool compact; @@ -32,7 +32,7 @@ namespace Venom { public string contact_status_tooltip { get; set; } public bool contact_requires_attention { get; set; } - public ContactListEntryViewModel(ILogger logger, IContact contact, bool compact) { + public ContactListEntryViewModel(Logger logger, IContact contact, bool compact) { logger.d("ContactListEntryViewModel created."); this.logger = logger; this.contact = contact; diff --git a/src/viewmodel/ContactListViewModel.vala b/src/viewmodel/ContactListViewModel.vala index 4c6e96a..ee55043 100644 --- a/src/viewmodel/ContactListViewModel.vala +++ b/src/viewmodel/ContactListViewModel.vala @@ -21,7 +21,7 @@ namespace Venom { public class ContactListViewModel : GLib.Object { - private ILogger logger; + private Logger logger; private ContactListWidgetCallback callback; private UserInfo user_info; private ObservableList contacts; @@ -40,7 +40,7 @@ namespace Venom { public bool conference_invite_visible { get; set; } public string conference_invite_label { get; set; } - public ContactListViewModel(ILogger logger, ObservableList contacts, ObservableList friend_requests, ObservableList conference_invites, ContactListWidgetCallback callback, UserInfo user_info) { + public ContactListViewModel(Logger logger, ObservableList contacts, ObservableList friend_requests, ObservableList conference_invites, ContactListWidgetCallback callback, UserInfo user_info) { logger.d("ContactListViewModel created."); this.logger = logger; this.contacts = contacts; diff --git a/src/viewmodel/CreateGroupchatViewModel.vala b/src/viewmodel/CreateGroupchatViewModel.vala index 61565d7..d8ff763 100644 --- a/src/viewmodel/CreateGroupchatViewModel.vala +++ b/src/viewmodel/CreateGroupchatViewModel.vala @@ -31,12 +31,12 @@ namespace Venom { public signal void leave_view(); - private ILogger logger; + private Logger logger; private ObservableList conference_invites; private CreateGroupchatWidgetListener listener; ConferenceInviteEntryListener entry_listener; - public CreateGroupchatViewModel(ILogger logger, ObservableList conference_invites, CreateGroupchatWidgetListener listener, ConferenceInviteEntryListener entry_listener) { + public CreateGroupchatViewModel(Logger logger, ObservableList conference_invites, CreateGroupchatWidgetListener listener, ConferenceInviteEntryListener entry_listener) { logger.d("CreateGroupchatViewModel created."); this.logger = logger; this.listener = listener; diff --git a/src/viewmodel/FileTransferEntryViewModel.vala b/src/viewmodel/FileTransferEntryViewModel.vala index e594cb5..3f0d0f2 100644 --- a/src/viewmodel/FileTransferEntryViewModel.vala +++ b/src/viewmodel/FileTransferEntryViewModel.vala @@ -21,7 +21,7 @@ namespace Venom { public class FileTransferEntryViewModel : GLib.Object { - private ILogger logger; + private Logger logger; private unowned FileTransfer file_transfer; private unowned FileTransferEntryListener listener; @@ -35,7 +35,7 @@ namespace Venom { public signal void open_file(string filename); public signal void open_save_file_dialog(string path, string filename); - public FileTransferEntryViewModel(ILogger logger, FileTransfer file_transfer, FileTransferEntryListener listener) { + public FileTransferEntryViewModel(Logger logger, FileTransfer file_transfer, FileTransferEntryListener listener) { logger.d("FileTransferEntryViewModel created."); this.logger = logger; this.file_transfer = file_transfer; diff --git a/src/viewmodel/FriendInfoViewModel.vala b/src/viewmodel/FriendInfoViewModel.vala index ebd40d6..8bf9335 100644 --- a/src/viewmodel/FriendInfoViewModel.vala +++ b/src/viewmodel/FriendInfoViewModel.vala @@ -36,11 +36,11 @@ namespace Venom { public signal void leave_view(); - private ILogger logger; + private Logger logger; private Contact contact; private FriendInfoWidgetListener listener; - public FriendInfoViewModel(ILogger logger, FriendInfoWidgetListener listener, Contact contact) { + public FriendInfoViewModel(Logger logger, FriendInfoWidgetListener listener, Contact contact) { logger.d("FriendInfoViewModel created."); this.logger = logger; this.contact = contact; @@ -95,6 +95,7 @@ namespace Venom { contact.auto_location = ""; } contact._show_notifications = show_notifications; + listener.on_apply_friend_settings(contact); contact.changed(); } @@ -119,5 +120,6 @@ namespace Venom { public interface FriendInfoWidgetListener : GLib.Object { public abstract void on_remove_friend(IContact contact) throws Error; + public abstract void on_apply_friend_settings(IContact contact); } } diff --git a/src/viewmodel/MessageViewModel.vala b/src/viewmodel/MessageViewModel.vala index 5243531..c11624b 100644 --- a/src/viewmodel/MessageViewModel.vala +++ b/src/viewmodel/MessageViewModel.vala @@ -21,36 +21,78 @@ namespace Venom { public class MessageViewModel : GLib.Object { - public string sender { get; set; } - public bool sender_sensitive { get; set; } - public Gdk.Pixbuf sender_image { get; set; } - public string timestamp { get; set; } + public string sender { get; set; } + public bool sender_sensitive { get; set; } + public Gdk.Pixbuf sender_image { get; set; } + public string timestamp { get; set; } public string timestamp_tooltip { get; set; } - public string message { get; set; } - public bool sent_visible { get; set; } - public bool sent_dim { get; set; } - public string sent_tooltip { get; set; } - public string sender_color { get; set; default = ""; } - public bool sender_bold { get; set; default = true; } - - private ILogger logger; - private IMessage message_content; + public string message { get; set; } + public bool sent_visible { get; set; } + public bool sent_dim { get; set; } + public string sent_tooltip { get; set; } + public string sender_color { get; set; default = ""; } + public bool sender_bold { get; set; default = true; } + + private Logger logger; + private Message message_content; + private FormattedMessage formatted_message; private ISettingsDatabase settings; - private const string[] colors_light = {"#F44336", "#E91E63", "#9C27B0", "#673AB7", "#2196F3", "#03A9F4", "#00BCD4", "#009688", "#4CAF50", "#FFEB3B"}; - private const string[] colors_dark = {"#8BC34A", "#3F51B5", "#CDDC39", "#FFC107", "#FF9800", "#FF5722", "#795548", "#9E9E9E", "#607D8B"}; + private const string[] colors_light = { "#F44336", "#E91E63", "#9C27B0", "#673AB7", "#2196F3", "#03A9F4", "#00BCD4", "#009688", "#4CAF50", "#FFEB3B" }; + private const string[] colors_dark = { "#8BC34A", "#3F51B5", "#CDDC39", "#FFC107", "#FF9800", "#FF5722", "#795548", "#9E9E9E", "#607D8B" }; - public MessageViewModel(ILogger logger, IMessage message_content, ISettingsDatabase settings) { + public MessageViewModel(Logger logger, Message message_content, UserInfo user_info, ISettingsDatabase settings) { logger.d("MessageViewModel created."); this.logger = logger; this.message_content = message_content; this.settings = settings; + formatted_message = (FormattedMessage) message_content; + settings.notify["enable-dark-theme"].connect(on_theme_changed); update_sender_color(); - message_content.message_changed.connect(on_message_changed); - on_message_changed(); + message_content.notify["state"].connect(update_message_state); + update_message_state(); + + Gdk.Pixbuf? pixbuf = null; + sender_sensitive = message_content.sender == MessageSender.REMOTE; + switch (message_content.sender) { + case MessageSender.LOCAL: + sender = user_info.name; + pixbuf = user_info.avatar.pixbuf; + break; + case MessageSender.SYSTEM: + sender = _("Notice"); + break; + case MessageSender.REMOTE: + sender = formatted_message.get_sender_plain(); + pixbuf = formatted_message.get_sender_image(); + break; + default: + logger.e("MessageViewModel unknown message sender!"); + break; + } + + message = formatted_message.get_message_plain(); + if (pixbuf != null) { + sender_image = round_corners(pixbuf.scale_simple(20, 20, Gdk.InterpType.BILINEAR)); + } + timestamp = message_content.timestamp.format("%X"); + timestamp_tooltip = message_content.timestamp.format("%c"); + } + + private void update_message_state() { + if (message_content.sender == MessageSender.LOCAL) { + sent_dim = message_content.state != TransmissionState.RECEIVED; + if (message_content.state == TransmissionState.SENT) { + sent_visible = true; + sent_tooltip = _("Message sent ✓"); + } else if (message_content.state == TransmissionState.RECEIVED) { + sent_visible = true; + sent_tooltip = _("Message received ✓"); + } + } } private void on_theme_changed() { @@ -59,10 +101,10 @@ namespace Venom { } private void update_sender_color() { - if (message_content.message_direction == MessageDirection.OUTGOING) { + if (message_content.sender != MessageSender.REMOTE) { return; } - var hash = message_content.get_sender_id().hash(); + var hash = formatted_message.get_sender_id().hash(); if (settings.enable_dark_theme) { sender_color = colors_light[hash % colors_light.length]; } else { @@ -70,27 +112,6 @@ namespace Venom { } } - private void on_message_changed() { - var outoing = message_content.message_direction == MessageDirection.OUTGOING; - sender_sensitive = !outoing; - if (outoing) { - sent_visible = true; - sent_dim = !message_content.received; - sent_tooltip = !message_content.received ? _("Message sent ✓") : _("Message received ✓"); - sender = _("me"); - } else { - sender = message_content.get_sender_plain(); - } - - message = message_content.get_message_plain(); - var pixbuf = message_content.get_sender_image(); - if (pixbuf != null) { - sender_image = round_corners(pixbuf.scale_simple(20, 20, Gdk.InterpType.BILINEAR)); - } - timestamp = message_content.timestamp.format("%X"); - timestamp_tooltip = message_content.timestamp.format("%c"); - } - ~MessageViewModel() { logger.d("MessageViewModel destroyed."); } @@ -123,8 +144,8 @@ namespace Venom { } public class UriTransform { - private ILogger logger; - public UriTransform(ILogger logger) { + private Logger logger; + public UriTransform(Logger logger) { this.logger = logger; } diff --git a/src/viewmodel/UserInfoViewModel.vala b/src/viewmodel/UserInfoViewModel.vala index ae81855..f08d8cb 100644 --- a/src/viewmodel/UserInfoViewModel.vala +++ b/src/viewmodel/UserInfoViewModel.vala @@ -21,23 +21,29 @@ namespace Venom { public class UserInfoViewModel : GLib.Object { + public signal void reset_nospams(); + public string username { get; set; } public string statusmessage { get; set; } public Gdk.Pixbuf avatar { get; set; } public string tox_id { get; set; } public Gdk.Pixbuf tox_qr_code { get; set; } + public string tox_nospam { get; set; } - private ILogger logger; + private Logger logger; private UserInfo user_info; private UserInfoViewListener listener; private AvatarChange avatar_change; private GLib.ListStore avatars; + private NospamRepository nospam_repository; + private Rand random = new Rand(); - public UserInfoViewModel(ILogger logger, UserInfo user_info, UserInfoViewListener listener) { + public UserInfoViewModel(Logger logger, NospamRepository nospam_repository, UserInfo user_info, UserInfoViewListener listener) { logger.d("UserInfoViewModel created."); this.logger = logger; this.user_info = user_info; this.listener = listener; + this.nospam_repository = nospam_repository; avatars = new GLib.ListStore(typeof(GLib.File)); init_liststore.begin(); @@ -78,12 +84,56 @@ namespace Venom { } } + public void on_set_nospam_clicked() { + logger.d("UserInfoViewModel on_set_nospam_clicked."); + int new_nospam = nospam_str_to_int(tox_nospam); + int current_nospam = nospam_str_to_int(get_current_nospam()); + + var nospam = new Nospam(); + nospam.nospam = current_nospam; + nospam.timestamp = new DateTime.now_local(); + + try { + listener.set_self_nospam(new_nospam); + + nospam_repository.create(nospam); + reset_nospams(); + update_info(); + } catch (GLib.Error e) { + logger.e("UserInfoViewModel cannot set user info: " + e.message); + } + } + + public void on_remove_nospam_clicked(Nospam nospam) { + nospam_repository.delete(nospam); + reset_nospams(); + } + + public void on_random_nospam() { + tox_nospam = "%.8X".printf(random.next_int() & 0xffffffff); + } + + public void on_select_nospam(Nospam nospam) { + tox_nospam = "%.8X".printf(nospam.nospam); + } + + private int nospam_str_to_int(string nospam_str) { + int nospam_int = 0; + nospam_str.up().scanf("%X", &nospam_int); + return nospam_int & 0xffffffff; + } + + private string get_current_nospam() { + return user_info.tox_id.substring(ToxCore.PUBLIC_KEY_SIZE * 2, ToxCore.NOSPAM_SIZE * 2); + } + private void update_info() { logger.d("UserInfoViewModel update_info."); username = user_info.name; statusmessage = user_info.status_message; avatar = user_info.avatar.pixbuf; tox_id = user_info.tox_id; + tox_nospam = get_current_nospam(); avatar_change = AvatarChange.NONE; } @@ -145,6 +195,7 @@ namespace Venom { public abstract void set_self_name(string name) throws GLib.Error; public abstract void set_self_status_message(string status_message) throws GLib.Error; public abstract void set_self_avatar(Gdk.Pixbuf pixbuf) throws GLib.Error; + public abstract void set_self_nospam(int nospam) throws GLib.Error; public abstract void reset_self_avatar() throws GLib.Error; } }