Refactor provider module. Support starting without mode.

This commit is contained in:
2020-05-23 12:42:57 +02:00
parent 6c81d89d41
commit 2907cf6574
2 changed files with 77 additions and 64 deletions

View File

@@ -1,4 +1,4 @@
import os, sets, random, httpClient, json, strformat import os, sets, random, httpClient, json, strformat, options
import gintro/[gdkpixbuf, gobject] import gintro/[gdkpixbuf, gobject]
import commands import commands
@@ -12,20 +12,19 @@ type
FileOpResult* = object of OpResult FileOpResult* = object of OpResult
file*: string file*: string
ProviderKind* {.pure.} = enum Mode* {.pure.} = enum ## Options for the display mode
None = "none" ## No images will be displayed
Foxes = "foxes" ## Some nice foxes Foxes = "foxes" ## Some nice foxes
Inspiro = "inspiro" ## Inspiring nonsense Inspiro = "inspiro" ## Inspiring nonsense
File = "file" ## Images from a local path File = "file" ## Images from a local path
ImageProvider* = ref object of RootObj ImageProvider* = ref object of RootObj
verbose: bool ## Manages images that should be displayed
case kind: ProviderKind verbose: bool ## Additional logging for the image provider
of ProviderKind.Foxes, ProviderKind.Inspiro: mode* : Mode ## Selects the API that is used to get images
url: string path*: Option[string] ## Path on the local file syetem that will be used in `file` mode
of ProviderKind.File: exts: HashSet[string] ## Allowed extensions that the `file` mode will display
exts: HashSet[string] files: seq[string] ## Currently loaded list of images in `file` mode
path*: string
files*: seq[string]
var var
client = newHttpClient() ## For loading images from the web client = newHttpClient() ## For loading images from the web
@@ -34,28 +33,21 @@ var
# Constructors # Constructors
######################## ########################
proc newFileProvider(path: string): ImageProvider = proc newImageProvider(verbose: bool, mode: Mode, path: Option[string]): ImageProvider =
## Create an image provider to access images from the local file system
randomize() randomize()
result = ImageProvider(kind: ProviderKind.File, path: path, exts: supportedExts.toHashSet) ImageProvider(verbose: verbose, mode: mode, path: path, exts: supportedExts.toHashSet)
proc newInspiroProvider(): ImageProvider = proc newImageProvider*(verbose: bool): ImageProvider =
## Create an image provider for the inspiro bot API newImageProvider(verbose, Mode.None, none(string))
ImageProvider(kind: ProviderKind.Inspiro, url: inspiroUrl)
proc newFoxProvider(): ImageProvider = proc newImageProvider*(verbose: bool, path: string): ImageProvider =
## Create an image provider to access the API at "https://randomfox.ca/floof/". newImageProvider(verbose, Mode.None, some(path))
ImageProvider(kind: ProviderKind.Foxes, url: foxesUrl)
proc newImageProvider*(kind: ProviderKind, filePath: string = ""): ImageProvider = proc newImageProvider*(verbose: bool, mode: Mode): ImageProvider =
## Create a new `ImageProvider` for the API. newImageProvider(verbose, mode, none(string))
case kind
of ProviderKind.Foxes: proc newImageProvider*(verbose: bool, mode: Mode, path: string): ImageProvider =
newFoxProvider() newImageProvider(verbose, mode, some(path))
of ProviderKind.Inspiro:
newInspiroProvider()
of ProviderKind.File:
newFileProvider(filePath)
proc newFileOpResultError(msg: string): FileOpResult = proc newFileOpResultError(msg: string): FileOpResult =
FileOpResult(success: false, errorMsg: msg) FileOpResult(success: false, errorMsg: msg)
@@ -77,7 +69,7 @@ proc log(ip: ImageProvider, msg: string) =
proc getFox(ip: ImageProvider): FileOpResult = proc getFox(ip: ImageProvider): FileOpResult =
## Download image from the fox API ## Download image from the fox API
try: try:
let urlData = client.getContent(ip.url) let urlData = client.getContent(foxesUrl)
let info = parseJson(urlData) let info = parseJson(urlData)
let imageData = client.getContent(info["image"].getStr) let imageData = client.getContent(info["image"].getStr)
let dlFile = fmt"{tmpFile}.download" let dlFile = fmt"{tmpFile}.download"
@@ -93,7 +85,7 @@ proc getFox(ip: ImageProvider): FileOpResult =
proc getInspiro(ip: ImageProvider): FileOpResult = proc getInspiro(ip: ImageProvider): FileOpResult =
## Download and save image from the inspiro API ## Download and save image from the inspiro API
try: try:
let imageUrl = client.getContent(ip.url) let imageUrl = client.getContent(inspiroUrl)
ip.log fmt"Downloading inspiro image from: '{imageUrl}'" ip.log fmt"Downloading inspiro image from: '{imageUrl}'"
let imageData = client.getContent(imageUrl) let imageData = client.getContent(imageUrl)
let dlFile = fmt"{tmpFile}.download" let dlFile = fmt"{tmpFile}.download"
@@ -105,32 +97,35 @@ proc getInspiro(ip: ImageProvider): FileOpResult =
proc getLocalFile(ip: var ImageProvider): FileOpResult = proc getLocalFile(ip: var ImageProvider): FileOpResult =
## Provide an image from a local folder ## Provide an image from a local folder
# First, check if there are still images left to be loaded. # First, check if there are still images left to be loaded.
# If not reread all files from the path # If not reread all files from the path
if ip.files.len < 1: if ip.files.len < 1:
if ip.path == "": if ip.path.isNone:
return newFileOpResultError("No path for image loading") return newFileOpResultError("No path for image loading")
ip.log "Reloading file list..." ip.log "Reloading file list..."
for file in walkDirRec(ip.path): for file in walkDirRec(ip.path.get):
let split = splitFile(file) let split = splitFile(file)
if ip.exts.contains(split.ext): if ip.exts.contains(split.ext):
ip.files.add(file) ip.files.add(file)
ip.log fmt"Loaded {ip.files.len} files" ip.log fmt"Loaded {ip.files.len} files"
shuffle(ip.files) shuffle(ip.files)
# Remove the current file after
# Remove the current file after
result = newFileOpResult(ip.files[0]) result = newFileOpResult(ip.files[0])
ip.files.delete(0) ip.files.delete(0)
proc getFileName(ip: var ImageProvider): FileOpResult = proc getFileName(ip: var ImageProvider): FileOpResult =
## Get the temporary file name of the next file to display ## Get the temporary file name of the next file to display
case ip.kind case ip.mode
of ProviderKind.File: of Mode.File:
return ip.getLocalFile() return ip.getLocalFile()
of ProviderKind.Foxes: of Mode.Foxes:
return ip.getFox() return ip.getFox()
of ProviderKind.Inspiro: of Mode.Inspiro:
return ip.getInspiro() return ip.getInspiro()
return newFileOpResultError("Not implemented") else:
return newFileOpResultError("Not implemented")
######################## ########################
# Exported procs # Exported procs
@@ -139,6 +134,9 @@ proc getFileName(ip: var ImageProvider): FileOpResult =
proc next*(ip: var ImageProvider, width, height: int): FileOpResult = proc next*(ip: var ImageProvider, width, height: int): FileOpResult =
## Uses the image provider to get a new image ready to display. ## Uses the image provider to get a new image ready to display.
## `width` and `height` should be the size of the window. ## `width` and `height` should be the size of the window.
if ip.mode == Mode.None:
return newFileOpResultError("No mode active")
let op = ip.getFileName() let op = ip.getFileName()
if not op.success: return op if not op.success: return op
@@ -147,7 +145,7 @@ proc next*(ip: var ImageProvider, width, height: int): FileOpResult =
var w, h: int var w, h: int
if (width > height): if (width > height):
h = height h = height
w = ((rawPixbuf.width * h) / rawPixbuf.height).toInt w = ((rawPixbuf.width * h) / rawPixbuf.height).toInt
else: else:
w = width w = width
h = ((rawPixbuf.height * w) / rawPixbuf.width).toInt h = ((rawPixbuf.height * w) / rawPixbuf.width).toInt

View File

@@ -24,7 +24,7 @@ var
# Server vor recieving commands from external tools # Server vor recieving commands from external tools
serverWorker: system.Thread[ServerArgs] serverWorker: system.Thread[ServerArgs]
proc enumToStrings(en: typedesc): seq[string] = proc enumToStrings(en: typedesc): seq[string] =
for x in en: for x in en:
result.add $x result.add $x
@@ -44,7 +44,7 @@ proc notify(label: Label, message: string = "") =
proc newArgs(): Option[Args] = proc newArgs(): Option[Args] =
let p = newParser("randopix"): let p = newParser("randopix"):
help(fmt"Version {version} - Display random images from different sources") help(fmt"Version {version} - Display random images from different sources")
option("-m", "--mode", help="The image source mode.", choices=enumToStrings(ProviderKind)) option("-m", "--mode", help="The image source mode.", choices=enumToStrings(Mode))
option("-p", "--path", help="Path to a directory with images for the 'file' mode") option("-p", "--path", help="Path to a directory with images for the 'file' mode")
option("-t", "--timeout", help="Seconds before the image is refreshed", default="300") option("-t", "--timeout", help="Seconds before the image is refreshed", default="300")
flag("-w", "--windowed", help="Do not start in fullscreen mode") flag("-w", "--windowed", help="Do not start in fullscreen mode")
@@ -52,12 +52,24 @@ proc newArgs(): Option[Args] =
try: try:
let opts = p.parse(commandLineParams()) let opts = p.parse(commandLineParams())
let mode = some(parseEnum[ProviderKind](opts.mode))
imageProvider = newImageProvider(mode.get, opts.path) # Parse the starting mode
var startMode: Mode
try:
startMode= parseEnum[Mode](opts.mode)
except ValueError:
startMode = Mode.None
# Create the image provider
if opts.path != "":
imageProvider = newImageProvider(opts.verbose, startMode, opts.path)
else:
imageProvider = newImageProvider(opts.verbose, startMode)
## Timeout is given in seconds as an argument ## Timeout is given in seconds as an argument
var timeout = 3000 var timeout = 3000
try: try:
timeout = opts.timeout.parseInt * 1000 timeout = opts.timeout.parseInt * 1000
except ValueError: except ValueError:
raise newException(UsageError, fmt"Invalid timeout value: {opts.timeout}") raise newException(UsageError, fmt"Invalid timeout value: {opts.timeout}")
@@ -66,44 +78,48 @@ proc newArgs(): Option[Args] =
verbose: opts.verbose, verbose: opts.verbose,
timeout: timeout)) timeout: timeout))
except: except:
echo getCurrentExceptionMsg()
echo p.help echo p.help
proc updateImage(image: Image): bool = proc updateImage(image: Image): bool =
## Updates the UI with a new image ## Updates the UI with a new image
try: try:
if (args.verbose): log "Refreshing..." if args.verbose: log "Refreshing..."
if imageProvider.mode == Mode.None:
log "No display mode"
label.notify "No mode selected"
return true
var wWidth, wHeight: int var wWidth, wHeight: int
window.getSize(wWidth, wHeight) window.getSize(wWidth, wHeight)
let op = imageProvider.next(wWidth, wHeight) let op = imageProvider.next(wWidth, wHeight)
result = op.success result = op.success
if op.success: if not op.success:
image.setFromFile(op.file)
else:
label.notify op.errorMsg label.notify op.errorMsg
return
image.setFromFile(op.file)
label.notify
except: except:
let let
e = getCurrentException() e = getCurrentException()
msg = getCurrentExceptionMsg() msg = getCurrentExceptionMsg()
log "Got exception ", repr(e), " with message ", msg log "Got exception ", repr(e), " with message ", msg
label.notify "Error while refreshing image, retrying..."
return false return false
proc timedUpdate(image: Image): bool = proc timedUpdate(image: Image): bool =
let ok = updateImage(image); discard updateImage(image);
if not ok:
label.notify "Error while refreshing image, retrying..."
else:
label.notify
updateTimeout = int(timeoutAdd(uint32(args.timeout), timedUpdate, image)) updateTimeout = int(timeoutAdd(uint32(args.timeout), timedUpdate, image))
return false return false
proc forceUpdate(action: SimpleAction; parameter: Variant; image: Image): void = proc forceUpdate(action: SimpleAction; parameter: Variant; image: Image): void =
log "Force refreshing image now" log "Refreshing image..."
label.notify "Refreshing image..."
if updateTimeout > 0: if updateTimeout > 0:
discard updateTimeout.remove discard updateTimeout.remove
discard image.timedUpdate() updateTimeout = int(timeoutAdd(500, timedUpdate, image))
proc checkServerChannel(image: Image): bool = proc checkServerChannel(image: Image): bool =
## Check the channel from the control server for incomming commands ## Check the channel from the control server for incomming commands
@@ -127,7 +143,6 @@ proc checkServerChannel(image: Image): bool =
sleep(100) sleep(100)
result = true result = true
# discard idleAdd(checkServerChannel, parameter)
proc toggleFullscreen(action: SimpleAction; parameter: Variant; window: ApplicationWindow) = proc toggleFullscreen(action: SimpleAction; parameter: Variant; window: ApplicationWindow) =
## Fullscreen toggle event ## Fullscreen toggle event
@@ -140,7 +155,7 @@ proc toggleFullscreen(action: SimpleAction; parameter: Variant; window: Applicat
proc cleanUp(w: ApplicationWindow, app: Application) = proc cleanUp(w: ApplicationWindow, app: Application) =
## Stop the control server and exit the GTK application ## Stop the control server and exit the GTK application
log "Stopping control server..." log "Stopping control server..."
closeServer() closeServer()
serverWorker.joinThread() serverWorker.joinThread()
chan.close() chan.close()
log "Server stopped." log "Server stopped."
@@ -149,7 +164,7 @@ proc cleanUp(w: ApplicationWindow, app: Application) =
proc quit(action: SimpleAction; parameter: Variant; app: Application) = proc quit(action: SimpleAction; parameter: Variant; app: Application) =
## Application quit event ## Application quit event
cleanUp(window, app) cleanUp(window, app)
proc appActivate(app: Application) = proc appActivate(app: Application) =
# Parse arguments from the command line # Parse arguments from the command line
let parsed = newArgs() let parsed = newArgs()
@@ -163,7 +178,7 @@ proc appActivate(app: Application) =
window.setKeepAbove(false) window.setKeepAbove(false)
window.setDefaultSize(800, 600) window.setDefaultSize(800, 600)
# Custom styling for e.g. the background color CSS data is in the "style.nim" module # Custom styling for e.g. the background color CSS data is in the "style.nim" module
let provider = newCssProvider() let provider = newCssProvider()
discard provider.loadFromData(css) discard provider.loadFromData(css)
addProviderForScreen(getDefaultScreen(), provider, STYLE_PROVIDER_PRIORITY_USER) addProviderForScreen(getDefaultScreen(), provider, STYLE_PROVIDER_PRIORITY_USER)
@@ -176,14 +191,14 @@ proc appActivate(app: Application) =
let container = newOverlay() let container = newOverlay()
container.addOverlay(label) container.addOverlay(label)
window.add(container) window.add(container)
let image = newImage() let image = newImage()
container.add(image) container.add(image)
if args.fullscreen: if args.fullscreen:
window.fullscreen window.fullscreen
## Connect the GTK signals to the procs ## Connect the GTK signals to the procs
let fullscreenAction = newSimpleAction("fullscreen") let fullscreenAction = newSimpleAction("fullscreen")
discard fullscreenAction.connect("activate", toggleFullscreen, window) discard fullscreenAction.connect("activate", toggleFullscreen, window)
app.setAccelsForAction("win.fullscreen", "F") app.setAccelsForAction("win.fullscreen", "F")