Refactor provider module. Support starting without mode.
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import os, sets, random, httpClient, json, strformat
|
||||
import os, sets, random, httpClient, json, strformat, options
|
||||
import gintro/[gdkpixbuf, gobject]
|
||||
import commands
|
||||
|
||||
@@ -12,20 +12,19 @@ type
|
||||
FileOpResult* = object of OpResult
|
||||
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
|
||||
Inspiro = "inspiro" ## Inspiring nonsense
|
||||
File = "file" ## Images from a local path
|
||||
|
||||
ImageProvider* = ref object of RootObj
|
||||
verbose: bool
|
||||
case kind: ProviderKind
|
||||
of ProviderKind.Foxes, ProviderKind.Inspiro:
|
||||
url: string
|
||||
of ProviderKind.File:
|
||||
exts: HashSet[string]
|
||||
path*: string
|
||||
files*: seq[string]
|
||||
## Manages images that should be displayed
|
||||
verbose: bool ## Additional logging for the image provider
|
||||
mode* : Mode ## Selects the API that is used to get images
|
||||
path*: Option[string] ## Path on the local file syetem that will be used in `file` mode
|
||||
exts: HashSet[string] ## Allowed extensions that the `file` mode will display
|
||||
files: seq[string] ## Currently loaded list of images in `file` mode
|
||||
|
||||
var
|
||||
client = newHttpClient() ## For loading images from the web
|
||||
@@ -34,28 +33,21 @@ var
|
||||
# Constructors
|
||||
########################
|
||||
|
||||
proc newFileProvider(path: string): ImageProvider =
|
||||
## Create an image provider to access images from the local file system
|
||||
proc newImageProvider(verbose: bool, mode: Mode, path: Option[string]): ImageProvider =
|
||||
randomize()
|
||||
result = ImageProvider(kind: ProviderKind.File, path: path, exts: supportedExts.toHashSet)
|
||||
ImageProvider(verbose: verbose, mode: mode, path: path, exts: supportedExts.toHashSet)
|
||||
|
||||
proc newInspiroProvider(): ImageProvider =
|
||||
## Create an image provider for the inspiro bot API
|
||||
ImageProvider(kind: ProviderKind.Inspiro, url: inspiroUrl)
|
||||
proc newImageProvider*(verbose: bool): ImageProvider =
|
||||
newImageProvider(verbose, Mode.None, none(string))
|
||||
|
||||
proc newFoxProvider(): ImageProvider =
|
||||
## Create an image provider to access the API at "https://randomfox.ca/floof/".
|
||||
ImageProvider(kind: ProviderKind.Foxes, url: foxesUrl)
|
||||
proc newImageProvider*(verbose: bool, path: string): ImageProvider =
|
||||
newImageProvider(verbose, Mode.None, some(path))
|
||||
|
||||
proc newImageProvider*(kind: ProviderKind, filePath: string = ""): ImageProvider =
|
||||
## Create a new `ImageProvider` for the API.
|
||||
case kind
|
||||
of ProviderKind.Foxes:
|
||||
newFoxProvider()
|
||||
of ProviderKind.Inspiro:
|
||||
newInspiroProvider()
|
||||
of ProviderKind.File:
|
||||
newFileProvider(filePath)
|
||||
proc newImageProvider*(verbose: bool, mode: Mode): ImageProvider =
|
||||
newImageProvider(verbose, mode, none(string))
|
||||
|
||||
proc newImageProvider*(verbose: bool, mode: Mode, path: string): ImageProvider =
|
||||
newImageProvider(verbose, mode, some(path))
|
||||
|
||||
proc newFileOpResultError(msg: string): FileOpResult =
|
||||
FileOpResult(success: false, errorMsg: msg)
|
||||
@@ -77,7 +69,7 @@ proc log(ip: ImageProvider, msg: string) =
|
||||
proc getFox(ip: ImageProvider): FileOpResult =
|
||||
## Download image from the fox API
|
||||
try:
|
||||
let urlData = client.getContent(ip.url)
|
||||
let urlData = client.getContent(foxesUrl)
|
||||
let info = parseJson(urlData)
|
||||
let imageData = client.getContent(info["image"].getStr)
|
||||
let dlFile = fmt"{tmpFile}.download"
|
||||
@@ -93,7 +85,7 @@ proc getFox(ip: ImageProvider): FileOpResult =
|
||||
proc getInspiro(ip: ImageProvider): FileOpResult =
|
||||
## Download and save image from the inspiro API
|
||||
try:
|
||||
let imageUrl = client.getContent(ip.url)
|
||||
let imageUrl = client.getContent(inspiroUrl)
|
||||
ip.log fmt"Downloading inspiro image from: '{imageUrl}'"
|
||||
let imageData = client.getContent(imageUrl)
|
||||
let dlFile = fmt"{tmpFile}.download"
|
||||
@@ -105,32 +97,35 @@ proc getInspiro(ip: ImageProvider): FileOpResult =
|
||||
|
||||
proc getLocalFile(ip: var ImageProvider): FileOpResult =
|
||||
## Provide an image from a local folder
|
||||
|
||||
# First, check if there are still images left to be loaded.
|
||||
# If not reread all files from the path
|
||||
if ip.files.len < 1:
|
||||
if ip.path == "":
|
||||
if ip.files.len < 1:
|
||||
if ip.path.isNone:
|
||||
return newFileOpResultError("No path for image loading")
|
||||
ip.log "Reloading file list..."
|
||||
for file in walkDirRec(ip.path):
|
||||
for file in walkDirRec(ip.path.get):
|
||||
let split = splitFile(file)
|
||||
if ip.exts.contains(split.ext):
|
||||
ip.files.add(file)
|
||||
ip.log fmt"Loaded {ip.files.len} files"
|
||||
shuffle(ip.files)
|
||||
# Remove the current file after
|
||||
|
||||
# Remove the current file after
|
||||
result = newFileOpResult(ip.files[0])
|
||||
ip.files.delete(0)
|
||||
|
||||
proc getFileName(ip: var ImageProvider): FileOpResult =
|
||||
## Get the temporary file name of the next file to display
|
||||
case ip.kind
|
||||
of ProviderKind.File:
|
||||
case ip.mode
|
||||
of Mode.File:
|
||||
return ip.getLocalFile()
|
||||
of ProviderKind.Foxes:
|
||||
of Mode.Foxes:
|
||||
return ip.getFox()
|
||||
of ProviderKind.Inspiro:
|
||||
of Mode.Inspiro:
|
||||
return ip.getInspiro()
|
||||
return newFileOpResultError("Not implemented")
|
||||
else:
|
||||
return newFileOpResultError("Not implemented")
|
||||
|
||||
########################
|
||||
# Exported procs
|
||||
@@ -139,6 +134,9 @@ proc getFileName(ip: var ImageProvider): FileOpResult =
|
||||
proc next*(ip: var ImageProvider, width, height: int): FileOpResult =
|
||||
## Uses the image provider to get a new image ready to display.
|
||||
## `width` and `height` should be the size of the window.
|
||||
if ip.mode == Mode.None:
|
||||
return newFileOpResultError("No mode active")
|
||||
|
||||
let op = ip.getFileName()
|
||||
if not op.success: return op
|
||||
|
||||
@@ -147,7 +145,7 @@ proc next*(ip: var ImageProvider, width, height: int): FileOpResult =
|
||||
var w, h: int
|
||||
if (width > height):
|
||||
h = height
|
||||
w = ((rawPixbuf.width * h) / rawPixbuf.height).toInt
|
||||
w = ((rawPixbuf.width * h) / rawPixbuf.height).toInt
|
||||
else:
|
||||
w = width
|
||||
h = ((rawPixbuf.height * w) / rawPixbuf.width).toInt
|
||||
|
||||
@@ -24,7 +24,7 @@ var
|
||||
# Server vor recieving commands from external tools
|
||||
serverWorker: system.Thread[ServerArgs]
|
||||
|
||||
proc enumToStrings(en: typedesc): seq[string] =
|
||||
proc enumToStrings(en: typedesc): seq[string] =
|
||||
for x in en:
|
||||
result.add $x
|
||||
|
||||
@@ -44,7 +44,7 @@ proc notify(label: Label, message: string = "") =
|
||||
proc newArgs(): Option[Args] =
|
||||
let p = newParser("randopix"):
|
||||
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("-t", "--timeout", help="Seconds before the image is refreshed", default="300")
|
||||
flag("-w", "--windowed", help="Do not start in fullscreen mode")
|
||||
@@ -52,12 +52,24 @@ proc newArgs(): Option[Args] =
|
||||
|
||||
try:
|
||||
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
|
||||
var timeout = 3000
|
||||
try:
|
||||
timeout = opts.timeout.parseInt * 1000
|
||||
timeout = opts.timeout.parseInt * 1000
|
||||
except ValueError:
|
||||
raise newException(UsageError, fmt"Invalid timeout value: {opts.timeout}")
|
||||
|
||||
@@ -66,44 +78,48 @@ proc newArgs(): Option[Args] =
|
||||
verbose: opts.verbose,
|
||||
timeout: timeout))
|
||||
except:
|
||||
echo getCurrentExceptionMsg()
|
||||
echo p.help
|
||||
|
||||
proc updateImage(image: Image): bool =
|
||||
## Updates the UI with a new image
|
||||
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
|
||||
window.getSize(wWidth, wHeight)
|
||||
|
||||
let op = imageProvider.next(wWidth, wHeight)
|
||||
result = op.success
|
||||
if op.success:
|
||||
image.setFromFile(op.file)
|
||||
else:
|
||||
if not op.success:
|
||||
label.notify op.errorMsg
|
||||
return
|
||||
|
||||
image.setFromFile(op.file)
|
||||
label.notify
|
||||
except:
|
||||
let
|
||||
e = getCurrentException()
|
||||
msg = getCurrentExceptionMsg()
|
||||
log "Got exception ", repr(e), " with message ", msg
|
||||
label.notify "Error while refreshing image, retrying..."
|
||||
return false
|
||||
|
||||
proc timedUpdate(image: Image): bool =
|
||||
let ok = updateImage(image);
|
||||
if not ok:
|
||||
label.notify "Error while refreshing image, retrying..."
|
||||
else:
|
||||
label.notify
|
||||
proc timedUpdate(image: Image): bool =
|
||||
discard updateImage(image);
|
||||
updateTimeout = int(timeoutAdd(uint32(args.timeout), timedUpdate, image))
|
||||
return false
|
||||
|
||||
proc forceUpdate(action: SimpleAction; parameter: Variant; image: Image): void =
|
||||
log "Force refreshing image now"
|
||||
log "Refreshing image..."
|
||||
label.notify "Refreshing image..."
|
||||
if updateTimeout > 0:
|
||||
discard updateTimeout.remove
|
||||
discard image.timedUpdate()
|
||||
updateTimeout = int(timeoutAdd(500, timedUpdate, image))
|
||||
|
||||
proc checkServerChannel(image: Image): bool =
|
||||
## Check the channel from the control server for incomming commands
|
||||
@@ -127,7 +143,6 @@ proc checkServerChannel(image: Image): bool =
|
||||
|
||||
sleep(100)
|
||||
result = true
|
||||
# discard idleAdd(checkServerChannel, parameter)
|
||||
|
||||
proc toggleFullscreen(action: SimpleAction; parameter: Variant; window: ApplicationWindow) =
|
||||
## Fullscreen toggle event
|
||||
@@ -140,7 +155,7 @@ proc toggleFullscreen(action: SimpleAction; parameter: Variant; window: Applicat
|
||||
proc cleanUp(w: ApplicationWindow, app: Application) =
|
||||
## Stop the control server and exit the GTK application
|
||||
log "Stopping control server..."
|
||||
closeServer()
|
||||
closeServer()
|
||||
serverWorker.joinThread()
|
||||
chan.close()
|
||||
log "Server stopped."
|
||||
@@ -149,7 +164,7 @@ proc cleanUp(w: ApplicationWindow, app: Application) =
|
||||
proc quit(action: SimpleAction; parameter: Variant; app: Application) =
|
||||
## Application quit event
|
||||
cleanUp(window, app)
|
||||
|
||||
|
||||
proc appActivate(app: Application) =
|
||||
# Parse arguments from the command line
|
||||
let parsed = newArgs()
|
||||
@@ -163,7 +178,7 @@ proc appActivate(app: Application) =
|
||||
window.setKeepAbove(false)
|
||||
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()
|
||||
discard provider.loadFromData(css)
|
||||
addProviderForScreen(getDefaultScreen(), provider, STYLE_PROVIDER_PRIORITY_USER)
|
||||
@@ -176,14 +191,14 @@ proc appActivate(app: Application) =
|
||||
let container = newOverlay()
|
||||
container.addOverlay(label)
|
||||
window.add(container)
|
||||
|
||||
|
||||
let image = newImage()
|
||||
container.add(image)
|
||||
|
||||
if args.fullscreen:
|
||||
window.fullscreen
|
||||
|
||||
## Connect the GTK signals to the procs
|
||||
## Connect the GTK signals to the procs
|
||||
let fullscreenAction = newSimpleAction("fullscreen")
|
||||
discard fullscreenAction.connect("activate", toggleFullscreen, window)
|
||||
app.setAccelsForAction("win.fullscreen", "F")
|
||||
|
||||
Reference in New Issue
Block a user