morpheusv2

A Matrix client written in Go-QT
git clone git://archive.git.mtrnord.blog/Nordgedanken/morpheusv2.git
Log | Files | Refs | README | LICENSE

commit 113c78c16d68cbbb219ba0f8cd9d05ad667217c6
parent 117632e38fbde50322fd3339c93c7f121f3a2cf9
Author: Marcel <MTRNord@users.noreply.github.com>
Date:   Wed,  2 May 2018 19:47:46 +0200

Implement basic state managment for rooms, messages, users (not state as in matrix proto state) (#4)

* Make sure we used the installed go files instead of the ones from vendor

* Fix copying

* Fix linter and QT Source Copy

* Debug whats happening inside the qtSrc Copy

* Remove QT Src before copy

* Remove ls call that broke the CI

* Make linter happy

* Keep QT outside of Dep

* Ignore qt in dep

* Fix ignored

* Readd lock

[ci skip]

* Fix deb building

* Fix early exit

* Run QT Stuff in main Thread

* Try to prevent deadlock without a noop

* Cleanup and log

* Use app in more places

* Handle Exit

* Run signal.Notify in main thread

* Please the linter

* Prepare setting windows in a more clean way

* Actually start The QApp

* Open MainUI on start (basicly empty yet)

* Load qml and move it to the correct directory

* Please the linter

* Open Window before entering main event loop and run both still on the main thread

* Check if windows binary got build wrong

* Fix deb menu icon

* Fix Win build on CI

* Debug output for windows

* Check if LockOSThread breaks QT

* Remove LockOSThread

* Add comments for exported functions

* Fix comments

* Prepare needed types using interfaces and init the DB

* Generate needed Directories

* Log also to file

* Fix DB comments

* Document interfaces in dataTypes

* Document the Init

* Fix deb once again

Diffstat:
M.circleci/config.yml | 6++++++
MGopkg.lock | 6++++++
Mcmd/root.go | 54++++++++++++++++++++++++++++++++++++++++++++++--------
Mdeb.json | 2+-
Mmain.go | 22++++++++--------------
Mpkg/app/main.go | 60++++++++++++++++++++++++++++++++++++++++--------------------
Apkg/app/ui.go | 12++++++++++++
Apkg/db/db.go | 14++++++++++++++
Apkg/db/implementation/db.go | 51+++++++++++++++++++++++++++++++++++++++++++++++++++
Apkg/mainUI/mainUI.go | 63+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Apkg/matrix/dataTypes.go | 38++++++++++++++++++++++++++++++++++++++
11 files changed, 285 insertions(+), 43 deletions(-)

diff --git a/.circleci/config.yml b/.circleci/config.yml @@ -21,12 +21,18 @@ jobs: name: Get Go Dependencies working_directory: /home/user/work/src/github.com/Nordgedanken/Morpheusv2/ command: dep ensure + - run: + name: Setup Code Climate test-reporter + command: | + curl -L https://codeclimate.com/downloads/test-reporter/test-reporter-latest-linux-amd64 > ./cc-test-reporter + chmod +x ./cc-test-reporter - persist_to_workspace: # Must be an absolute path, or relative path from working_directory root: /home/user/work/ # Must be relative path from root paths: - src/ + - ./cc-test-reporter build_linux: docker: - image: therecipe/qt:linux diff --git a/Gopkg.lock b/Gopkg.lock @@ -9,6 +9,12 @@ [[projects]] branch = "master" + name = "github.com/matrix-org/gomatrix" + packages = ["."] + revision = "a7fc80c8060c2544fe5d4dae465b584f8e9b4e27" + +[[projects]] + branch = "master" name = "github.com/shibukawa/configdir" packages = ["."] revision = "e180dbdc8da04c4fa04272e875ce64949f38bd3e" diff --git a/cmd/root.go b/cmd/root.go @@ -19,19 +19,57 @@ import ( "os" "github.com/Nordgedanken/Morpheusv2/pkg/app" + dbImpl "github.com/Nordgedanken/Morpheusv2/pkg/db/implementation" + "github.com/shibukawa/configdir" "github.com/spf13/cobra" + "io" + "log" + "path/filepath" ) // rootCmd represents the base command when called without any subcommands var rootCmd = &cobra.Command{ - Use: "Morpheusv2", - Short: "A brief description of your application", - Long: `A longer description that spans multiple lines and likely contains -examples and usage of using your application. For example: - -Cobra is a CLI library for Go that empowers applications. -This application is a tool to generate the needed files -to quickly create a Cobra application.`, + Use: "Morpheusv2", + PreRunE: func(cmd *cobra.Command, args []string) error { + // Init Logs and folders + // configdir.New gets the Config Paths that are available on this system + configDirs := configdir.New("Nordgedanken", "Morpheusv2") + // path holds the global configdir of the system with the log folder appended + path := filepath.ToSlash(configDirs.QueryFolders(configdir.Global)[0].Path + "/log/") + logFilePath := filepath.ToSlash(path + "main.log") + + // This checks if the directory is missing and if that's the case to generate the directory + if _, StatErr := os.Stat(path); os.IsNotExist(StatErr) { + MkdirErr := os.MkdirAll(path, os.ModeDir) + if MkdirErr != nil { + return MkdirErr + } + } + + // os.OpenFile opens the file where the log is supposed to be written to + logFile, err := os.OpenFile(logFilePath, os.O_CREATE|os.O_APPEND|os.O_RDWR, 0666) + if err != nil { + return err + } + + // MultiWriter makes it possible to print log to both log File and console + mw := io.MultiWriter(os.Stdout, logFile) + + // SetOutput tells the log where to write to + log.SetOutput(mw) + + // dbImpl.Init() generates the needed tables if needed before the app starts + err = dbImpl.Init() + if err != nil { + return err + } + + log.Println("DB Set Up") + return nil + }, + // TODO add descriptions + Short: "", + Long: ``, // Uncomment the following line if your bare application // has an action associated with it: RunE: func(cmd *cobra.Command, args []string) error { diff --git a/deb.json b/deb.json @@ -50,7 +50,7 @@ "description":"A Matrix client written in Go-QT ", "generic-name":"Matrix Client", "exec":"/usr/bin/Morpheusv2.sh", - "icon":"pkg/qml/resources/logos/MorpheusBig.png", + "icon":"qml/resources/logos/MorpheusBig.png", "type":"Application", "keywords":"matrix;Morpheusv2", "startup-notify": false, diff --git a/main.go b/main.go @@ -16,24 +16,18 @@ package main import ( "github.com/Nordgedanken/Morpheusv2/cmd" - "github.com/Nordgedanken/Morpheusv2/pkg" - "runtime" + "os" + "os/signal" + "syscall" ) -// Arrange that main.main runs on main thread. -func init() { - runtime.LockOSThread() -} +var c = make(chan os.Signal, 2) func main() { - go pkg.Do(cmd.Execute) + signal.Notify(c, os.Interrupt, syscall.SIGTERM) go func() { - for { - pkg.Do(noop) - } + <-c + os.Exit(1) }() - - pkg.Main() + cmd.Execute() } - -func noop() {} diff --git a/pkg/app/main.go b/pkg/app/main.go @@ -15,28 +15,37 @@ package app import ( - "github.com/shibukawa/configdir" + "github.com/Nordgedanken/Morpheusv2/pkg/mainUI" + "github.com/matrix-org/gomatrix" "github.com/therecipe/qt/core" "github.com/therecipe/qt/gui" "github.com/therecipe/qt/widgets" "log" - "os" - "path/filepath" ) +var args []string +var cli *gomatrix.Client +var windowHeight = 600 +var windowWidth = 950 +var window *widgets.QMainWindow + // Start prepares the Main QT Window and opens it -func Start(args []string) error { +func Start(argsArg []string) error { + args = argsArg log.Println("Starting Morpheus v2") - // Init Logs and folders - configDirs := configdir.New("Nordgedanken", "Morpheus") - if _, StatErr := os.Stat(filepath.ToSlash(configDirs.QueryFolders(configdir.Global)[0].Path) + "/log/"); os.IsNotExist(StatErr) { - MkdirErr := os.MkdirAll(filepath.ToSlash(configDirs.QueryFolders(configdir.Global)[0].Path)+"/log/", 0700) - if MkdirErr != nil { - return MkdirErr - } - } + initApp() + + mainUIS := mainUI.NewMainUI(windowWidth, windowHeight, window) + SetNewWindow(mainUIS, window) + + widgets.QApplication_Exec() + return nil +} + +func initApp() { + log.Println("Create QApp") app := widgets.NewQApplication(len(args), args) app.SetAttribute(core.Qt__AA_UseHighDpiPixmaps, true) @@ -44,22 +53,33 @@ func Start(args []string) error { app.SetApplicationVersion("0.1.0") appIcon := gui.NewQIcon5(":/qml/resources/logos/MorpheusBig.png") app.SetWindowIcon(appIcon) + window = widgets.NewQMainWindow(nil, 0) + app.SetActiveWindow(window) - window := widgets.NewQMainWindow(nil, 0) - - windowHeight := 600 - windowWidth := 950 - - desktopApp := widgets.QApplication_Desktop() + desktopApp := app.Desktop() primaryScreen := desktopApp.PrimaryScreen() screen := desktopApp.Screen(primaryScreen) windowX := (screen.Width() - windowHeight) / 2 windowY := (screen.Height() - windowWidth) / 2 window.Resize2(windowWidth, windowHeight) - window.Show() - window.Move2(windowX, windowY) + app.ConnectQuit(func() { + log.Println("Morpheus closed") + }) + +} + +// SetNewWindow loads the new UI into the QMainWindow +func SetNewWindow(ui ui, window *widgets.QMainWindow) error { + ui.SetCli(cli) + uiErr := ui.NewUI() + if uiErr != nil { + return uiErr + } + ui.GetWidget().Resize2(windowWidth, windowHeight) + window.SetCentralWidget(ui.GetWidget()) + window.Show() return nil } diff --git a/pkg/app/ui.go b/pkg/app/ui.go @@ -0,0 +1,12 @@ +package app + +import ( + "github.com/matrix-org/gomatrix" + "github.com/therecipe/qt/widgets" +) + +type ui interface { + SetCli(cli *gomatrix.Client) + GetWidget() (widget *widgets.QWidget) + NewUI() error +} diff --git a/pkg/db/db.go b/pkg/db/db.go @@ -0,0 +1,14 @@ +package db + +import "github.com/Nordgedanken/Morpheusv2/pkg/matrix" + +// DB defines a Interface to allow multiple DB Implementations +type DB interface { + SaveRoom(Room matrix.Room) error + GetRooms() (rooms map[string]matrix.Room, err error) + GetRoom(roomID string) (room matrix.Room, err error) + + SaveUser(user matrix.User) error + GetUsers() (map[string]matrix.User, error) + GetCurrentUser() (matrix.User, error) +} diff --git a/pkg/db/implementation/db.go b/pkg/db/implementation/db.go @@ -0,0 +1,51 @@ +package implementation + +import ( + "database/sql" + "fmt" + _ "github.com/mattn/go-sqlite3" // Go-sqlite3 side effect import needed to use SQLite3 databases + "github.com/shibukawa/configdir" + "log" + "path/filepath" +) + +var db *sql.DB + +// Init prepares the DB by opening it and creating the required tables if needed +// TODO: Make part of the interface +func Init() (err error) { + log.Println("Start setting up DB") + var openErr error + + // Open the data.db file. It will be created if it doesn't exist. + configDirs := configdir.New("Nordgedanken", "Morpheusv2") + filePath := filepath.ToSlash(configDirs.QueryFolders(configdir.Global)[0].Path) + + log.Println("DBFilePath: ", filePath+"/data.db") + db, openErr = sql.Open("sqlite3", filePath+"/data.db") + if openErr != nil { + err = openErr + return + } + + log.Println("Creating DB Tables if needed") + createTables := `CREATE TABLE IF NOT EXISTS users (id integer not null primary key, display_name text, avatar text); + CREATE TABLE IF NOT EXISTS messages (id integer not null primary key, author text, message text, timestamp text, pure_event text); + CREATE TABLE IF NOT EXISTS rooms (id integer not null primary key, room_aliases text, room_id text, room_name text, room_avatar text, room_topic text, room_messages text); + ` + _, execErr := db.Exec(createTables) + if execErr != nil { + err = fmt.Errorf("DB EXEC ERR: %s", execErr) + return + } + log.Println("Finished setting DB Setup") + return +} + +// Open returns the in Init() created db variable +func Open() *sql.DB { + if db != nil { + return db + } + return nil +} diff --git a/pkg/mainUI/mainUI.go b/pkg/mainUI/mainUI.go @@ -0,0 +1,63 @@ +package mainUI + +import ( + "github.com/matrix-org/gomatrix" + "github.com/therecipe/qt/core" + "github.com/therecipe/qt/gui" + "github.com/therecipe/qt/uitools" + "github.com/therecipe/qt/widgets" +) + +// MainUI defines the data for the main ui (that one with the chats) +type MainUI struct { + widget *widgets.QWidget + cli *gomatrix.Client + window *widgets.QMainWindow + windowWidth int + windowHeight int +} + +// NewMainUI gives you a MainUI struct with prefilled data +func NewMainUI(windowWidth, windowHeight int, window *widgets.QMainWindow) (mainUI *MainUI) { + mainUI = &MainUI{ + windowWidth: windowWidth, + windowHeight: windowHeight, + window: window, + } + return +} + +// SetCli sets the gomatrix Client for the MainUI +func (m *MainUI) SetCli(cli *gomatrix.Client) { + m.cli = cli +} + +// GetWidget returns the QWidget of the MainUI +func (m *MainUI) GetWidget() (widget *widgets.QWidget) { + return m.widget +} + +// NewUI prepares the new UI +func (m *MainUI) NewUI() error { + m.widget = widgets.NewQWidget(nil, 0) + + var loader = uitools.NewQUiLoader(nil) + var file = core.NewQFile2(":/qml/ui/chat.ui") + + file.Open(core.QIODevice__ReadOnly) + mainWidget := loader.Load(file, m.widget) + file.Close() + + var layout = widgets.NewQHBoxLayout() + m.window.SetLayout(layout) + layout.InsertWidget(0, mainWidget, 0, core.Qt__AlignTop|core.Qt__AlignLeft) + layout.SetSpacing(0) + layout.SetContentsMargins(0, 0, 0, 0) + + m.widget.ConnectResizeEvent(func(event *gui.QResizeEvent) { + mainWidget.Resize(event.Size()) + event.Accept() + }) + + return nil +} diff --git a/pkg/matrix/dataTypes.go b/pkg/matrix/dataTypes.go @@ -0,0 +1,38 @@ +package matrix + +import ( + "github.com/matrix-org/gomatrix" + "time" +) + +// User defines a Interface to allow multiple User type Implementations +type User interface { + SetCli(cli *gomatrix.Client) + SetMXID(id string) + GetDisplayName(roomID string) (string, error) + GetAvatar(roomID string) (string, error) +} + +// Room defines a Interface to allow multiple Room type Implementations +type Room interface { + // Handled using global "own User" + //SetCli(cli *gomatrix.Client) + SetRoomID(id string) + SetRoomAliases(aliases map[int64]string) + GetName() (string, error) + GetAvatar() (string, error) + GetTopic() (string, error) + GetMessages() (map[string]Message, error) +} + +// Message defines a Interface to allow multiple Message type Implementations +type Message interface { + // Handled using global "own User" + //SetCli(cli *gomatrix.Client) + SetEventID(id string) + SetEvent(event *gomatrix.Event) + SetAuthorMXID(mxid string) + SetMessage(message string) + SetTimestamp(ts *time.Time) + Show() error +}