First version
This commit is contained in:
commit
1e18557bd8
|
@ -0,0 +1,4 @@
|
|||
/target
|
||||
/test_pictures
|
||||
/wallpapers
|
||||
.vscode
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,15 @@
|
|||
[package]
|
||||
name = "roulette-rs"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
actix-web = "4"
|
||||
clap = { version = "4.0.23", features = [ "derive" ] }
|
||||
clap_complete = "4.0.5"
|
||||
infer = "0.11.0"
|
||||
rand = "0.8.5"
|
||||
tokio = { version = "1.21.2", features = [ "fs" ] }
|
||||
imagesize = "0.10.1"
|
|
@ -0,0 +1,21 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2022 SeanOMik
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
|
@ -0,0 +1,6 @@
|
|||
# Roulette
|
||||
Serve a random image!
|
||||
|
||||
---
|
||||
|
||||
This project is a rust rewrite of a [friend's project](https://git.seedno.de/seednode/roulette) that I did for fun.
|
|
@ -0,0 +1,9 @@
|
|||
{ pkgs ? import <nixpkgs> {} }:
|
||||
|
||||
pkgs.mkShell {
|
||||
# nativeBuildInputs is usually what you want -- tools you need to run
|
||||
nativeBuildInputs = with pkgs; [
|
||||
gdb
|
||||
lldb
|
||||
];
|
||||
}
|
|
@ -0,0 +1,193 @@
|
|||
use actix_web::{get, web, App, HttpResponse, HttpServer, Responder};
|
||||
use clap::Parser;
|
||||
use infer::MatcherType;
|
||||
use tokio::io::AsyncReadExt;
|
||||
use std::path::{PathBuf, Path};
|
||||
use std::{io, fs};
|
||||
use rand::seq::SliceRandom;
|
||||
|
||||
/*
|
||||
Usage:
|
||||
roulette <path> [path2]... [flags]
|
||||
roulette [command]
|
||||
|
||||
Available Commands:
|
||||
completion Generate the autocompletion script for the specified shell
|
||||
help Help about any command
|
||||
version Print version
|
||||
|
||||
Flags:
|
||||
-c, --cache only scan directories once, at startup (incompatible with --filter)
|
||||
-f, --filter enable filtering via query parameters (incompatible with --cache)
|
||||
-h, --help help for roulette
|
||||
-p, --port uint16 port to listen on (default 8080)
|
||||
-r, --recursive recurse into subdirectories
|
||||
-s, --sort enable sorting via query parameters
|
||||
-v, --verbose log accessed files to stdout
|
||||
|
||||
Use "roulette [command] --help" for more information about a command.
|
||||
*/
|
||||
|
||||
/// Simple program to greet a person
|
||||
#[derive(Parser, Debug)]
|
||||
#[command(author, version, about)]
|
||||
struct CliArgs {
|
||||
/// Name of the person to greet
|
||||
//#[arg(short, long)]
|
||||
paths: Vec<PathBuf>,
|
||||
|
||||
#[arg(short, long, default_value_t = 8080)]
|
||||
port: u16,
|
||||
|
||||
#[arg(short, long, default_value_t = true)]
|
||||
recursive: bool
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct IndexedImage {
|
||||
path: PathBuf
|
||||
}
|
||||
|
||||
impl IndexedImage {
|
||||
pub fn new(path: PathBuf) -> Self {
|
||||
Self {
|
||||
path
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct AppState {
|
||||
pub pictures: Vec<IndexedImage>,
|
||||
}
|
||||
|
||||
impl AppState {
|
||||
pub fn new(pictures: Vec<IndexedImage>) -> Self {
|
||||
Self {
|
||||
pictures
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[get("/{tail}*")]
|
||||
async fn chose_picture(data: web::Data<AppState>, path: web::Path<String>) -> impl Responder {
|
||||
let tail = path.into_inner();
|
||||
|
||||
if tail.is_empty() {
|
||||
let pictures = &data.pictures;
|
||||
|
||||
if let Some(chosen) = pictures.choose(&mut rand::thread_rng()) {
|
||||
let path = match chosen.path.to_str() {
|
||||
Some(s) => s,
|
||||
None => {
|
||||
return HttpResponse::InternalServerError().body("Failed to convert path PathBuf to &str (The path is not valid unicode)!");
|
||||
}
|
||||
};
|
||||
|
||||
HttpResponse::TemporaryRedirect().insert_header((actix_web::http::header::LOCATION, format!("/{}", path))).finish()
|
||||
} else {
|
||||
HttpResponse::NotFound().body("No images were discovered")
|
||||
}
|
||||
} else {
|
||||
println!("Chose \"{}\"", tail);
|
||||
|
||||
let (width, height) = match imagesize::size(tail.clone()) {
|
||||
Ok(dim) => (dim.width, dim.height),
|
||||
Err(err) => panic!("Error: {}", err),
|
||||
};
|
||||
|
||||
let body = format!("
|
||||
<html lang=\"en\">
|
||||
<head>
|
||||
<style>
|
||||
html,body{{margin:0;padding:0;height:100%;}}a{{display:block;height:100%;width:100%;text-decoration:none;}}img{{margin:auto;display:block;max-width:97%;max-height:97%;object-fit:scale-down;position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);}}
|
||||
</style>
|
||||
<title>
|
||||
dirty-closet.png ({}x{})
|
||||
</title>
|
||||
</head>
|
||||
<body>
|
||||
<a href=\"/\">
|
||||
<img src=\"/img/{}\" width=\"{0}\" height=\"{1}\" alt=\"Roulette selected: {}\">
|
||||
</a>
|
||||
</body>
|
||||
</html>", width, height, tail.clone(), tail);
|
||||
|
||||
HttpResponse::Ok().body(body)
|
||||
}
|
||||
}
|
||||
|
||||
fn get_image_mime(path: &String) -> std::io::Result<Option<String>> {
|
||||
if let Some(kind) = infer::get_from_path(path)? {
|
||||
if kind.matcher_type() == MatcherType::Image {
|
||||
return Ok(Some(String::from(kind.mime_type())));
|
||||
}
|
||||
}
|
||||
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
#[get("/img/{tail}*")]
|
||||
async fn serve_picture(path: web::Path<String>) -> impl Responder {
|
||||
let tail = path.into_inner();
|
||||
|
||||
if !tail.is_empty() {
|
||||
println!("Serving file \"{}\"", tail);
|
||||
|
||||
let mut file = tokio::fs::File::open(&tail).await.unwrap(); // TODO: Don't unwrap
|
||||
|
||||
let mut contents = vec![];
|
||||
file.read_to_end(&mut contents).await.unwrap(); // TODO: Don't unwrap
|
||||
|
||||
let content_type = get_image_mime(&tail).unwrap(); // TODO: Don't unwrap
|
||||
|
||||
HttpResponse::Ok().content_type(content_type.unwrap_or_default()).body(contents)
|
||||
} else {
|
||||
HttpResponse::NotFound().body("No path given")
|
||||
}
|
||||
}
|
||||
|
||||
fn image_discovery(dir: &Path, recursive: bool) -> io::Result<Vec<IndexedImage>> {
|
||||
let mut files = vec![];
|
||||
|
||||
if dir.is_dir() {
|
||||
for entry in fs::read_dir(dir)? {
|
||||
let entry = entry?;
|
||||
let path = entry.path();
|
||||
if path.is_dir() {
|
||||
if recursive {
|
||||
let mut explored = image_discovery(&path, recursive)?;
|
||||
files.append(&mut explored);
|
||||
}
|
||||
} else {
|
||||
files.push(IndexedImage::new(path));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(files)
|
||||
}
|
||||
|
||||
#[actix_web::main]
|
||||
async fn main() -> std::io::Result<()> {
|
||||
let args: CliArgs = CliArgs::parse();
|
||||
|
||||
// Read all the images from the supplied paths
|
||||
let mut images = vec![];
|
||||
for path in args.paths {
|
||||
println!("Exploring path: {:?}", path);
|
||||
let mut explored = image_discovery(Path::new(&path), args.recursive)?;
|
||||
images.append(&mut explored);
|
||||
}
|
||||
|
||||
println!("Discovered {} images!", images.len());
|
||||
|
||||
HttpServer::new(move || {
|
||||
App::new()
|
||||
.app_data(web::Data::new(AppState::new(images.clone())))
|
||||
.service(serve_picture)
|
||||
.service(chose_picture)
|
||||
})
|
||||
.bind(("127.0.0.1", 8080))?
|
||||
.run()
|
||||
.await
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
import requests
|
||||
|
||||
base_url = "https://cdn.seedno.de/img/PokemonWallpapers/"
|
||||
out = "pokemon-wallpapers"
|
||||
|
||||
for i in range(0, 493):
|
||||
padded = format(i, '03')
|
||||
|
||||
print(f'Downloading {i:03}.jpg')
|
||||
|
||||
r = requests.get(base_url + padded + ".jpg")
|
||||
|
||||
with open(out + "/" + padded + ".jpg", 'wb') as f:
|
||||
f.write(r.content)
|
||||
|
||||
print("Done!")
|
Loading…
Reference in New Issue