Skip to content

Commit

Permalink
feat: server switching polish
Browse files Browse the repository at this point in the history
- remove --server / --tls flags from server as
  they are now passed from the browser client
- update binary release workflow to latest go
  version
- don't throw error if invalid flags are passed
  since --server / --tls were just removed we
  don't want users to upgrade and fail to start
- add to changelog
- better error message when TLS handshake fails
- reorganize some state stuff into better file
  locations
- add localstorage migration
- refine server menu ui
  • Loading branch information
evan-buss committed Oct 9, 2023
1 parent 3fee39e commit d90c584
Show file tree
Hide file tree
Showing 25 changed files with 280 additions and 191 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ jobs:

- uses: actions/setup-go@v2
with:
go-version: "^1.19.2"
go-version: "^1.21.2"
- run: go version

- name: Setup Node.js
Expand Down
1 change: 1 addition & 0 deletions .idea/codeStyles/Project.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions Taskfile.Development.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@ tasks:
client:
desc: Run OpenBooks React Client Application in Development Mode.
dir: server/app
env:
MOCK_SERVERS: true
cmds:
- npm run dev

Expand Down
18 changes: 14 additions & 4 deletions cmd/mock/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ package main
import (
"context"
"github.com/evan-buss/openbooks/mock"
"log"
"os"
"os/signal"
)
Expand All @@ -17,8 +16,16 @@ func main() {
//}

//Start mock book operator
operator := mock.NewOperator(&mock.Config{
Server: "localhost:6667",
highwayOp := mock.NewOperator(&mock.Config{
Server: "localhost:6667",
Name: "search",
Channel: "#ebooks",
})

undernetOp := mock.NewOperator(&mock.Config{
Server: "localhost:6667",
Name: "search2",
Channel: "#bookz",
})

go func() {
Expand All @@ -28,5 +35,8 @@ func main() {
cancel()
}()

log.Fatal(operator.StartListening(ctx))
go highwayOp.StartListening(ctx)
go undernetOp.StartListening(ctx)

<-ctx.Done()
}
5 changes: 3 additions & 2 deletions cmd/openbooks/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,6 @@ func init() {
desktopCmd.PersistentFlags().BoolVar(&debug, "debug", false, "Enable debug mode.")
desktopCmd.PersistentFlags().StringVarP(&globalFlags.UserName, "name", "n", "", "Username used to connect to IRC server.")
desktopCmd.MarkPersistentFlagRequired("name")
desktopCmd.PersistentFlags().StringVarP(&globalFlags.Server, "server", "s", "irc.irchighway.net:6697", "IRC server to connect to.")
desktopCmd.PersistentFlags().BoolVar(&globalFlags.EnableTLS, "tls", true, "Connect to server using TLS.")
desktopCmd.PersistentFlags().BoolVarP(&globalFlags.Log, "log", "l", false, "Save raw IRC logs for each client connection.")
desktopCmd.PersistentFlags().StringVar(&globalFlags.SearchBot, "searchbot", "search", "The IRC bot that handles search queries. Try 'searchook' if 'search' is down.")
desktopCmd.PersistentFlags().StringVarP(&globalFlags.UserAgent, "useragent", "u", fmt.Sprintf("OpenBooks %s", ircVersion), "UserAgent / Version Reported to IRC Server.")
Expand All @@ -58,6 +56,9 @@ var desktopCmd = &cobra.Command{
Use: "openbooks",
Short: "Quickly and easily download eBooks from IRCHighway.",
Long: "Runs OpenBooks in desktop mode. This allows you to run OpenBooks like a regular desktop application. This functionality utilizes your OS's native browser renderer and as such may not work on certain operating systems.",
FParseErrWhitelist: cobra.FParseErrWhitelist{
UnknownFlags: true, // Don't error on unknown flags. Users updating to new version may have old flags.
},
PreRun: func(cmd *cobra.Command, args []string) {
bindGlobalServerFlags(&desktopConfig)
rateLimit, _ := cmd.Flags().GetInt("rate-limit")
Expand Down
5 changes: 4 additions & 1 deletion cmd/openbooks/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ func init() {

serverCmd.Flags().StringVarP(&serverConfig.Port, "port", "p", "5228", "Set the local network port for browser mode.")
serverCmd.Flags().IntP("rate-limit", "r", 10, "The number of seconds to wait between searches to reduce strain on IRC search servers. Minimum is 10 seconds.")
serverCmd.Flags().BoolVar(&serverConfig.DisableBrowserDownloads, "no-browser-downloads", false, "The browser won't recieve and download eBook files, but they are still saved to the defined 'dir' path.")
serverCmd.Flags().BoolVar(&serverConfig.DisableBrowserDownloads, "no-browser-downloads", false, "The browser won't receive and download eBook files, but they are still saved to the defined 'dir' path.")
serverCmd.Flags().StringVar(&serverConfig.Basepath, "basepath", "/", `Base path where the application is accessible. For example "/openbooks/".`)
serverCmd.Flags().BoolVarP(&openBrowser, "browser", "b", false, "Open the browser on server start.")
serverCmd.Flags().BoolVar(&serverConfig.Persist, "persist", false, "Persist eBooks in 'dir'. Default is to delete after sending.")
Expand All @@ -30,6 +30,9 @@ var serverCmd = &cobra.Command{
Use: "server",
Short: "Run OpenBooks in server mode.",
Long: "Run OpenBooks in server mode. This allows you to use a web interface to search and download eBooks.",
FParseErrWhitelist: cobra.FParseErrWhitelist{
UnknownFlags: true, // Don't error on unknown flags. Users updating to new version may have old flags.
},
PreRun: func(cmd *cobra.Command, args []string) {
bindGlobalServerFlags(&serverConfig)
rateLimit, _ := cmd.Flags().GetInt("rate-limit")
Expand Down
2 changes: 0 additions & 2 deletions cmd/openbooks/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,7 @@ func bindGlobalServerFlags(config *server.Config) {
config.UserAgent = globalFlags.UserAgent
config.UserName = globalFlags.UserName
config.Log = globalFlags.Log
config.Server = globalFlags.Server
config.SearchBot = globalFlags.SearchBot
config.EnableTLS = globalFlags.EnableTLS
}

// Make sure the server config has a valid rate limit.
Expand Down
2 changes: 1 addition & 1 deletion core/irchighway.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import (
)

// Specific irc.irchighway.net commands
// Also works with Unanet as they use the same operator software
// Also works with Undernet as they use the same operator software

// Join connects to the irc.irchighway.net server and joins the #ebooks channel
func Join(irc *irc.Conn, address string, channel string, enableTLS bool) error {
Expand Down
14 changes: 14 additions & 0 deletions docs/docs/changelog.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,17 @@
# [v5.0.0] - 2023-10-08

## Added
- The browser application now supports multiple servers. Click the connection icon in the bottom left to select a server.
- Results Grid Changes:
- Added fuzzy search
- Can show and hide columns
- New filter UI

## Removed
- No longer supports CLI mode. This mode was a holdover from the original version of OpenBooks, which was a command line application.
- `--server` and `--tls` flags are removed. The browser application allows switching between servers and toggling TLS.
- OpenBooks can now connect to the Undernet #bookz channel, as this uses the same IRC operator software as IRC Highway.

# [v4.5.0] - 2023-01-08

## Added
Expand Down
6 changes: 6 additions & 0 deletions irc/irc.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,13 @@ package irc

import (
"crypto/tls"
"errors"
"log/slog"
"net"
)

var ErrTLSHandshake = errors.New("TLS handshake failed. The server may not support TLS")

// Conn represents an IRC connection to a server
type Conn struct {
net.Conn
Expand Down Expand Up @@ -42,6 +45,9 @@ func (i *Conn) Connect(address string, enableTLS bool) error {
}

if err != nil {
if err.Error() == "tls: first record does not look like a TLS handshake" {
return ErrTLSHandshake
}
return err
}

Expand Down
16 changes: 10 additions & 6 deletions mock/operator.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package mock

import (
"context"
"fmt"
"gopkg.in/irc.v4"
"log"
"math/rand"
Expand All @@ -13,8 +14,10 @@ import (

type Config struct {
Server string
Channel string
SearchResultPath string
BookResultPath string
Name string
}

// Operator listens to an IRC channel and responds to search / download requests
Expand All @@ -34,14 +37,14 @@ func NewOperator(config *Config) *Operator {
operator := &Operator{
config: config,
dccManager: NewDccManager(),
log: log.New(os.Stdout, "OPERATOR: ", 0),
log: log.New(os.Stdout, fmt.Sprintf("OPERATOR %s: ", config.Channel), 0),
}

client := irc.NewClient(conn, irc.ClientConfig{
Nick: "search",
Nick: config.Name,
Pass: "",
User: "search",
Name: "search",
User: config.Name,
Name: config.Name,
EnableISupport: true,
EnableTracker: true,
PingFrequency: time.Second * 30,
Expand Down Expand Up @@ -90,10 +93,11 @@ func (o *Operator) Handler(client *irc.Client, message *irc.Message) {
}

func (o *Operator) StartListening(ctx context.Context) error {
fmt.Println("Starting operator")
go func() {
time.Sleep(time.Second * 2)
o.client.Write("JOIN #ebooks")
o.log.Println("Joined #ebooks channel")
o.client.Write("JOIN " + o.config.Channel)
o.log.Printf("Joined %s channel\n", o.config.Channel)
}()

o.log.Printf("Connected to %s\n", o.config.Server)
Expand Down
2 changes: 1 addition & 1 deletion server/app/.env.development
Original file line number Diff line number Diff line change
@@ -1 +1 @@
VITE_MOCK_IRC_SERVERS=true
VITE_MOCK_IRC_SERVERS=${MOCK_SERVERS:-true}
1 change: 1 addition & 0 deletions server/app/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import "@mantine/core/styles/ActionIcon.css";
import "@mantine/core/styles/AppShell.css";
import "@mantine/core/styles/Badge.css";
import "@mantine/core/styles/Center.css";
import "@mantine/core/styles/Code.css";
import "@mantine/core/styles/Divider.css";
import "@mantine/core/styles/Image.css";
import "@mantine/core/styles/Indicator.css";
Expand Down
31 changes: 1 addition & 30 deletions server/app/src/components/sidebar/ServerMenu.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -37,33 +37,4 @@
.switch {
display: none;
}
}


/*
//.root {
// background-color: light-dark(var(--mantine-color-white), var(--mantine-color-dark-6));
// border: 1px solid light-dark(var(--mantine-color-gray-3), var(--mantine-color-gray-8));
//
// &[data-active] {
// border-color: var(--mantine-primary-color-filled);
// box-shadow: var(--mantine-shadow-sm);
// }
//
// @mixin hover {
// background-color: light-dark(var(--mantine-color-gray-1), var(--mantine-color-dark-5));
// }
//}
//
//.inner {
// color: light-dark(black, white);
// font-weight: normal;
// justify-content: space-between;
//}
//
//.label {
// padding-left: var(--mantine-spacing-sm);
// width: 100%;
// text-align: start;
// font-size: var(--mantine-font-size-sm);
//}*/
}
61 changes: 43 additions & 18 deletions server/app/src/components/sidebar/ServerMenu.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import {
ActionIcon,
Button,
Code,
Group,
Modal,
px,
Expand All @@ -9,18 +10,24 @@ import {
Text,
Tooltip
} from "@mantine/core";
import { CheckCircle, Plugs, PlugsConnected } from "@phosphor-icons/react";
import {
CheckCircle,
Plugs,
PlugsConnected,
ShieldCheck,
ShieldSlash
} from "@phosphor-icons/react";
import { useId, useState } from "react";
import classes from "./ServerMenu.module.css";
import { useDisclosure } from "@mantine/hooks";
import { useAppDispatch, useAppSelector } from "../../state/store";
import {
connectToServer,
IrcServer,
SelectedServer,
selectServer
setIrcServer
} from "../../state/connectionSlice";
import isEqual from "lodash/isEqual";
import { setActiveItem } from "../../state/stateSlice";
import { connectToServer } from "../../state/socketMiddleware";

export function ServerMenu({ connected }: { connected: boolean }) {
const [opened, { open, close }] = useDisclosure(false);
Expand All @@ -37,7 +44,8 @@ export function ServerMenu({ connected }: { connected: boolean }) {
};

const handleConnect = () => {
dispatch(selectServer(selected));
dispatch(setActiveItem(null));
dispatch(setIrcServer(selected));
dispatch(connectToServer());
close();
};
Expand All @@ -57,14 +65,12 @@ export function ServerMenu({ connected }: { connected: boolean }) {
key={server.name}
server={server}
isSelected={selected.name === server.name}
isTLSEnabled={selectedServer.enableTLS}
onSelect={(ssl) => handleSelect(server, ssl)}
/>
))}
<Button
style={{ alignSelf: "end" }}
disabled={isEqual(selectedServer, selected)}
onClick={handleConnect}>
Connect
<Button style={{ alignSelf: "end" }} onClick={handleConnect}>
{selectedServer.name === selected.name ? "Reconnect" : "Connect"}
</Button>
</Stack>
</Modal>
Expand All @@ -75,12 +81,18 @@ export function ServerMenu({ connected }: { connected: boolean }) {
interface ServerOptionProps {
server: IrcServer;
isSelected: boolean;
isTLSEnabled: boolean;
onSelect: (ssl: boolean) => void;
}

function ServerOption({ server, isSelected, onSelect }: ServerOptionProps) {
function ServerOption({
server,
isSelected,
isTLSEnabled,
onSelect
}: ServerOptionProps) {
const id = useId();
const [enableTLS, setEnableTLS] = useState(true);
const [enableTLS, setEnableTLS] = useState(isTLSEnabled);
const serverString = `${server.address}:${
enableTLS ? server.sslPort : server.port
}`;
Expand Down Expand Up @@ -110,24 +122,37 @@ function ServerOption({ server, isSelected, onSelect }: ServerOptionProps) {
<Group gap={12} align="center">
<CheckCircle size={20} weight="bold" className={classes.icon} />
<Stack gap={0}>
<Text>{server.name}</Text>
<Text size="md" fw={500}>
{server.name} <Code>#{server.channel}</Code>
</Text>
<Tooltip position="right" label={serverString}>
<Text c="gray.5">{server.address}</Text>
</Tooltip>
</Stack>
</Group>

<div>
<Tooltip
label={enableTLS ? "TLS Enabled" : "TLS Disabled"}
refProp="rootRef">
<Switch
aria-label="Enable TLS?"
className={classes.switch}
defaultChecked
size="xs"
w={100}
labelPosition="right"
size="lg"
onLabel="ON"
offLabel="OFF"
thumbIcon={
enableTLS ? (
<ShieldCheck weight="bold" color="black" size={14} />
) : (
<ShieldSlash weight="bold" color="black" size={14} />
)
}
checked={enableTLS}
onChange={(e) => handleTLSChange(e.currentTarget.checked)}
label="TLS?"
/>
</div>
</Tooltip>
</label>
</div>
);
Expand Down
Loading

0 comments on commit d90c584

Please sign in to comment.