Skip to content

Instantly share code, notes, and snippets.

@arifd

arifd/foo.rs Secret

Last active August 30, 2022 19:43
Show Gist options
  • Save arifd/51499d11b19ef3be7409ce1c49820ed9 to your computer and use it in GitHub Desktop.
Save arifd/51499d11b19ef3be7409ce1c49820ed9 to your computer and use it in GitHub Desktop.
use crate::Source;
use cabinet_types::DocumentEntryId;
use indicatif::{MultiProgress, ProgressBar};
use once_cell::sync::Lazy;
pub use phases::*;
use std::time::{Duration, Instant};
pub static PROGRESS: Lazy<MultiProgress> = Lazy::new(|| {
let multi = MultiProgress::new();
// attempt to add a header, to divide logs and currently active
// jobs. Don't know why it doesn't appear!
// let bar = ProgressBar::new_spinner();
// bar.enable_steady_tick(Duration::from_millis(250));
// multi.add(bar);
multi
});
pub struct Progress<T: Phase> {
start: Instant,
curr: Instant,
phase: T,
bar: ProgressBar,
finished: bool,
}
impl<T: Phase> Drop for Progress<T> {
fn drop(&mut self) {
// drop-check to know when we have been dropped prematurely.
// state transitions will set this flag to true before dropping the previous state
// and a terminating state must have this set to true also.
// returning from a function via `?` or simply not reaching a terminal state
// before dropping `Progress` will cause us to determine the pipeline failed.
if !self.finished {
// self.bar.set_style(self.phase.to_style(<T as Phase>::FAIL));
// self.bar.finish_with_message("Failure");
self.bar.finish_and_clear();
}
}
}
pub struct ProgressBuilder;
impl ProgressBuilder {
pub fn build(id: DocumentEntryId, source: Source) -> Progress<Preparing> {
let start = Instant::now();
let phase = Preparing { id, source };
let style = phase.to_style(Preparing::RUN);
let bar = PROGRESS.add(ProgressBar::new_spinner());
bar.set_style(style);
bar.set_message("Processing");
bar.enable_steady_tick(Duration::from_millis(250));
Progress {
start,
curr: start,
phase,
bar,
finished: false,
}
}
}
impl Progress<Preparing> {
pub fn advance_to_extracting(mut self) -> Progress<Extracting> {
self.finished = true;
let phase = Extracting {
id: self.phase.id,
source: self.phase.source.clone(),
prev: [self.curr.elapsed()],
};
let style = phase.to_style(Extracting::RUN);
let bar = self.bar.clone();
bar.set_style(style);
Progress {
start: self.start,
curr: Instant::now(),
phase,
bar,
finished: false,
}
}
pub fn advance_to_parsing(mut self) -> Progress<Parsing> {
self.finished = true;
let phase = Parsing {
id: self.phase.id,
source: self.phase.source.clone(),
prev: [self.curr.elapsed()],
};
let style = phase.to_style(Extracting::RUN);
let bar = self.bar.clone();
bar.set_style(style);
Progress {
start: self.start,
curr: Instant::now(),
phase,
bar,
finished: false,
}
}
pub fn reject(self) {
// let phase = Success {
// id: self.phase.id,
// source: self.phase.source.clone(),
// start: self.start,
// prev: [
// self.curr.elapsed(),
// Duration::ZERO,
// Duration::ZERO,
// Duration::ZERO,
// Duration::ZERO,
// Duration::ZERO,
// ]
// };
// let style = phase.to_style(Success::RUN);
// self.bar.set_style(style);
// self.bar.finish_with_message("Rejected");
// self.finished = true;
}
}
impl Progress<Extracting> {
pub fn success(self) {
// let [t0] = self.phase.prev;
// self.finished = true;
// let phase = Success {
// id: self.phase.id,
// source: self.phase.source.clone(),
// start: self.start,
// prev: [
// t0,
// self.curr.elapsed(),
// Duration::ZERO,
// Duration::ZERO,
// Duration::ZERO,
// Duration::ZERO,
// ],
// };
// let style = phase.to_style(Success::RUN);
// self.bar.set_style(style);
// self.bar.finish_with_message("Success");
// self.finished = true;
}
}
impl Progress<Parsing> {
pub fn advance_to_storing(mut self) -> Progress<Storing> {
let [t0] = self.phase.prev;
self.finished = true;
let phase = Storing {
id: self.phase.id,
source: self.phase.source.clone(),
prev: [t0, self.curr.elapsed()],
};
let style = phase.to_style(Storing::RUN);
let bar = self.bar.clone();
bar.set_style(style);
Progress {
start: self.start,
curr: Instant::now(),
phase,
bar,
finished: false,
}
}
}
impl Progress<Storing> {
pub fn advance_to_indexing(mut self) -> Progress<Indexing> {
let [t0, t1] = self.phase.prev;
self.finished = true;
let phase = Indexing {
id: self.phase.id,
source: self.phase.source.clone(),
prev: [t0, t1, self.curr.elapsed()],
};
let style = phase.to_style(Indexing::RUN);
let bar = self.bar.clone();
bar.set_style(style);
Progress {
start: self.start,
curr: Instant::now(),
phase,
bar,
finished: false,
}
}
}
impl Progress<Indexing> {
pub fn advance_to_syncing(mut self) -> Progress<Syncing> {
let [t0, t1, t2] = self.phase.prev;
self.finished = true;
let phase = Syncing {
id: self.phase.id,
source: self.phase.source.clone(),
prev: [t0, t1, t2, self.curr.elapsed()],
};
let style = phase.to_style(Syncing::RUN);
let bar = self.bar.clone();
bar.set_style(style);
Progress {
start: self.start,
curr: Instant::now(),
phase,
bar,
finished: false,
}
}
}
impl Progress<Syncing> {
pub fn advance_to_updating(mut self) -> Progress<Updating> {
let [t0, t1, t2, t3] = self.phase.prev;
self.finished = true;
let phase = Updating {
id: self.phase.id,
source: self.phase.source.clone(),
prev: [t0, t1, t2, t3, self.curr.elapsed()],
};
let style = phase.to_style(Updating::RUN);
let bar = self.bar.clone();
bar.set_style(style);
Progress {
start: self.start,
curr: Instant::now(),
phase,
bar,
finished: false,
}
}
}
impl Progress<Updating> {
pub fn success(self) {
// let [t0, t1, t2, t3, t4] = self.phase.prev;
// self.finished = true;
// let phase = Success {
// start: self.start,
// id: self.phase.id,
// source: self.phase.source.clone(),
// prev: [t0, t1, t2, t3, t4, self.curr.elapsed()],
// };
// let style = phase.to_style(Success::RUN);
// self.bar.set_style(style);
// self.bar.finish_with_message("Success");
// self.finished = true;
}
}
mod phases {
use crate::Source;
use cabinet_types::{DocumentEntryId, EntryId};
use indicatif::{ProgressState, ProgressStyle};
use std::{
fmt::Write,
time::{Duration, Instant},
};
pub trait Phase {
const RUN: &'static str;
const FAIL: &'static str;
fn to_style(&self, template: &str) -> ProgressStyle;
}
pub struct Preparing {
pub id: DocumentEntryId,
pub source: Source,
}
impl Phase for Preparing {
const RUN: &'static str = "[{msg:.magenta}] [{id:.blue}] [{source:.blue}] [{spinner:.blue}]: [{0:.blue}] [{1}] [{2}] [{3}] [{4}] [{5}]: [{elapsed_precise:.blue}]";
const FAIL: &'static str = "[{msg:.magenta}] [{id:.blue}] [{source:.blue}] [{spinner:.blue}]: [{0:.red}] [{1}] [{2}] [{3}] [{4}] [{5}]: [{elapsed_precise:.blue}]";
#[rustfmt::skip]
fn to_style(&self, template: &str) -> ProgressStyle {
let id = self.id.as_i32();
let source = self.source.clone();
let mut sty = ProgressStyle::with_template(template).unwrap();
sty = spinner(sty);
sty = sty.with_key("id", move |_state: &ProgressState, w: &mut dyn Write| write!(w, "{}", id ).unwrap());
sty = sty.with_key("source", move |_state: &ProgressState, w: &mut dyn Write| write!(w, "{}", source).unwrap());
sty = sty.with_key("0", |_state: &ProgressState, w: &mut dyn Write| write!(w, "Preparing" ).unwrap());
sty = sty.with_key("1", |_state: &ProgressState, w: &mut dyn Write| write!(w, "Unknown" ).unwrap());
sty = sty.with_key("2", |_state: &ProgressState, w: &mut dyn Write| write!(w, "Storing" ).unwrap());
sty = sty.with_key("3", |_state: &ProgressState, w: &mut dyn Write| write!(w, "Indexing" ).unwrap());
sty = sty.with_key("4", |_state: &ProgressState, w: &mut dyn Write| write!(w, "Syncing" ).unwrap());
sty = sty.with_key("5", |_state: &ProgressState, w: &mut dyn Write| write!(w, "Updating" ).unwrap());
sty
}
}
pub struct Extracting {
pub id: DocumentEntryId,
pub source: Source,
pub prev: [Duration; 1],
}
impl Phase for Extracting {
const RUN: &'static str = "[{msg:.magenta}] [{id:.blue}] [{source:.blue}] [{spinner:.blue}]: [{0:.green}] [{1:.blue}] [{2}] [{3}] [{4}] [{5}]: [{elapsed_precise:.blue}]";
const FAIL: &'static str = "[{msg:.red}] [{id:.blue}] [{source:.blue}] [{spinner:.blue}]: [{0:.green}] [{1:.red}] [{2}] [{3}] [{4}] [{5}]: [{elapsed_precise:.blue}]";
#[rustfmt::skip]
fn to_style(&self, template: &str) -> ProgressStyle {
let id = self.id.as_i32();
let source = self.source.clone();
let [t0] = self.prev;
let mut sty = ProgressStyle::with_template(template).unwrap();
sty = spinner(sty);
sty = sty.with_key("id", move |_state: &ProgressState, w: &mut dyn Write| write!(w, "{}", id ).unwrap());
sty = sty.with_key("source", move |_state: &ProgressState, w: &mut dyn Write| write!(w, "{}", source).unwrap());
sty = sty.with_key("0", move |_state: &ProgressState, w: &mut dyn Write| write!(w, "{:?}", t0 ).unwrap());
sty = sty.with_key("1", move |_state: &ProgressState, w: &mut dyn Write| write!(w, "Extracting" ).unwrap());
sty = sty.with_key("2", |_state: &ProgressState, w: &mut dyn Write| write!(w, "Storing" ).unwrap());
sty = sty.with_key("3", |_state: &ProgressState, w: &mut dyn Write| write!(w, "Indexing" ).unwrap());
sty = sty.with_key("4", |_state: &ProgressState, w: &mut dyn Write| write!(w, "Syncing" ).unwrap());
sty = sty.with_key("5", |_state: &ProgressState, w: &mut dyn Write| write!(w, "Updating" ).unwrap());
sty
}
}
pub struct Parsing {
pub id: DocumentEntryId,
pub source: Source,
pub prev: [Duration; 1],
}
impl Phase for Parsing {
const RUN: &'static str = "[{msg:.magenta}] [{id:.blue}] [{source:.blue}] [{spinner:.blue}]: [{0:.green}] [{1:.blue}] [{2}] [{3}] [{4}] [{5}]: [{elapsed_precise:.blue}]";
const FAIL: &'static str = "[{msg:.red}] [{id:.blue}] [{source:.blue}] [{spinner:.blue}]: [{0:.green}] [{1:.red}] [{2}] [{3}] [{4}] [{5}]: [{elapsed_precise:.blue}]";
#[rustfmt::skip]
fn to_style(&self, template: &str) -> ProgressStyle {
let id = self.id.as_i32();
let source = self.source.clone();
let [t0] = self.prev;
let mut sty = ProgressStyle::with_template(template).unwrap();
sty = spinner(sty);
sty = sty.with_key("id", move |_state: &ProgressState, w: &mut dyn Write| write!(w, "{}", id ).unwrap());
sty = sty.with_key("source", move |_state: &ProgressState, w: &mut dyn Write| write!(w, "{}", source).unwrap());
sty = sty.with_key("0", move |_state: &ProgressState, w: &mut dyn Write| write!(w, "{:?}", t0 ).unwrap());
sty = sty.with_key("1", move |_state: &ProgressState, w: &mut dyn Write| write!(w, "Parsing" ).unwrap());
sty = sty.with_key("2", |_state: &ProgressState, w: &mut dyn Write| write!(w, "Storing" ).unwrap());
sty = sty.with_key("3", |_state: &ProgressState, w: &mut dyn Write| write!(w, "Indexing" ).unwrap());
sty = sty.with_key("4", |_state: &ProgressState, w: &mut dyn Write| write!(w, "Syncing" ).unwrap());
sty = sty.with_key("5", |_state: &ProgressState, w: &mut dyn Write| write!(w, "Updating" ).unwrap());
sty
}
}
pub struct Storing {
pub id: DocumentEntryId,
pub source: Source,
pub prev: [Duration; 2],
}
impl Phase for Storing {
const RUN: &'static str = "[{msg:.magenta}] [{id:.blue}] [{source:.blue}] [{spinner:.blue}]: [{0:.green}] [{1:.green}] [{2:.blue}] [{3}] [{4}] [{5}]: [{elapsed_precise:.blue}]";
const FAIL: &'static str = "[{msg:.red}] [{id:.blue}] [{source:.blue}] [{spinner:.blue}]: [{0:.green}] [{1:.green}] [{2:.red}] [{3}] [{4}] [{5}]: [{elapsed_precise:.blue}]";
#[rustfmt::skip]
fn to_style(&self, template: &str) -> ProgressStyle {
let id = self.id.as_i32();
let source = self.source.clone();
let [t0, t1] = self.prev;
let mut sty = ProgressStyle::with_template(template).unwrap();
sty = spinner(sty);
sty = sty.with_key("id", move |_state: &ProgressState, w: &mut dyn Write| write!(w, "{}", id ).unwrap());
sty = sty.with_key("source", move |_state: &ProgressState, w: &mut dyn Write| write!(w, "{}", source).unwrap());
sty = sty.with_key("0", move |_state: &ProgressState, w: &mut dyn Write| write!(w, "{:?}", t0 ).unwrap());
sty = sty.with_key("1", move |_state: &ProgressState, w: &mut dyn Write| write!(w, "{:?}", t1 ).unwrap());
sty = sty.with_key("2", |_state: &ProgressState, w: &mut dyn Write| write!(w, "Storing" ).unwrap());
sty = sty.with_key("3", |_state: &ProgressState, w: &mut dyn Write| write!(w, "Indexing" ).unwrap());
sty = sty.with_key("4", |_state: &ProgressState, w: &mut dyn Write| write!(w, "Syncing" ).unwrap());
sty = sty.with_key("5", |_state: &ProgressState, w: &mut dyn Write| write!(w, "Updating" ).unwrap());
sty
}
}
pub struct Indexing {
pub id: DocumentEntryId,
pub source: Source,
pub prev: [Duration; 3],
}
impl Phase for Indexing {
const RUN: &'static str = "[{msg:.magenta}] [{id:.blue}] [{source:.blue}] [{spinner:.blue}]: [{0:.green}] [{1:.green}] [{2:.green}] [{3:.blue}] [{4}] [{5}]: [{elapsed_precise:.blue}]";
const FAIL: &'static str = "[{msg:.red}] [{id:.blue}] [{source:.blue}] [{spinner:.blue}]: [{0:.green}] [{1:.green}] [{2:.green}] [{3:.red}] [{4}] [{5}]: [{elapsed_precise:.blue}]";
#[rustfmt::skip]
fn to_style(&self, template: &str) -> ProgressStyle {
let id = self.id.as_i32();
let source = self.source.clone();
let [t0, t1, t2] = self.prev;
let mut sty = ProgressStyle::with_template(template).unwrap();
sty = spinner(sty);
sty = sty.with_key("id", move |_state: &ProgressState, w: &mut dyn Write| write!(w, "{}", id ).unwrap());
sty = sty.with_key("source", move |_state: &ProgressState, w: &mut dyn Write| write!(w, "{}", source).unwrap());
sty = sty.with_key("0", move |_state: &ProgressState, w: &mut dyn Write| write!(w, "{:?}", t0 ).unwrap());
sty = sty.with_key("1", move |_state: &ProgressState, w: &mut dyn Write| write!(w, "{:?}", t1 ).unwrap());
sty = sty.with_key("2", move |_state: &ProgressState, w: &mut dyn Write| write!(w, "{:?}", t2 ).unwrap());
sty = sty.with_key("3", |_state: &ProgressState, w: &mut dyn Write| write!(w, "Indexing" ).unwrap());
sty = sty.with_key("4", |_state: &ProgressState, w: &mut dyn Write| write!(w, "Syncing" ).unwrap());
sty = sty.with_key("5", |_state: &ProgressState, w: &mut dyn Write| write!(w, "Updating" ).unwrap());
sty
}
}
pub struct Syncing {
pub id: DocumentEntryId,
pub source: Source,
pub prev: [Duration; 4],
}
impl Phase for Syncing {
const RUN: &'static str = "[{msg:.magenta}] [{id:.blue}] [{source:.blue}] [{spinner:.blue}]: [{0:.green}] [{1:.green}] [{2:.green}] [{3:.green}] [{4:.blue}] [{5}]: [{elapsed_precise:.blue}]";
const FAIL: &'static str = "[{msg:.red}] [{id:.blue}] [{source:.blue}] [{spinner:.blue}]: [{0:.green}] [{1:.green}] [{2:.green}] [{3:.green}] [{4:.red}] [{5}]: [{elapsed_precise:.blue}]";
#[rustfmt::skip]
fn to_style(&self, template: &str) -> ProgressStyle {
let id = self.id.as_i32();
let source = self.source.clone();
let [t0, t1, t2, t3] = self.prev;
let mut sty = ProgressStyle::with_template(template).unwrap();
sty = spinner(sty);
sty = sty.with_key("id", move |_state: &ProgressState, w: &mut dyn Write| write!(w, "{}", id ).unwrap());
sty = sty.with_key("source", move |_state: &ProgressState, w: &mut dyn Write| write!(w, "{}", source).unwrap());
sty = sty.with_key("0", move |_state: &ProgressState, w: &mut dyn Write| write!(w, "{:?}", t0 ).unwrap());
sty = sty.with_key("1", move |_state: &ProgressState, w: &mut dyn Write| write!(w, "{:?}", t1 ).unwrap());
sty = sty.with_key("2", move |_state: &ProgressState, w: &mut dyn Write| write!(w, "{:?}", t2 ).unwrap());
sty = sty.with_key("3", move |_state: &ProgressState, w: &mut dyn Write| write!(w, "{:?}", t3 ).unwrap());
sty = sty.with_key("4", |_state: &ProgressState, w: &mut dyn Write| write!(w, "Syncing" ).unwrap());
sty = sty.with_key("5", |_state: &ProgressState, w: &mut dyn Write| write!(w, "Updating" ).unwrap());
sty
}
}
pub struct Updating {
pub id: DocumentEntryId,
pub source: Source,
pub prev: [Duration; 5],
}
impl Phase for Updating {
const RUN: &'static str = "[{msg:.magenta}] [{id:.blue}] [{source:.blue}] [{spinner:.blue}]: [{0:.green}] [{1:.green}] [{2:.green}] [{3:.green}] [{4:.green}] [{5:.blue}]: [{elapsed_precise:.blue}]";
const FAIL: &'static str = "[{msg:.red}] [{id:.blue}] [{source:.blue}] [{spinner:.blue}]: [{0:.green}] [{1:.green}] [{2:.green}] [{3:.green}] [{4:.green}] [{5:.red}]: [{elapsed_precise:.blue}]";
#[rustfmt::skip]
fn to_style(&self, template: &str) -> ProgressStyle {
let id = self.id.as_i32();
let source = self.source.clone();
let [t0, t1, t2, t3, t4] = self.prev;
let mut sty = ProgressStyle::with_template(template).unwrap();
sty = spinner(sty);
sty = sty.with_key("id", move |_state: &ProgressState, w: &mut dyn Write| write!(w, "{}", id ).unwrap());
sty = sty.with_key("source", move |_state: &ProgressState, w: &mut dyn Write| write!(w, "{}", source).unwrap());
sty = sty.with_key("0", move |_state: &ProgressState, w: &mut dyn Write| write!(w, "{:?}", t0 ).unwrap());
sty = sty.with_key("1", move |_state: &ProgressState, w: &mut dyn Write| write!(w, "{:?}", t1 ).unwrap());
sty = sty.with_key("2", move |_state: &ProgressState, w: &mut dyn Write| write!(w, "{:?}", t2 ).unwrap());
sty = sty.with_key("3", move |_state: &ProgressState, w: &mut dyn Write| write!(w, "{:?}", t3 ).unwrap());
sty = sty.with_key("4", move |_state: &ProgressState, w: &mut dyn Write| write!(w, "{:?}", t4 ).unwrap());
sty = sty.with_key("5", |_state: &ProgressState, w: &mut dyn Write| write!(w, "Updating" ).unwrap());
sty
}
}
pub struct Success {
pub id: DocumentEntryId,
pub source: Source,
pub prev: [Duration; 6],
pub start: Instant,
}
impl Phase for Success {
const RUN: &'static str = "";
const FAIL: &'static str = "";
#[rustfmt::skip]
fn to_style(&self, _: &str) -> ProgressStyle {
let id = self.id.as_i32();
let source = self.source.clone();
let total = self.start.elapsed();
let [t0, t1, t2, t3, t4, t5] = self.prev;
let template = format!("[{{msg:.green}}] [{{id:.green}}] [{{source:.green}}] [{{spinner:.green}}]: [{{0:.{}}}] [{{1:.{}}}] [{{2:.{}}}] [{{3:.{}}}] [{{4:.{}}}] [{{5:.{}}}]: [{{total:.green}}]",
if t0.is_zero() {"white"} else {"green"},
if t1.is_zero() {"white"} else {"green"},
if t2.is_zero() {"white"} else {"green"},
if t3.is_zero() {"white"} else {"green"},
if t4.is_zero() {"white"} else {"green"},
if t5.is_zero() {"white"} else {"green"},
);
let mut sty = ProgressStyle::with_template(&template).unwrap();
sty = spinner(sty);
sty = sty.with_key("id", move |_state: &ProgressState, w: &mut dyn Write| write!(w, "{}", id ).unwrap());
sty = sty.with_key("source", move |_state: &ProgressState, w: &mut dyn Write| write!(w, "{}", source).unwrap());
sty = sty.with_key("total", move |_state: &ProgressState, w: &mut dyn Write| write!(w, "{:?}", total ).unwrap());
if t0.is_zero() {
sty = sty.with_key("0", move |_state: &ProgressState, w: &mut dyn Write| write!(w, "Preparing" ).unwrap());
} else {
sty = sty.with_key("0", move |_state: &ProgressState, w: &mut dyn Write| write!(w, "{:?}", t0 ).unwrap());
}
if t1.is_zero() {
sty = sty.with_key("1", move |_state: &ProgressState, w: &mut dyn Write| write!(w, "N/A" ).unwrap());
} else {
sty = sty.with_key("1", move |_state: &ProgressState, w: &mut dyn Write| write!(w, "{:?}", t1 ).unwrap());
}
if t2.is_zero() {
sty = sty.with_key("2", move |_state: &ProgressState, w: &mut dyn Write| write!(w, "Storing" ).unwrap());
} else {
sty = sty.with_key("2", move |_state: &ProgressState, w: &mut dyn Write| write!(w, "{:?}", t2 ).unwrap());
}
if t3.is_zero() {
sty = sty.with_key("3", move |_state: &ProgressState, w: &mut dyn Write| write!(w, "Indexing" ).unwrap());
} else {
sty = sty.with_key("3", move |_state: &ProgressState, w: &mut dyn Write| write!(w, "{:?}", t3 ).unwrap());
}
if t4.is_zero() {
sty = sty.with_key("4", move |_state: &ProgressState, w: &mut dyn Write| write!(w, "Syncing" ).unwrap());
} else {
sty = sty.with_key("4", move |_state: &ProgressState, w: &mut dyn Write| write!(w, "{:?}", t4 ).unwrap());
}
if t5.is_zero() {
sty = sty.with_key("5", move |_state: &ProgressState, w: &mut dyn Write| write!(w, "Updating" ).unwrap());
} else {
sty = sty.with_key("5", move |_state: &ProgressState, w: &mut dyn Write| write!(w, "{:?}", t5 ).unwrap());
}
sty
}
}
#[inline]
fn spinner(sty: ProgressStyle) -> ProgressStyle {
sty.tick_strings(&[
"▹▹▹▹▹",
"▸▹▹▹▹",
"▹▸▹▹▹",
"▹▹▸▹▹",
"▹▹▹▸▹",
"▹▹▹▹▸",
"▪▪▪▪▪",
])
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment