Compare commits
7 Commits
cdb6babd48
...
a41197562b
Author | SHA1 | Date | |
---|---|---|---|
|
a41197562b | ||
|
0ace41f545 | ||
|
cefe57b980 | ||
|
fe3f0579ad | ||
|
cb1a1c24b7 | ||
|
bc09317d4b | ||
|
c0d376c79f |
@ -2,7 +2,7 @@ use std::path::PathBuf;
|
||||
use structopt::StructOpt;
|
||||
|
||||
mod repo;
|
||||
mod tags;
|
||||
mod repository;
|
||||
mod ui;
|
||||
mod widget;
|
||||
|
||||
|
72
src/repository/dockerhub.rs
Normal file
72
src/repository/dockerhub.rs
Normal file
@ -0,0 +1,72 @@
|
||||
use serde::Deserialize;
|
||||
|
||||
use crate::repository::Error;
|
||||
|
||||
#[derive(Deserialize, Debug, Clone)]
|
||||
struct ImageDetails {
|
||||
architecture: String,
|
||||
size: usize,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Clone)]
|
||||
pub struct Images {
|
||||
images: Vec<ImageDetails>,
|
||||
#[serde(rename(deserialize = "name"))]
|
||||
tag_name: String,
|
||||
last_updated: String,
|
||||
}
|
||||
|
||||
impl Images {
|
||||
pub fn convert(&self) -> super::Tag {
|
||||
super::Tag {
|
||||
name: self.tag_name.clone(),
|
||||
last_updated: Some(self.last_updated.clone()),
|
||||
details: self
|
||||
.images
|
||||
.iter()
|
||||
.map(|d| super::TagDetails {
|
||||
arch: Some(d.architecture.clone()),
|
||||
size: Some(d.size),
|
||||
})
|
||||
.collect(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct DockerHub {
|
||||
#[serde(rename(deserialize = "next"))]
|
||||
next_page: Option<String>,
|
||||
results: Vec<Images>,
|
||||
}
|
||||
|
||||
impl DockerHub {
|
||||
/// fetches tag information with a repository name in the form of organization/repository or library/repository in the case of official images from docker
|
||||
pub fn create_repo(repo: &str) -> Result<super::Repo, Error> {
|
||||
let request = format!("https://hub.docker.com/v2/repositories/{}/tags", repo);
|
||||
Self::with_url(&request)
|
||||
}
|
||||
|
||||
/// fetches tag information from a url
|
||||
pub fn with_url(url: &str) -> Result<super::Repo, Error> {
|
||||
let response = match reqwest::blocking::get(url) {
|
||||
Ok(result) => result,
|
||||
Err(e) => return Err(Error::Fetching(format!("reqwest error: {}", e))),
|
||||
};
|
||||
|
||||
//convert it to json
|
||||
let tags = match response.json::<Self>() {
|
||||
Ok(result) => result,
|
||||
Err(e) => return Err(Error::Converting(format!("invalid json: {}", e))),
|
||||
};
|
||||
|
||||
if tags.results.is_empty() {
|
||||
return Err(Error::NoTagsFound);
|
||||
}
|
||||
|
||||
Ok(super::Repo {
|
||||
tags: tags.results.iter().map(|t| t.convert()).collect(),
|
||||
next_page: tags.next_page,
|
||||
})
|
||||
}
|
||||
}
|
73
src/repository/ghcr.rs
Normal file
73
src/repository/ghcr.rs
Normal file
@ -0,0 +1,73 @@
|
||||
use serde::Deserialize;
|
||||
|
||||
use crate::repository::Error;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct Token {
|
||||
token: String,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct Ghcr {
|
||||
tags: Vec<String>,
|
||||
}
|
||||
|
||||
impl Ghcr {
|
||||
/// fetches tag information with a repository name in the form of organization/repository or library/repository in the case of official images from docker
|
||||
pub fn create_repo(repo: &str) -> Result<super::Repo, Error> {
|
||||
let request_token = format!("https://ghcr.io/token?scope=repository:{}:pull", repo);
|
||||
let response = match reqwest::blocking::get(request_token) {
|
||||
Err(e) => return Err(Error::Fetching(format!("reqwest error: {}", e))),
|
||||
Ok(response) => response,
|
||||
};
|
||||
|
||||
let token = match response.json::<Token>() {
|
||||
Err(e) => return Err(Error::Converting(format!("invalid token json: {}", e))),
|
||||
Ok(token) => token.token,
|
||||
};
|
||||
|
||||
let request = format!("https://ghcr.io/v2/{}/tags/list?n=100", repo);
|
||||
let client = reqwest::blocking::Client::new();
|
||||
let response = match client
|
||||
.get(request)
|
||||
.header(reqwest::header::AUTHORIZATION, format!("Bearer {}", token))
|
||||
.send()
|
||||
{
|
||||
// let response = match reqwest::blocking::get(url) {
|
||||
Ok(result) => result,
|
||||
Err(e) => return Err(Error::Fetching(format!("reqwest error: {}", e))),
|
||||
};
|
||||
|
||||
//convert it to json
|
||||
let tags = match response.json::<Self>() {
|
||||
Ok(result) => result,
|
||||
Err(e) => return Err(Error::Converting(format!("invalid json: {}", e))),
|
||||
};
|
||||
|
||||
if tags.tags.is_empty() {
|
||||
return Err(Error::NoTagsFound);
|
||||
}
|
||||
|
||||
Ok(super::Repo {
|
||||
tags: tags
|
||||
.tags
|
||||
.iter()
|
||||
.map(|t| super::Tag {
|
||||
name: t.clone(),
|
||||
details: vec![],
|
||||
last_updated: None,
|
||||
})
|
||||
.collect(),
|
||||
next_page: None,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::Ghcr;
|
||||
#[test]
|
||||
fn test_ghcr() {
|
||||
Ghcr::create_repo("ghcr.io/linuxserver/beets").unwrap();
|
||||
}
|
||||
}
|
183
src/repository/mod.rs
Normal file
183
src/repository/mod.rs
Normal file
@ -0,0 +1,183 @@
|
||||
mod dockerhub;
|
||||
mod ghcr;
|
||||
|
||||
use std::fmt;
|
||||
|
||||
use chrono::DateTime;
|
||||
|
||||
use crate::repo;
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub enum Error {
|
||||
/// couldn't fetch json with reqwest
|
||||
Fetching(String),
|
||||
/// a serde error
|
||||
Converting(String),
|
||||
/// invalid repos show a valid json with 0 tags
|
||||
NoTagsFound,
|
||||
}
|
||||
|
||||
impl fmt::Display for Error {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
Error::Fetching(s) => write!(f, "Fetching error: {}", s),
|
||||
Error::Converting(s) => write!(f, "Converting error: {}", s),
|
||||
Error::NoTagsFound => write!(f, "Given Repo has 0 tags. Is it valid?"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct TagDetails {
|
||||
arch: Option<String>,
|
||||
size: Option<usize>,
|
||||
}
|
||||
|
||||
impl fmt::Display for TagDetails {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
let size = match self.size {
|
||||
None => "".to_string(),
|
||||
Some(s) => (s / 1024 / 1024).to_string(),
|
||||
};
|
||||
write!(
|
||||
f,
|
||||
"{}|{}MB",
|
||||
self.arch.as_ref().unwrap_or(&"".to_string()),
|
||||
size
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Tag {
|
||||
name: String,
|
||||
details: Vec<TagDetails>,
|
||||
last_updated: Option<String>,
|
||||
}
|
||||
|
||||
impl Tag {
|
||||
pub fn get_name(&self) -> &str {
|
||||
&self.name
|
||||
}
|
||||
|
||||
pub fn get_name_with_details(&self) -> String {
|
||||
//architecture infos
|
||||
let mut arch = String::new();
|
||||
for image in self.details.iter().take(1) {
|
||||
arch.push_str(&format!("{}", image));
|
||||
}
|
||||
for image in self.details.iter().skip(1) {
|
||||
arch.push_str(&format!(", {}", image));
|
||||
}
|
||||
let arch = if !arch.is_empty() {
|
||||
format!(" [{}]", arch)
|
||||
} else {
|
||||
String::new()
|
||||
};
|
||||
|
||||
let dif = match &self.last_updated {
|
||||
None => "".to_string(),
|
||||
Some(last_updated) => {
|
||||
let now = chrono::Utc::now();
|
||||
let rfc3339 = DateTime::parse_from_rfc3339(last_updated).unwrap();
|
||||
let dif = now - rfc3339.with_timezone(&chrono::Utc);
|
||||
format!(" vor {}", format_time_nice(dif))
|
||||
}
|
||||
};
|
||||
|
||||
if dif.is_empty() {}
|
||||
format!("{}{}{}", self.name, dif, arch)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Repo {
|
||||
tags: Vec<Tag>,
|
||||
next_page: Option<String>,
|
||||
}
|
||||
|
||||
impl Repo {
|
||||
pub fn new(repo: &str) -> Result<Self, Error> {
|
||||
use crate::repo::Repo;
|
||||
let (registry, repo) = match crate::repo::split_repo_without_tag(repo) {
|
||||
Ok(Repo::WithServer(reg, org, pro)) => (Some(reg), format!("{}/{}", org, pro)),
|
||||
Ok(Repo::WithOrga(org, pro)) => (None, format!("{}/{}", org, pro)),
|
||||
Ok(Repo::Project(pro)) => (None, format!("library/{}", pro)),
|
||||
Err(e) => return Err(Error::Converting(format!("{}", e))),
|
||||
};
|
||||
|
||||
if registry.unwrap_or_default() == "ghcr.io" {
|
||||
ghcr::Ghcr::create_repo(&repo)
|
||||
} else {
|
||||
dockerhub::DockerHub::create_repo(&repo)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_url(url: &str) -> Result<Self, Error> {
|
||||
//TODO fix for other registries
|
||||
dockerhub::DockerHub::with_url(url)
|
||||
}
|
||||
|
||||
pub fn get_tags(&self) -> &Vec<Tag> {
|
||||
&self.tags
|
||||
}
|
||||
|
||||
pub fn next_page(&self) -> Option<Self> {
|
||||
match &self.next_page {
|
||||
Some(url) => match Self::with_url(url) {
|
||||
Ok(tags) => Some(tags),
|
||||
Err(_) => None,
|
||||
},
|
||||
None => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// converts a given duration to a readable string
|
||||
fn format_time_nice(time: chrono::Duration) -> String {
|
||||
if time.num_weeks() == 52 {
|
||||
format!("{} Jahr", (time.num_weeks() / 52) as i32)
|
||||
} else if time.num_weeks() > 103 {
|
||||
format!("{} Jahren", (time.num_weeks() / 52) as i32)
|
||||
} else if time.num_days() == 1 {
|
||||
format!("{} Tag", time.num_days())
|
||||
} else if time.num_days() > 1 {
|
||||
format!("{} Tagen", time.num_days())
|
||||
} else if time.num_hours() == 1 {
|
||||
format!("{} Stunde", time.num_hours())
|
||||
} else if time.num_hours() > 1 {
|
||||
format!("{} Stunden", time.num_hours())
|
||||
} else if time.num_minutes() == 1 {
|
||||
format!("{} Minute", time.num_minutes())
|
||||
} else if time.num_minutes() > 1 {
|
||||
format!("{} Minuten", time.num_minutes())
|
||||
} else {
|
||||
format!("{} Sekunden", time.num_seconds())
|
||||
}
|
||||
}
|
||||
|
||||
/// checks the repo name and may add a prefix for official images
|
||||
pub fn check_repo(name: &str) -> Result<String, Error> {
|
||||
let repo = match repo::split_tag_from_repo(name) {
|
||||
Err(e) => return Err(Error::Converting(format!("{}", e))),
|
||||
Ok((name, _)) => name,
|
||||
};
|
||||
|
||||
match repo::split_repo_without_tag(name) {
|
||||
Ok(repo::Repo::Project(s)) => Ok(format!("library/{}", s)),
|
||||
Ok(_) => Ok(repo.to_string()),
|
||||
Err(e) => Err(Error::Converting(format!("{}", e))),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
#[test]
|
||||
fn test_check_repo() {
|
||||
assert_eq!(super::check_repo("nginx").unwrap(), "library/nginx");
|
||||
assert_eq!(super::check_repo("library/nginx").unwrap(), "library/nginx");
|
||||
assert_eq!(
|
||||
super::check_repo("rocketchat/rocket.chat").unwrap(),
|
||||
"rocketchat/rocket.chat"
|
||||
);
|
||||
}
|
||||
}
|
168
src/tags.rs
168
src/tags.rs
@ -1,168 +0,0 @@
|
||||
use std::fmt;
|
||||
|
||||
use crate::repo;
|
||||
use chrono::DateTime;
|
||||
use serde::Deserialize;
|
||||
|
||||
#[derive(Deserialize, Debug, Clone)]
|
||||
struct ImageDetails {
|
||||
architecture: String,
|
||||
size: usize,
|
||||
}
|
||||
|
||||
impl fmt::Display for ImageDetails {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{}|{}MB", self.architecture, self.size / 1024 / 1024)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Clone)]
|
||||
pub struct Images {
|
||||
images: Vec<ImageDetails>,
|
||||
#[serde(rename(deserialize = "name"))]
|
||||
pub tag_name: String,
|
||||
last_updated: String,
|
||||
}
|
||||
|
||||
impl fmt::Display for Images {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
//architecture infos
|
||||
let mut arch = String::new();
|
||||
for image in self.images.iter().take(1) {
|
||||
arch.push_str(&format!("{}", image));
|
||||
}
|
||||
for image in self.images.iter().skip(1) {
|
||||
arch.push_str(&format!(", {}", image));
|
||||
}
|
||||
|
||||
let now = chrono::Utc::now();
|
||||
let rfc3339 = DateTime::parse_from_rfc3339(&self.last_updated).unwrap();
|
||||
let dif = now - rfc3339.with_timezone(&chrono::Utc);
|
||||
write!(
|
||||
f,
|
||||
"{} vor {} [{}]",
|
||||
self.tag_name,
|
||||
format_time_nice(dif),
|
||||
arch
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct Tags {
|
||||
count: usize,
|
||||
#[serde(rename(deserialize = "next"))]
|
||||
pub next_page: Option<String>,
|
||||
pub results: Vec<Images>,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub enum Error {
|
||||
/// couldn't fetch json with reqwest
|
||||
Fetching(String),
|
||||
/// a serde error
|
||||
Converting(String),
|
||||
/// invalid repos show a valid json with 0 tags
|
||||
NoTagsFound,
|
||||
}
|
||||
|
||||
impl fmt::Display for Error {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
Error::Fetching(s) => write!(f, "Fetching error: {}", s),
|
||||
Error::Converting(s) => write!(f, "Converting error: {}", s),
|
||||
Error::NoTagsFound => write!(f, "Given Repo has 0 tags. Is it valid?"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Tags {
|
||||
/// fetches tag information with a repository name in the form of organization/repository or library/repository in the case of official images from docker
|
||||
pub fn new(repo: String) -> Result<Self, Error> {
|
||||
let request = format!("https://hub.docker.com/v2/repositories/{}/tags", repo);
|
||||
Self::with_url(&request)
|
||||
}
|
||||
|
||||
/// fetches tag information from a url
|
||||
fn with_url(url: &str) -> Result<Self, Error> {
|
||||
let res = match reqwest::blocking::get(url) {
|
||||
Ok(result) => result,
|
||||
Err(e) => return Err(Error::Fetching(format!("reqwest error: {}", e))),
|
||||
};
|
||||
|
||||
//convert it to json
|
||||
let raw = res.text().unwrap();
|
||||
let tags: Self = match serde_json::from_str(&raw) {
|
||||
Ok(result) => result,
|
||||
Err(e) => return Err(Error::Converting(format!("invalid json: {}", e))),
|
||||
};
|
||||
|
||||
if tags.count == 0 {
|
||||
return Err(Error::NoTagsFound);
|
||||
}
|
||||
|
||||
Ok(tags)
|
||||
}
|
||||
|
||||
/// checks the repo name and may add a prefix for official images
|
||||
pub fn check_repo(name: &str) -> Result<String, Error> {
|
||||
let repo = match repo::split_tag_from_repo(name) {
|
||||
Err(e) => return Err(Error::Converting(format!("{}", e))),
|
||||
Ok((name, _)) => name,
|
||||
};
|
||||
|
||||
match repo::split_repo_without_tag(name) {
|
||||
Ok(repo::Repo::Project(s)) => Ok(format!("library/{}", s)),
|
||||
Ok(_) => Ok(repo.to_string()),
|
||||
Err(e) => Err(Error::Converting(format!("{}", e))),
|
||||
}
|
||||
}
|
||||
|
||||
/// returns tags of next page
|
||||
pub fn next_page(&self) -> Option<Self> {
|
||||
match &self.next_page {
|
||||
Some(url) => match Self::with_url(url) {
|
||||
Ok(tags) => Some(tags),
|
||||
Err(_) => None,
|
||||
},
|
||||
None => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// converts a given duration to a readable string
|
||||
fn format_time_nice(time: chrono::Duration) -> String {
|
||||
if time.num_weeks() == 52 {
|
||||
format!("{} Jahr", (time.num_weeks() / 52) as i32)
|
||||
} else if time.num_weeks() > 103 {
|
||||
format!("{} Jahren", (time.num_weeks() / 52) as i32)
|
||||
} else if time.num_days() == 1 {
|
||||
format!("{} Tag", time.num_days())
|
||||
} else if time.num_days() > 1 {
|
||||
format!("{} Tagen", time.num_days())
|
||||
} else if time.num_hours() == 1 {
|
||||
format!("{} Stunde", time.num_hours())
|
||||
} else if time.num_hours() > 1 {
|
||||
format!("{} Stunden", time.num_hours())
|
||||
} else if time.num_minutes() == 1 {
|
||||
format!("{} Minute", time.num_minutes())
|
||||
} else if time.num_minutes() > 1 {
|
||||
format!("{} Minuten", time.num_minutes())
|
||||
} else {
|
||||
format!("{} Sekunden", time.num_seconds())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::tags::Tags;
|
||||
#[test]
|
||||
fn test_check_repo() {
|
||||
assert_eq!(Tags::check_repo("nginx").unwrap(), "library/nginx");
|
||||
assert_eq!(Tags::check_repo("library/nginx").unwrap(), "library/nginx");
|
||||
assert_eq!(
|
||||
Tags::check_repo("rocketchat/rocket.chat").unwrap(),
|
||||
"rocketchat/rocket.chat"
|
||||
);
|
||||
}
|
||||
}
|
@ -7,6 +7,7 @@ use tui::backend::TermionBackend;
|
||||
use tui::layout::{Constraint, Direction, Layout};
|
||||
use tui::Terminal;
|
||||
|
||||
use crate::repository;
|
||||
use crate::widget::info;
|
||||
use crate::widget::repo_entry;
|
||||
use crate::widget::service_switcher;
|
||||
@ -156,7 +157,7 @@ impl Ui {
|
||||
match ui.services.extract_repo() {
|
||||
Err(e) => ui.info.set_info(&format!("{}", e)),
|
||||
Ok(s) => {
|
||||
let repo = match crate::tags::Tags::check_repo(&s) {
|
||||
let repo = match repository::check_repo(&s) {
|
||||
Err(e) => {
|
||||
ui.info.set_info(&format!("{}", e));
|
||||
continue;
|
||||
@ -177,7 +178,7 @@ impl Ui {
|
||||
match ui.services.extract_repo() {
|
||||
Err(e) => ui.info.set_info(&format!("{}", e)),
|
||||
Ok(s) => {
|
||||
let repo = match crate::tags::Tags::check_repo(&s) {
|
||||
let repo = match repository::check_repo(&s) {
|
||||
Err(e) => {
|
||||
ui.info.set_info(&format!("{}", e));
|
||||
continue;
|
||||
|
@ -4,7 +4,7 @@ use termion::event::Key;
|
||||
use tui::style::{Color, Style};
|
||||
use tui::widgets::{Block, Borders, List, ListState};
|
||||
|
||||
use crate::tags;
|
||||
use crate::repository;
|
||||
|
||||
pub enum Error {
|
||||
NoneSelected,
|
||||
@ -24,7 +24,7 @@ impl fmt::Display for Error {
|
||||
|
||||
enum Line {
|
||||
Status(String),
|
||||
Image(tags::Images),
|
||||
Image(repository::Tag),
|
||||
NextPage(String),
|
||||
}
|
||||
|
||||
@ -32,7 +32,7 @@ impl fmt::Display for Line {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
Line::Status(s) => write!(f, "{}", s),
|
||||
Line::Image(i) => write!(f, "{}", i),
|
||||
Line::Image(i) => write!(f, "{}", i.get_name_with_details()),
|
||||
Line::NextPage(s) => write!(f, "{}", s),
|
||||
}
|
||||
}
|
||||
@ -41,7 +41,7 @@ impl fmt::Display for Line {
|
||||
pub struct TagList {
|
||||
lines: Vec<Line>,
|
||||
state: ListState,
|
||||
tags: Option<tags::Tags>,
|
||||
tags: Option<repository::Repo>,
|
||||
}
|
||||
|
||||
impl TagList {
|
||||
@ -54,15 +54,15 @@ impl TagList {
|
||||
}
|
||||
|
||||
pub fn with_repo_name(repo: String) -> Self {
|
||||
match tags::Tags::new(repo) {
|
||||
match repository::Repo::new(&repo) {
|
||||
Ok(tags) => Self::with_tags(tags),
|
||||
Err(_) => Self::with_status("input repo was not found"),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_tags(mut tags: tags::Tags) -> Self {
|
||||
pub fn with_tags(mut tags: repository::Repo) -> Self {
|
||||
let mut lines: Vec<Line> = tags
|
||||
.results
|
||||
.get_tags()
|
||||
.iter()
|
||||
.map(|r| Line::Image(r.clone()))
|
||||
.collect();
|
||||
@ -136,7 +136,7 @@ impl TagList {
|
||||
None => Err(Error::NoneSelected),
|
||||
Some(i) => match &self.lines[i] {
|
||||
Line::Status(_) => Err(Error::SelectedStatus),
|
||||
Line::Image(i) => Ok(i.tag_name.clone()),
|
||||
Line::Image(i) => Ok(i.get_name().to_string()),
|
||||
Line::NextPage(_) => {
|
||||
self.load_next_page();
|
||||
Err(Error::NextPageSelected)
|
||||
@ -158,12 +158,17 @@ impl TagList {
|
||||
let next_page = self.lines.pop();
|
||||
|
||||
//add tags
|
||||
for image in &self.tags.as_ref().unwrap().results {
|
||||
self.lines.push(Line::Image(image.clone()));
|
||||
match &self.tags {
|
||||
None => (),
|
||||
Some(tags) => {
|
||||
for image in tags.get_tags().iter() {
|
||||
self.lines.push(Line::Image(image.clone()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//readd next page
|
||||
match self.tags.as_ref().unwrap().next_page {
|
||||
match self.tags.as_ref().unwrap().next_page() {
|
||||
None => (),
|
||||
Some(_) => self.lines.push(next_page.unwrap()),
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user