diff --git a/src/providers.nim b/src/providers.nim index 4167152..dacde94 100644 --- a/src/providers.nim +++ b/src/providers.nim @@ -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 diff --git a/src/randopix.nim b/src/randopix.nim index 290fbc6..a6335a1 100644 --- a/src/randopix.nim +++ b/src/randopix.nim @@ -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")