feat: split into actors
This commit is contained in:
parent
61323484a2
commit
0e123898db
15 changed files with 1070 additions and 275 deletions
|
@ -15,6 +15,7 @@ derive_more = { version = "1.0", features = [
|
||||||
"from",
|
"from",
|
||||||
] }
|
] }
|
||||||
inquire = "0.7"
|
inquire = "0.7"
|
||||||
|
kameo = "0.13"
|
||||||
# kxio = {path = "../kxio/"}
|
# kxio = {path = "../kxio/"}
|
||||||
kxio = "4.0"
|
kxio = "4.0"
|
||||||
reqwest = { version = "0.12" , features = ["multipart", "stream"]}
|
reqwest = { version = "0.12" , features = ["multipart", "stream"]}
|
||||||
|
|
|
@ -1,56 +1,123 @@
|
||||||
//
|
//
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
use kameo::{mailbox::unbounded::UnboundedMailbox, Actor};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
nextcloud::{
|
nextcloud::model::{NextcloudBoardId, NextcloudCardId, NextcloudStackId},
|
||||||
client::DeckClient,
|
on_actor_start, p,
|
||||||
model::{Card, NextcloudBoardId, NextcloudStackId},
|
trello::model::{
|
||||||
},
|
|
||||||
p,
|
|
||||||
trello::{
|
|
||||||
client::TrelloClient,
|
|
||||||
model::{
|
|
||||||
attachment::{TrelloAttachment, TrelloAttachmentId},
|
attachment::{TrelloAttachment, TrelloAttachmentId},
|
||||||
card::TrelloShortCard,
|
card::TrelloCardId,
|
||||||
},
|
|
||||||
},
|
},
|
||||||
FullCtx,
|
FullCtx,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub(super) async fn import_attachment(
|
// pub(super) async fn import_attachment(
|
||||||
ctx: &FullCtx,
|
// ctx: &FullCtx,
|
||||||
|
// nextcloud_board_id: NextcloudBoardId,
|
||||||
|
// nextcloud_stack_id: NextcloudStackId,
|
||||||
|
// trello_card: &TrelloShortCard,
|
||||||
|
// nextcloud_card: &Card,
|
||||||
|
// trello_attachment: &TrelloAttachment,
|
||||||
|
// ) -> color_eyre::Result<()> {
|
||||||
|
// let trello_client: TrelloClient = ctx.trello_client();
|
||||||
|
//
|
||||||
|
// let deck_client: DeckClient = ctx.deck_client();
|
||||||
|
//
|
||||||
|
// p!(ctx.prt, ">> Adding attachment: {}", trello_attachment.name);
|
||||||
|
// // - - - download the attachment from trello
|
||||||
|
// let attachment_path = trello_client
|
||||||
|
// .save_attachment(
|
||||||
|
// &trello_card.id,
|
||||||
|
// &TrelloAttachmentId::new(&trello_attachment.id),
|
||||||
|
// Some(&PathBuf::from(&trello_attachment.id)),
|
||||||
|
// )
|
||||||
|
// .await?;
|
||||||
|
// let attachment_file = ctx.fs.file(&attachment_path);
|
||||||
|
// // - - - upload the attachment to nextcloud card
|
||||||
|
// let attachment = deck_client
|
||||||
|
// .add_attachment_to_card(
|
||||||
|
// nextcloud_board_id,
|
||||||
|
// nextcloud_stack_id,
|
||||||
|
// nextcloud_card.id,
|
||||||
|
// &attachment_file,
|
||||||
|
// )
|
||||||
|
// .await
|
||||||
|
// .result?;
|
||||||
|
// p!(
|
||||||
|
// ctx.prt,
|
||||||
|
// ">> Attachment added: {}:{}",
|
||||||
|
// attachment.id,
|
||||||
|
// attachment.attachment_type,
|
||||||
|
// // attachment.extended_data.mimetype
|
||||||
|
// );
|
||||||
|
// // delete local copy of attachment
|
||||||
|
// attachment_file.remove()?;
|
||||||
|
// Ok(())
|
||||||
|
// }
|
||||||
|
|
||||||
|
pub(crate) struct ImportAttachmentActor {
|
||||||
|
ctx: FullCtx,
|
||||||
nextcloud_board_id: NextcloudBoardId,
|
nextcloud_board_id: NextcloudBoardId,
|
||||||
nextcloud_stack_id: NextcloudStackId,
|
nextcloud_stack_id: NextcloudStackId,
|
||||||
trello_card: &TrelloShortCard,
|
nextcloud_card_id: NextcloudCardId,
|
||||||
nextcloud_card: &Card,
|
trello_card_id: TrelloCardId,
|
||||||
trello_attachment: &TrelloAttachment,
|
trello_attachment: TrelloAttachment,
|
||||||
) -> color_eyre::Result<()> {
|
}
|
||||||
let trello_client: TrelloClient = ctx.trello_client();
|
impl ImportAttachmentActor {
|
||||||
|
pub fn new(
|
||||||
let deck_client: DeckClient = ctx.deck_client();
|
ctx: FullCtx,
|
||||||
|
nextcloud_board_id: NextcloudBoardId,
|
||||||
p!(ctx.prt, ">> Adding attachment: {}", trello_attachment.name);
|
nextcloud_stack_id: NextcloudStackId,
|
||||||
// - - - download the attachment from trello
|
nextcloud_card_id: NextcloudCardId,
|
||||||
let attachment_path = trello_client
|
trello_card_id: TrelloCardId,
|
||||||
.save_attachment(
|
trello_attachment: TrelloAttachment,
|
||||||
&trello_card.id,
|
) -> Self {
|
||||||
&TrelloAttachmentId::new(&trello_attachment.id),
|
Self {
|
||||||
Some(&PathBuf::from(&trello_attachment.id)),
|
ctx,
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
let attachment_file = ctx.fs.file(&attachment_path);
|
|
||||||
// - - - upload the attachment to nextcloud card
|
|
||||||
let attachment = deck_client
|
|
||||||
.add_attachment_to_card(
|
|
||||||
nextcloud_board_id,
|
nextcloud_board_id,
|
||||||
nextcloud_stack_id,
|
nextcloud_stack_id,
|
||||||
nextcloud_card.id,
|
nextcloud_card_id,
|
||||||
|
trello_card_id,
|
||||||
|
trello_attachment,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Actor for ImportAttachmentActor {
|
||||||
|
type Mailbox = UnboundedMailbox<Self>;
|
||||||
|
|
||||||
|
on_actor_start!(this, actor_ref, {
|
||||||
|
p!(
|
||||||
|
this.ctx.prt,
|
||||||
|
">> Adding attachment: {}",
|
||||||
|
this.trello_attachment.name
|
||||||
|
);
|
||||||
|
// - - - download the attachment from trello
|
||||||
|
let attachment_path = this
|
||||||
|
.ctx
|
||||||
|
.trello_client()
|
||||||
|
.save_attachment(
|
||||||
|
&this.trello_card_id,
|
||||||
|
&TrelloAttachmentId::new(&this.trello_attachment.id),
|
||||||
|
Some(&PathBuf::from(&this.trello_attachment.id)),
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
let attachment_file = this.ctx.fs.file(&attachment_path);
|
||||||
|
// - - - upload the attachment to nextcloud card
|
||||||
|
let attachment = this
|
||||||
|
.ctx
|
||||||
|
.deck_client()
|
||||||
|
.add_attachment_to_card(
|
||||||
|
this.nextcloud_board_id,
|
||||||
|
this.nextcloud_stack_id,
|
||||||
|
this.nextcloud_card_id,
|
||||||
&attachment_file,
|
&attachment_file,
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.result?;
|
.result?;
|
||||||
p!(
|
p!(
|
||||||
ctx.prt,
|
this.ctx.prt,
|
||||||
">> Attachment added: {}:{}",
|
">> Attachment added: {}:{}",
|
||||||
attachment.id,
|
attachment.id,
|
||||||
attachment.attachment_type,
|
attachment.attachment_type,
|
||||||
|
@ -58,5 +125,7 @@ pub(super) async fn import_attachment(
|
||||||
);
|
);
|
||||||
// delete local copy of attachment
|
// delete local copy of attachment
|
||||||
attachment_file.remove()?;
|
attachment_file.remove()?;
|
||||||
Ok(())
|
|
||||||
|
Ok(actor_ref.stop_gracefully().await?)
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,75 +1,194 @@
|
||||||
//
|
//
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
use kameo::{
|
||||||
|
actor::{ActorID, ActorRef},
|
||||||
|
error::ActorStopReason,
|
||||||
|
mailbox::unbounded::UnboundedMailbox,
|
||||||
|
Actor,
|
||||||
|
};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
import::{attachment, label},
|
import::{attachment::ImportAttachmentActor, label::ImportLabelActor, labels::LabelsActor},
|
||||||
nextcloud::{
|
nextcloud::{
|
||||||
client::DeckClient,
|
client::DeckClient,
|
||||||
model::{
|
model::{NextcloudBoardId, NextcloudCardDescription, NextcloudCardTitle, NextcloudStackId},
|
||||||
Label, NextcloudBoardId, NextcloudCardDescription, NextcloudCardTitle,
|
|
||||||
NextcloudLabelTitle, NextcloudStackId,
|
|
||||||
},
|
},
|
||||||
},
|
on_actor_link_died, on_actor_start, p, spawn_in_thread,
|
||||||
p,
|
|
||||||
trello::{client::TrelloClient, model::card::TrelloShortCard},
|
trello::{client::TrelloClient, model::card::TrelloShortCard},
|
||||||
FullCtx,
|
FullCtx,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub(super) async fn import_card(
|
// pub(super) async fn import_card(
|
||||||
ctx: &FullCtx,
|
// ctx: &FullCtx,
|
||||||
|
// nextcloud_board_id: NextcloudBoardId,
|
||||||
|
// nextcloud_labels: &mut HashMap<NextcloudLabelTitle, Label>,
|
||||||
|
// nextcloud_stack_id: NextcloudStackId,
|
||||||
|
// trello_card: &TrelloShortCard,
|
||||||
|
// ) -> color_eyre::Result<()> {
|
||||||
|
// let trello_client: TrelloClient = ctx.trello_client();
|
||||||
|
//
|
||||||
|
// let deck_client: DeckClient = ctx.deck_client();
|
||||||
|
//
|
||||||
|
// p!(ctx.prt, "> Importing card: {}", trello_card.name);
|
||||||
|
// // - - create a nextcloud card
|
||||||
|
// let title = NextcloudCardTitle::from(&trello_card.name);
|
||||||
|
// let desc: Option<NextcloudCardDescription> = match trello_card.desc.len() {
|
||||||
|
// 0 => None,
|
||||||
|
// _ => Some(NextcloudCardDescription::from(&trello_card.desc)),
|
||||||
|
// };
|
||||||
|
// let nextcloud_card = deck_client
|
||||||
|
// .create_card(
|
||||||
|
// nextcloud_board_id,
|
||||||
|
// nextcloud_stack_id,
|
||||||
|
// &title,
|
||||||
|
// desc.as_ref(),
|
||||||
|
// )
|
||||||
|
// .await
|
||||||
|
// .result?;
|
||||||
|
// // - - for each label on the trello card
|
||||||
|
// for trello_label in trello_card.labels.iter() {
|
||||||
|
// labels::import_label(
|
||||||
|
// ctx,
|
||||||
|
// nextcloud_board_id,
|
||||||
|
// nextcloud_labels,
|
||||||
|
// nextcloud_stack_id,
|
||||||
|
// &nextcloud_card,
|
||||||
|
// trello_label,
|
||||||
|
// )
|
||||||
|
// .await?;
|
||||||
|
// }
|
||||||
|
// // - - for each attachment on the trello card
|
||||||
|
// let attachments = trello_client
|
||||||
|
// .card(&trello_card.id)
|
||||||
|
// .await
|
||||||
|
// .result?
|
||||||
|
// .attachments;
|
||||||
|
// for trello_attachment in attachments.iter() {
|
||||||
|
// attachment::import_attachment(
|
||||||
|
// ctx,
|
||||||
|
// nextcloud_board_id,
|
||||||
|
// nextcloud_stack_id,
|
||||||
|
// trello_card,
|
||||||
|
// &nextcloud_card,
|
||||||
|
// trello_attachment,
|
||||||
|
// )
|
||||||
|
// .await?;
|
||||||
|
// }
|
||||||
|
// Ok(())
|
||||||
|
// }
|
||||||
|
|
||||||
|
pub(crate) struct ImportCardActor {
|
||||||
|
ctx: FullCtx,
|
||||||
|
trello_card: TrelloShortCard,
|
||||||
nextcloud_board_id: NextcloudBoardId,
|
nextcloud_board_id: NextcloudBoardId,
|
||||||
nextcloud_labels: &mut HashMap<NextcloudLabelTitle, Label>,
|
|
||||||
nextcloud_stack_id: NextcloudStackId,
|
nextcloud_stack_id: NextcloudStackId,
|
||||||
trello_card: &TrelloShortCard,
|
labels_actor_ref: ActorRef<LabelsActor>,
|
||||||
) -> color_eyre::Result<()> {
|
|
||||||
let trello_client: TrelloClient = ctx.trello_client();
|
|
||||||
|
|
||||||
let deck_client: DeckClient = ctx.deck_client();
|
labels_children: HashMap<ActorID, ActorRef<ImportLabelActor>>,
|
||||||
|
attachments_children: HashMap<ActorID, ActorRef<ImportAttachmentActor>>,
|
||||||
|
}
|
||||||
|
impl ImportCardActor {
|
||||||
|
pub(crate) fn new(
|
||||||
|
ctx: FullCtx,
|
||||||
|
trello_card: TrelloShortCard,
|
||||||
|
nextcloud_board_id: NextcloudBoardId,
|
||||||
|
nextcloud_stack_id: NextcloudStackId,
|
||||||
|
labels_actor_ref: ActorRef<LabelsActor>,
|
||||||
|
) -> Self {
|
||||||
|
Self {
|
||||||
|
ctx,
|
||||||
|
trello_card,
|
||||||
|
nextcloud_board_id,
|
||||||
|
nextcloud_stack_id,
|
||||||
|
labels_actor_ref,
|
||||||
|
|
||||||
p!(ctx.prt, "> Importing card: {}", trello_card.name);
|
labels_children: Default::default(),
|
||||||
|
attachments_children: Default::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Actor for ImportCardActor {
|
||||||
|
type Mailbox = UnboundedMailbox<Self>;
|
||||||
|
|
||||||
|
on_actor_start!(this, actor_ref, {
|
||||||
|
let trello_client: TrelloClient = this.ctx.trello_client();
|
||||||
|
|
||||||
|
let deck_client: DeckClient = this.ctx.deck_client();
|
||||||
|
|
||||||
|
p!(this.ctx.prt, "> Importing card: {}", this.trello_card.name);
|
||||||
// - - create a nextcloud card
|
// - - create a nextcloud card
|
||||||
let title = NextcloudCardTitle::from(&trello_card.name);
|
let title = NextcloudCardTitle::from(&this.trello_card.name);
|
||||||
let desc: Option<NextcloudCardDescription> = match trello_card.desc.len() {
|
let desc: Option<NextcloudCardDescription> = match this.trello_card.desc.len() {
|
||||||
0 => None,
|
0 => None,
|
||||||
_ => Some(NextcloudCardDescription::from(&trello_card.desc)),
|
_ => Some(NextcloudCardDescription::from(&this.trello_card.desc)),
|
||||||
};
|
};
|
||||||
let nextcloud_card = deck_client
|
let nextcloud_card = deck_client
|
||||||
.create_card(
|
.create_card(
|
||||||
nextcloud_board_id,
|
this.nextcloud_board_id,
|
||||||
nextcloud_stack_id,
|
this.nextcloud_stack_id,
|
||||||
&title,
|
&title,
|
||||||
desc.as_ref(),
|
desc.as_ref(),
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.result?;
|
.result?;
|
||||||
|
|
||||||
// - - for each label on the trello card
|
// - - for each label on the trello card
|
||||||
for trello_label in trello_card.labels.iter() {
|
let mut labels = vec![];
|
||||||
label::import_label(
|
std::mem::swap(&mut this.trello_card.labels, &mut labels);
|
||||||
ctx,
|
for trello_label in labels.into_iter() {
|
||||||
nextcloud_board_id,
|
let child = spawn_in_thread!(
|
||||||
nextcloud_labels,
|
actor_ref,
|
||||||
nextcloud_stack_id,
|
ImportLabelActor::new(
|
||||||
&nextcloud_card,
|
this.ctx.clone(),
|
||||||
|
this.nextcloud_board_id,
|
||||||
|
this.nextcloud_stack_id,
|
||||||
|
nextcloud_card.id,
|
||||||
trello_label,
|
trello_label,
|
||||||
|
this.labels_actor_ref.clone(),
|
||||||
)
|
)
|
||||||
.await?;
|
);
|
||||||
|
this.labels_children.insert(child.id(), child.clone());
|
||||||
}
|
}
|
||||||
// - - for each attachment on the trello card
|
|
||||||
let attachments = trello_client
|
let attachments = trello_client
|
||||||
.card(&trello_card.id)
|
.card(&this.trello_card.id)
|
||||||
.await
|
.await
|
||||||
.result?
|
.result?
|
||||||
.attachments;
|
.attachments;
|
||||||
for trello_attachment in attachments.iter() {
|
for trello_attachment in attachments.into_iter() {
|
||||||
attachment::import_attachment(
|
let child = spawn_in_thread!(
|
||||||
ctx,
|
actor_ref,
|
||||||
nextcloud_board_id,
|
ImportAttachmentActor::new(
|
||||||
nextcloud_stack_id,
|
this.ctx.clone(),
|
||||||
trello_card,
|
this.nextcloud_board_id,
|
||||||
&nextcloud_card,
|
this.nextcloud_stack_id,
|
||||||
|
nextcloud_card.id,
|
||||||
|
this.trello_card.id.clone(),
|
||||||
trello_attachment,
|
trello_attachment,
|
||||||
)
|
)
|
||||||
.await?;
|
);
|
||||||
|
this.attachments_children.insert(child.id(), child.clone());
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
});
|
||||||
|
|
||||||
|
on_actor_link_died!(this, actor_ref, id, reason, {
|
||||||
|
match reason {
|
||||||
|
ActorStopReason::Normal => {
|
||||||
|
this.labels_children.remove(&id);
|
||||||
|
this.attachments_children.remove(&id);
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
return Ok(Some(reason));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if this.labels_children.is_empty() && this.attachments_children.is_empty() {
|
||||||
|
return Ok(Some(ActorStopReason::Normal));
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(None)
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,67 +1,65 @@
|
||||||
//
|
//
|
||||||
use std::collections::HashMap;
|
use kameo::{actor::ActorRef, mailbox::unbounded::UnboundedMailbox, Actor};
|
||||||
|
|
||||||
use all_colors::get_color_hex;
|
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
nextcloud::{
|
ask,
|
||||||
client::DeckClient,
|
import::{labels, labels::LabelsActor},
|
||||||
model::{
|
nextcloud::model::{NextcloudBoardId, NextcloudCardId, NextcloudStackId},
|
||||||
Card, Label, NextcloudBoardId, NextcloudLabelColour, NextcloudLabelTitle,
|
on_actor_start,
|
||||||
NextcloudStackId,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
p,
|
|
||||||
trello::model::label::TrelloLabel,
|
trello::model::label::TrelloLabel,
|
||||||
FullCtx,
|
FullCtx,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub(super) async fn import_label(
|
pub(crate) struct ImportLabelActor {
|
||||||
ctx: &FullCtx,
|
ctx: FullCtx,
|
||||||
nextcloud_board_id: NextcloudBoardId,
|
nextcloud_board_id: NextcloudBoardId,
|
||||||
nextcloud_labels: &mut HashMap<NextcloudLabelTitle, Label>,
|
|
||||||
nextcloud_stack_id: NextcloudStackId,
|
nextcloud_stack_id: NextcloudStackId,
|
||||||
nextcloud_card: &Card,
|
nextcloud_card_id: NextcloudCardId,
|
||||||
trello_label: &TrelloLabel,
|
trello_label: TrelloLabel,
|
||||||
) -> color_eyre::Result<()> {
|
labels_actor_ref: ActorRef<LabelsActor>,
|
||||||
let deck_client: DeckClient = ctx.deck_client();
|
}
|
||||||
|
impl ImportLabelActor {
|
||||||
p!(
|
pub(crate) fn new(
|
||||||
ctx.prt,
|
ctx: FullCtx,
|
||||||
">> Adding label: {} ({})",
|
nextcloud_board_id: NextcloudBoardId,
|
||||||
trello_label.name,
|
nextcloud_stack_id: NextcloudStackId,
|
||||||
trello_label.color
|
nextcloud_card_id: NextcloudCardId,
|
||||||
);
|
trello_label: TrelloLabel,
|
||||||
// - - - find the equivalent label in nextcloud
|
labels_actor_ref: ActorRef<LabelsActor>,
|
||||||
let nextcloud_label: &Label =
|
) -> Self {
|
||||||
match nextcloud_labels.get(&NextcloudLabelTitle::new(trello_label.name.as_ref())) {
|
Self {
|
||||||
Some(label) => label,
|
ctx,
|
||||||
None => {
|
|
||||||
p!(ctx.prt, ">> Label not found in nextcloud board, creating");
|
|
||||||
|
|
||||||
let label = deck_client
|
|
||||||
.create_label(
|
|
||||||
nextcloud_board_id,
|
|
||||||
&NextcloudLabelTitle::new(trello_label.name.as_ref()),
|
|
||||||
&NextcloudLabelColour::new(get_color_hex(trello_label.color.as_ref())),
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
.result?;
|
|
||||||
nextcloud_labels.insert(label.title.clone(), label);
|
|
||||||
nextcloud_labels
|
|
||||||
.get(&NextcloudLabelTitle::new(trello_label.name.as_ref()))
|
|
||||||
.expect("label was just inserted")
|
|
||||||
}
|
|
||||||
};
|
|
||||||
// - - - add the label to the nextcloud card
|
|
||||||
deck_client
|
|
||||||
.add_label_to_card(
|
|
||||||
nextcloud_board_id,
|
nextcloud_board_id,
|
||||||
nextcloud_stack_id,
|
nextcloud_stack_id,
|
||||||
nextcloud_card.id,
|
nextcloud_card_id,
|
||||||
nextcloud_label.id,
|
trello_label,
|
||||||
|
labels_actor_ref,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Actor for ImportLabelActor {
|
||||||
|
type Mailbox = UnboundedMailbox<Self>;
|
||||||
|
|
||||||
|
on_actor_start!(this, actor_ref, {
|
||||||
|
let label_id = ask!(
|
||||||
|
this.labels_actor_ref,
|
||||||
|
labels::LookupNextcloudLabelId {
|
||||||
|
trello_name: this.trello_label.name.clone(),
|
||||||
|
trello_color: this.trello_label.color.clone()
|
||||||
|
}
|
||||||
|
)?;
|
||||||
|
// - - - add the label to the nextcloud card
|
||||||
|
this.ctx
|
||||||
|
.deck_client()
|
||||||
|
.add_label_to_card(
|
||||||
|
this.nextcloud_board_id,
|
||||||
|
this.nextcloud_stack_id,
|
||||||
|
this.nextcloud_card_id,
|
||||||
|
label_id,
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.result?;
|
.result?;
|
||||||
Ok(())
|
|
||||||
|
Ok(actor_ref.stop_gracefully().await?)
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
142
src/import/labels.rs
Normal file
142
src/import/labels.rs
Normal file
|
@ -0,0 +1,142 @@
|
||||||
|
//
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
use all_colors::get_color_hex;
|
||||||
|
use kameo::{
|
||||||
|
mailbox::unbounded::UnboundedMailbox,
|
||||||
|
message::{Context, Message},
|
||||||
|
Actor,
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
nextcloud::model::{
|
||||||
|
NextcloudBoardId, NextcloudLabelColour, NextcloudLabelId, NextcloudLabelTitle,
|
||||||
|
},
|
||||||
|
on_actor_start, p,
|
||||||
|
trello::model::label::{TrelloLabelColor, TrelloLabelName},
|
||||||
|
FullCtx,
|
||||||
|
};
|
||||||
|
|
||||||
|
// pub(super) async fn import_label(
|
||||||
|
// ctx: &FullCtx,
|
||||||
|
// nextcloud_board_id: NextcloudBoardId,
|
||||||
|
// nextcloud_labels: &mut HashMap<NextcloudLabelTitle, Label>,
|
||||||
|
// nextcloud_stack_id: NextcloudStackId,
|
||||||
|
// nextcloud_card: &Card,
|
||||||
|
// trello_label: &TrelloLabel,
|
||||||
|
// ) -> color_eyre::Result<()> {
|
||||||
|
// let deck_client: DeckClient = ctx.deck_client();
|
||||||
|
//
|
||||||
|
// p!(
|
||||||
|
// ctx.prt,
|
||||||
|
// ">> Adding label: {} ({})",
|
||||||
|
// trello_label.name,
|
||||||
|
// trello_label.color
|
||||||
|
// );
|
||||||
|
// // - - - find the equivalent label in nextcloud
|
||||||
|
// let nextcloud_label: &Label =
|
||||||
|
// match nextcloud_labels.get(&NextcloudLabelTitle::new(trello_label.name.as_ref())) {
|
||||||
|
// Some(label) => label,
|
||||||
|
// None => {
|
||||||
|
// p!(ctx.prt, ">> Label not found in nextcloud board, creating");
|
||||||
|
//
|
||||||
|
// let label = deck_client
|
||||||
|
// .create_label(
|
||||||
|
// nextcloud_board_id,
|
||||||
|
// &NextcloudLabelTitle::new(trello_label.name.as_ref()),
|
||||||
|
// &NextcloudLabelColour::new(get_color_hex(trello_label.color.as_ref())),
|
||||||
|
// )
|
||||||
|
// .await
|
||||||
|
// .result?;
|
||||||
|
// nextcloud_labels.insert(label.title.clone(), label);
|
||||||
|
// nextcloud_labels
|
||||||
|
// .get(&NextcloudLabelTitle::new(trello_label.name.as_ref()))
|
||||||
|
// .expect("label was just inserted")
|
||||||
|
// }
|
||||||
|
// };
|
||||||
|
// // - - - add the label to the nextcloud card
|
||||||
|
// deck_client
|
||||||
|
// .add_label_to_card(
|
||||||
|
// nextcloud_board_id,
|
||||||
|
// nextcloud_stack_id,
|
||||||
|
// nextcloud_card.id,
|
||||||
|
// nextcloud_label.id,
|
||||||
|
// )
|
||||||
|
// .await
|
||||||
|
// .result?;
|
||||||
|
// Ok(())
|
||||||
|
// }
|
||||||
|
|
||||||
|
pub(super) struct LabelsActor {
|
||||||
|
ctx: FullCtx,
|
||||||
|
nextcloud_board_id: NextcloudBoardId,
|
||||||
|
lookup: HashMap<NextcloudLabelTitle, NextcloudLabelId>,
|
||||||
|
}
|
||||||
|
impl LabelsActor {
|
||||||
|
pub(super) fn new(ctx: FullCtx, nextcloud_board_id: NextcloudBoardId) -> Self {
|
||||||
|
Self {
|
||||||
|
ctx,
|
||||||
|
nextcloud_board_id,
|
||||||
|
lookup: Default::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Actor for LabelsActor {
|
||||||
|
type Mailbox = UnboundedMailbox<Self>;
|
||||||
|
|
||||||
|
on_actor_start!(this, actor_ref, {
|
||||||
|
this.ctx
|
||||||
|
.deck_client()
|
||||||
|
.get_board(this.nextcloud_board_id)
|
||||||
|
.await
|
||||||
|
.result?
|
||||||
|
.labels
|
||||||
|
.iter()
|
||||||
|
.for_each(|l| {
|
||||||
|
this.lookup.insert(l.title.clone(), l.id);
|
||||||
|
});
|
||||||
|
Ok(())
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) struct LookupNextcloudLabelId {
|
||||||
|
pub(crate) trello_name: TrelloLabelName,
|
||||||
|
pub(crate) trello_color: TrelloLabelColor,
|
||||||
|
}
|
||||||
|
impl Message<LookupNextcloudLabelId> for LabelsActor {
|
||||||
|
type Reply = Result<NextcloudLabelId, kxio::Error>;
|
||||||
|
async fn handle(
|
||||||
|
&mut self,
|
||||||
|
msg: LookupNextcloudLabelId,
|
||||||
|
_ctx: Context<'_, Self, Self::Reply>,
|
||||||
|
) -> Result<NextcloudLabelId, kxio::Error> {
|
||||||
|
let nextcloud_label_id = match self
|
||||||
|
.lookup
|
||||||
|
.get(&NextcloudLabelTitle::new(msg.trello_name.as_ref()))
|
||||||
|
{
|
||||||
|
Some(nextcloud_label_id) => nextcloud_label_id,
|
||||||
|
None => {
|
||||||
|
p!(
|
||||||
|
self.ctx.prt,
|
||||||
|
">> Label not found in nextcloud board, creating"
|
||||||
|
);
|
||||||
|
|
||||||
|
let label = self
|
||||||
|
.ctx
|
||||||
|
.deck_client()
|
||||||
|
.create_label(
|
||||||
|
self.nextcloud_board_id,
|
||||||
|
&NextcloudLabelTitle::new(msg.trello_name.as_ref()),
|
||||||
|
&NextcloudLabelColour::new(get_color_hex(msg.trello_color.as_ref())),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.result?;
|
||||||
|
self.lookup.insert(label.title.clone(), label.id);
|
||||||
|
self.lookup
|
||||||
|
.get(&NextcloudLabelTitle::new(msg.trello_name.as_ref()))
|
||||||
|
.expect("label was just inserted")
|
||||||
|
}
|
||||||
|
};
|
||||||
|
Ok(*nextcloud_label_id)
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,11 +1,12 @@
|
||||||
//
|
//
|
||||||
use std::collections::HashMap;
|
|
||||||
|
|
||||||
use color_eyre::eyre::eyre;
|
use color_eyre::eyre::eyre;
|
||||||
|
use kameo::actor::spawn_in_thread;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
f,
|
f,
|
||||||
nextcloud::model::{Label, NextcloudBoardTitle, NextcloudLabelTitle},
|
import::{labels::LabelsActor, stacks::ImportStacksActor, supervisor::Supervisor},
|
||||||
|
nextcloud::model::NextcloudBoardTitle,
|
||||||
|
spawn_in_thread,
|
||||||
trello::model::board::{TrelloBoardId, TrelloBoardName, TrelloBoards},
|
trello::model::board::{TrelloBoardId, TrelloBoardName, TrelloBoards},
|
||||||
FullCtx,
|
FullCtx,
|
||||||
};
|
};
|
||||||
|
@ -13,7 +14,11 @@ use crate::{
|
||||||
mod attachment;
|
mod attachment;
|
||||||
mod card;
|
mod card;
|
||||||
mod label;
|
mod label;
|
||||||
|
mod labels;
|
||||||
|
mod spawn;
|
||||||
mod stack;
|
mod stack;
|
||||||
|
mod stacks;
|
||||||
|
mod supervisor;
|
||||||
|
|
||||||
pub(crate) async fn run(ctx: &FullCtx) -> color_eyre::Result<()> {
|
pub(crate) async fn run(ctx: &FullCtx) -> color_eyre::Result<()> {
|
||||||
// get list of trello boards
|
// get list of trello boards
|
||||||
|
@ -72,45 +77,26 @@ pub(crate) async fn run(ctx: &FullCtx) -> color_eyre::Result<()> {
|
||||||
.find(|b| b.title == nextcloud_board_name)
|
.find(|b| b.title == nextcloud_board_name)
|
||||||
.map(|b| b.id)
|
.map(|b| b.id)
|
||||||
.expect("find selected board");
|
.expect("find selected board");
|
||||||
// get list of nextcloud stacks in the selected board
|
|
||||||
let nextcloud_stacks = deck_client.get_stacks(nextcloud_board_id).await.result?;
|
|
||||||
|
|
||||||
stack::create_any_missing_stacks(
|
let supervisor = spawn_in_thread(Supervisor);
|
||||||
ctx,
|
|
||||||
&selected_trello_stack_names,
|
let labels_actor_ref = spawn_in_thread!(
|
||||||
|
supervisor,
|
||||||
|
LabelsActor::new(ctx.clone(), nextcloud_board_id)
|
||||||
|
);
|
||||||
|
|
||||||
|
let _main = spawn_in_thread!(
|
||||||
|
supervisor,
|
||||||
|
ImportStacksActor::new(
|
||||||
|
ctx.clone(),
|
||||||
|
trello_stacks.clone(),
|
||||||
|
selected_trello_stack_names.into_iter().cloned().collect(),
|
||||||
nextcloud_board_id,
|
nextcloud_board_id,
|
||||||
nextcloud_stacks,
|
labels_actor_ref.clone(),
|
||||||
)
|
)
|
||||||
.await?;
|
);
|
||||||
|
|
||||||
// - get the list of nextcloud stacks again (with new stack ids)
|
supervisor.wait_for_stop().await;
|
||||||
let nextcloud_stacks = deck_client.get_stacks(nextcloud_board_id).await.result?;
|
|
||||||
|
|
||||||
let mut nextcloud_labels: HashMap<NextcloudLabelTitle, Label> = deck_client
|
|
||||||
.get_board(nextcloud_board_id)
|
|
||||||
.await
|
|
||||||
.result?
|
|
||||||
.labels
|
|
||||||
.iter()
|
|
||||||
.map(|l| (l.title.clone(), l.clone()))
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
// for each selected trello stack
|
|
||||||
for selected_trello_stack in trello_stacks
|
|
||||||
.iter()
|
|
||||||
.filter(|s| selected_trello_stack_names.contains(&s.name.as_ref()))
|
|
||||||
.collect::<Vec<_>>()
|
|
||||||
.into_iter()
|
|
||||||
{
|
|
||||||
stack::import_stack(
|
|
||||||
ctx,
|
|
||||||
nextcloud_board_id,
|
|
||||||
&nextcloud_stacks,
|
|
||||||
&mut nextcloud_labels,
|
|
||||||
selected_trello_stack,
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
25
src/import/spawn.rs
Normal file
25
src/import/spawn.rs
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
//
|
||||||
|
/// spawns a new actor and sets up bi-directional links
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! spawn {
|
||||||
|
($parent:expr, $actor:expr) => {{
|
||||||
|
tracing::debug!("spawning : {}", stringify!($actor));
|
||||||
|
let new_actor_ref = kameo::spawn($actor);
|
||||||
|
new_actor_ref.link(&$parent).await;
|
||||||
|
$parent.link(&new_actor_ref).await;
|
||||||
|
tracing::debug!("spawned : {}", stringify!($actor));
|
||||||
|
new_actor_ref
|
||||||
|
}};
|
||||||
|
}
|
||||||
|
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! spawn_in_thread {
|
||||||
|
($parent:expr, $actor:expr) => {{
|
||||||
|
tracing::debug!("spawning in thread : {}", stringify!($actor));
|
||||||
|
let new_actor_ref = kameo::actor::spawn_in_thread($actor);
|
||||||
|
new_actor_ref.link(&$parent).await;
|
||||||
|
$parent.link(&new_actor_ref).await;
|
||||||
|
tracing::debug!("spawned in thread : {}", stringify!($actor));
|
||||||
|
new_actor_ref
|
||||||
|
}};
|
||||||
|
}
|
|
@ -1,13 +1,17 @@
|
||||||
//
|
//
|
||||||
use std::{collections::HashMap, ops::Deref};
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
use kameo::{
|
||||||
|
actor::{ActorID, ActorRef},
|
||||||
|
error::ActorStopReason,
|
||||||
|
mailbox::unbounded::UnboundedMailbox,
|
||||||
|
Actor,
|
||||||
|
};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
import::card,
|
import::{card::ImportCardActor, labels::LabelsActor},
|
||||||
nextcloud::{
|
nextcloud::model::{NextcloudBoardId, NextcloudStackId},
|
||||||
client::DeckClient,
|
on_actor_link_died, on_actor_start, spawn_in_thread,
|
||||||
model::{Label, NextcloudBoardId, NextcloudLabelTitle, Stack},
|
|
||||||
},
|
|
||||||
p,
|
|
||||||
trello::{
|
trello::{
|
||||||
client::TrelloClient,
|
client::TrelloClient,
|
||||||
model::list::{TrelloList, TrelloListId},
|
model::list::{TrelloList, TrelloListId},
|
||||||
|
@ -15,68 +19,74 @@ use crate::{
|
||||||
FullCtx,
|
FullCtx,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub(super) async fn create_any_missing_stacks(
|
pub(super) struct ImportStackActor {
|
||||||
ctx: &FullCtx,
|
ctx: FullCtx,
|
||||||
selected_trello_stack_names: &Vec<&String>,
|
trello_stack: TrelloList,
|
||||||
nextcloud_board_id: NextcloudBoardId,
|
nextcloud_board_id: NextcloudBoardId,
|
||||||
nextcloud_stacks: Vec<Stack>,
|
nextcloud_stack_id: NextcloudStackId,
|
||||||
) -> color_eyre::Result<()> {
|
labels_actor_ref: ActorRef<LabelsActor>,
|
||||||
let deck_client: DeckClient = ctx.deck_client();
|
children: HashMap<ActorID, ActorRef<ImportCardActor>>,
|
||||||
|
|
||||||
// identify any stacks by name from those selected in trello that are missing in nextcloud
|
|
||||||
let missing_stack_names = selected_trello_stack_names
|
|
||||||
.iter()
|
|
||||||
.filter(|s| !nextcloud_stacks.iter().any(|ns| ns.title.deref() == **s))
|
|
||||||
.cloned()
|
|
||||||
.collect::<Vec<_>>();
|
|
||||||
if !missing_stack_names.is_empty() {
|
|
||||||
crate::p!(ctx.prt, "Missing stacks: {:?}", missing_stack_names);
|
|
||||||
// create any missing stacks in nextcloud
|
|
||||||
// for each missing stack
|
|
||||||
for missing_stack_name in missing_stack_names.into_iter() {
|
|
||||||
// - create the stack
|
|
||||||
let stack = deck_client
|
|
||||||
.create_stack(nextcloud_board_id, &missing_stack_name.clone().into())
|
|
||||||
.await
|
|
||||||
.result?;
|
|
||||||
p!(ctx.prt, "Created stack: {}", stack.title);
|
|
||||||
}
|
|
||||||
p!(ctx.prt, "Stacks created");
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
impl ImportStackActor {
|
||||||
pub(super) async fn import_stack(
|
pub(crate) fn new(
|
||||||
ctx: &FullCtx,
|
ctx: FullCtx,
|
||||||
|
trello_stack: TrelloList,
|
||||||
nextcloud_board_id: NextcloudBoardId,
|
nextcloud_board_id: NextcloudBoardId,
|
||||||
nextcloud_stacks: &Vec<Stack>,
|
nextcloud_stack_id: NextcloudStackId,
|
||||||
nextcloud_labels: &mut HashMap<NextcloudLabelTitle, Label>,
|
labels_actor_ref: ActorRef<LabelsActor>,
|
||||||
selected_trello_stack: &TrelloList,
|
) -> Self {
|
||||||
) -> color_eyre::Result<()> {
|
Self {
|
||||||
let trello_client: TrelloClient = ctx.trello_client();
|
ctx,
|
||||||
|
trello_stack,
|
||||||
|
nextcloud_board_id,
|
||||||
|
nextcloud_stack_id,
|
||||||
|
labels_actor_ref,
|
||||||
|
children: Default::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Actor for ImportStackActor {
|
||||||
|
type Mailbox = UnboundedMailbox<Self>;
|
||||||
|
|
||||||
p!(ctx.prt, "Importing stack: {}", selected_trello_stack.name);
|
on_actor_start!(this, actor_ref, {
|
||||||
let nextcloud_stack_id = nextcloud_stacks
|
let trello_client: TrelloClient = this.ctx.trello_client();
|
||||||
.iter()
|
|
||||||
.find(|ns| ns.title.deref() == selected_trello_stack.name.as_ref())
|
crate::p!(this.ctx.prt, "Importing stack: {}", this.trello_stack.name);
|
||||||
.map(|ns| ns.id)
|
|
||||||
.expect("find nextcloud stack");
|
|
||||||
// - get the list of trello cards in the stack
|
// - get the list of trello cards in the stack
|
||||||
let trello_cards = trello_client
|
let trello_cards = trello_client
|
||||||
.list_cards(&TrelloListId::new(selected_trello_stack.id.as_ref()))
|
.list_cards(&TrelloListId::new(this.trello_stack.id.as_ref()))
|
||||||
.await
|
.await
|
||||||
.result?;
|
.result?;
|
||||||
|
|
||||||
// - for each card in the trello stack
|
// - for each card in the trello stack
|
||||||
for trello_card in trello_cards.into_iter() {
|
for trello_card in trello_cards.into_iter() {
|
||||||
card::import_card(
|
let child: ActorRef<ImportCardActor> = spawn_in_thread!(
|
||||||
ctx,
|
actor_ref.clone(),
|
||||||
nextcloud_board_id,
|
ImportCardActor::new(
|
||||||
nextcloud_labels,
|
this.ctx.clone(),
|
||||||
nextcloud_stack_id,
|
trello_card,
|
||||||
&trello_card,
|
this.nextcloud_board_id,
|
||||||
|
this.nextcloud_stack_id,
|
||||||
|
this.labels_actor_ref.clone(),
|
||||||
)
|
)
|
||||||
.await?;
|
);
|
||||||
|
this.children.insert(child.id(), child.clone());
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
|
});
|
||||||
|
|
||||||
|
on_actor_link_died!(this, actor_ref, id, reason, {
|
||||||
|
match reason {
|
||||||
|
ActorStopReason::Normal => {
|
||||||
|
this.children.remove(&id);
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
return Ok(Some(reason));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if this.children.is_empty() {
|
||||||
|
return Ok(Some(ActorStopReason::Normal));
|
||||||
|
}
|
||||||
|
Ok(None)
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
150
src/import/stacks.rs
Normal file
150
src/import/stacks.rs
Normal file
|
@ -0,0 +1,150 @@
|
||||||
|
//
|
||||||
|
use std::{collections::HashMap, ops::Deref};
|
||||||
|
|
||||||
|
use kameo::{
|
||||||
|
actor::{ActorID, ActorRef},
|
||||||
|
error::ActorStopReason,
|
||||||
|
mailbox::unbounded::UnboundedMailbox,
|
||||||
|
Actor,
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
import::{labels::LabelsActor, stack::ImportStackActor},
|
||||||
|
nextcloud::{
|
||||||
|
client::DeckClient,
|
||||||
|
model::{NextcloudBoardId, Stack},
|
||||||
|
},
|
||||||
|
on_actor_link_died, on_actor_start, p, spawn_in_thread,
|
||||||
|
trello::model::list::TrelloList,
|
||||||
|
FullCtx,
|
||||||
|
};
|
||||||
|
|
||||||
|
async fn create_any_missing_stacks(
|
||||||
|
ctx: &FullCtx,
|
||||||
|
selected_trello_stack_names: &[String],
|
||||||
|
nextcloud_board_id: NextcloudBoardId,
|
||||||
|
nextcloud_stacks: Vec<Stack>,
|
||||||
|
) -> color_eyre::Result<()> {
|
||||||
|
let deck_client: DeckClient = ctx.deck_client();
|
||||||
|
|
||||||
|
// identify any stacks by name from those selected in trello that are missing in nextcloud
|
||||||
|
let missing_stack_names = selected_trello_stack_names
|
||||||
|
.iter()
|
||||||
|
.filter(|s| !nextcloud_stacks.iter().any(|ns| ns.title.deref() == *s))
|
||||||
|
.cloned()
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
if !missing_stack_names.is_empty() {
|
||||||
|
crate::p!(ctx.prt, "Missing stacks: {:?}", missing_stack_names);
|
||||||
|
// create any missing stacks in nextcloud
|
||||||
|
// for each missing stack
|
||||||
|
for missing_stack_name in missing_stack_names.into_iter() {
|
||||||
|
// - create the stack
|
||||||
|
let stack = deck_client
|
||||||
|
.create_stack(nextcloud_board_id, &missing_stack_name.clone().into())
|
||||||
|
.await
|
||||||
|
.result?;
|
||||||
|
p!(ctx.prt, "Created stack: {}", stack.title);
|
||||||
|
}
|
||||||
|
p!(ctx.prt, "Stacks created");
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
pub(super) struct ImportStacksActor {
|
||||||
|
ctx: FullCtx,
|
||||||
|
trello_stacks: Vec<TrelloList>,
|
||||||
|
selected_trello_stack_names: Vec<String>,
|
||||||
|
nextcloud_board_id: NextcloudBoardId,
|
||||||
|
labels_actor_ref: ActorRef<LabelsActor>,
|
||||||
|
|
||||||
|
children: HashMap<ActorID, ActorRef<ImportStackActor>>,
|
||||||
|
}
|
||||||
|
impl ImportStacksActor {
|
||||||
|
pub(super) fn new(
|
||||||
|
ctx: FullCtx,
|
||||||
|
trello_stacks: Vec<TrelloList>,
|
||||||
|
selected_trello_stack_names: Vec<String>,
|
||||||
|
nextcloud_board_id: NextcloudBoardId,
|
||||||
|
labels_actor_ref: ActorRef<LabelsActor>,
|
||||||
|
) -> Self {
|
||||||
|
Self {
|
||||||
|
ctx,
|
||||||
|
trello_stacks,
|
||||||
|
selected_trello_stack_names,
|
||||||
|
nextcloud_board_id,
|
||||||
|
labels_actor_ref,
|
||||||
|
children: Default::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Actor for ImportStacksActor {
|
||||||
|
type Mailbox = UnboundedMailbox<Self>;
|
||||||
|
|
||||||
|
on_actor_start!(this, actor_ref, {
|
||||||
|
// spawn a new ImportStack actor for each trello_stack named in selected_trello_stack_names
|
||||||
|
|
||||||
|
// get list of nextcloud stacks in the selected board
|
||||||
|
let nextcloud_stacks = this
|
||||||
|
.ctx
|
||||||
|
.deck_client()
|
||||||
|
.get_stacks(this.nextcloud_board_id)
|
||||||
|
.await
|
||||||
|
.result?;
|
||||||
|
create_any_missing_stacks(
|
||||||
|
&this.ctx,
|
||||||
|
&this.selected_trello_stack_names,
|
||||||
|
this.nextcloud_board_id,
|
||||||
|
nextcloud_stacks,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
// - get the list of nextcloud stacks again (with new stack ids)
|
||||||
|
let nextcloud_stacks = this
|
||||||
|
.ctx
|
||||||
|
.deck_client()
|
||||||
|
.get_stacks(this.nextcloud_board_id)
|
||||||
|
.await
|
||||||
|
.result?;
|
||||||
|
|
||||||
|
// for each selected trello stack
|
||||||
|
for selected_trello_stack in this
|
||||||
|
.trello_stacks
|
||||||
|
.iter()
|
||||||
|
.filter(|s| this.selected_trello_stack_names.contains(s.name.as_ref()))
|
||||||
|
{
|
||||||
|
let nextcloud_stack_id = //this.
|
||||||
|
nextcloud_stacks
|
||||||
|
.iter()
|
||||||
|
.find(|ns| ns.title.deref() == selected_trello_stack.name.as_ref())
|
||||||
|
.map(|ns| ns.id)
|
||||||
|
.expect("find nextcloud stack");
|
||||||
|
let child: ActorRef<ImportStackActor> = spawn_in_thread!(
|
||||||
|
actor_ref,
|
||||||
|
ImportStackActor::new(
|
||||||
|
this.ctx.clone(),
|
||||||
|
selected_trello_stack.clone(),
|
||||||
|
this.nextcloud_board_id,
|
||||||
|
nextcloud_stack_id,
|
||||||
|
this.labels_actor_ref.clone(),
|
||||||
|
)
|
||||||
|
);
|
||||||
|
this.children.insert(child.id(), child);
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
});
|
||||||
|
|
||||||
|
on_actor_link_died!(this, actor_ref, id, reason, {
|
||||||
|
match reason {
|
||||||
|
ActorStopReason::Normal => {
|
||||||
|
this.children.remove(&id);
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
return Ok(Some(reason));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if this.children.is_empty() {
|
||||||
|
return Ok(Some(ActorStopReason::Normal));
|
||||||
|
}
|
||||||
|
Ok(None)
|
||||||
|
});
|
||||||
|
}
|
22
src/import/supervisor.rs
Normal file
22
src/import/supervisor.rs
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
//
|
||||||
|
use kameo::{
|
||||||
|
actor::{ActorID, WeakActorRef},
|
||||||
|
error::{ActorStopReason, BoxError},
|
||||||
|
mailbox::unbounded::UnboundedMailbox,
|
||||||
|
Actor,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub(super) struct Supervisor;
|
||||||
|
impl Actor for Supervisor {
|
||||||
|
type Mailbox = UnboundedMailbox<Self>;
|
||||||
|
|
||||||
|
async fn on_link_died(
|
||||||
|
&mut self,
|
||||||
|
_actor_ref: WeakActorRef<Self>,
|
||||||
|
_id: ActorID,
|
||||||
|
reason: ActorStopReason,
|
||||||
|
) -> Result<Option<ActorStopReason>, BoxError> {
|
||||||
|
// normally when ImportStacksActor stops it would not cause the supervisor to stop, but we want it to stop.
|
||||||
|
Ok(Some(reason))
|
||||||
|
}
|
||||||
|
}
|
15
src/lib.rs
15
src/lib.rs
|
@ -4,7 +4,11 @@ use std::path::PathBuf;
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
use color_eyre::eyre::eyre;
|
use color_eyre::eyre::eyre;
|
||||||
use config::AppConfig;
|
use config::AppConfig;
|
||||||
use kxio::{fs::FileSystem, net::Net, print::Printer};
|
use kxio::{fs::FileSystem, kxeprintln as e, kxprintln as p, net::Net, print::Printer};
|
||||||
|
|
||||||
|
use crate::{nextcloud::client::DeckClient, trello::client::TrelloClient};
|
||||||
|
|
||||||
|
use execute::Execute;
|
||||||
|
|
||||||
mod api_result;
|
mod api_result;
|
||||||
mod check;
|
mod check;
|
||||||
|
@ -13,7 +17,7 @@ mod conversion;
|
||||||
mod execute;
|
mod execute;
|
||||||
mod import;
|
mod import;
|
||||||
mod init;
|
mod init;
|
||||||
mod macros;
|
pub mod macros;
|
||||||
mod nextcloud;
|
mod nextcloud;
|
||||||
mod template;
|
mod template;
|
||||||
mod trello;
|
mod trello;
|
||||||
|
@ -23,13 +27,6 @@ mod tests;
|
||||||
|
|
||||||
const NAME: &str = "trello-to-deck";
|
const NAME: &str = "trello-to-deck";
|
||||||
|
|
||||||
use crate::nextcloud::client::DeckClient;
|
|
||||||
use crate::trello::client::TrelloClient;
|
|
||||||
use execute::Execute;
|
|
||||||
|
|
||||||
use kxio::kxeprintln as e;
|
|
||||||
use kxio::kxprintln as p;
|
|
||||||
|
|
||||||
#[derive(Parser, Debug)]
|
#[derive(Parser, Debug)]
|
||||||
#[clap(version = clap::crate_version!(), author = clap::crate_authors!(), about = clap::crate_description!())]
|
#[clap(version = clap::crate_version!(), author = clap::crate_authors!(), about = clap::crate_description!())]
|
||||||
pub struct Commands {
|
pub struct Commands {
|
||||||
|
|
209
src/macros/actor.rs
Normal file
209
src/macros/actor.rs
Normal file
|
@ -0,0 +1,209 @@
|
||||||
|
//
|
||||||
|
|
||||||
|
/// Called when the actor starts, before it processes any messages.
|
||||||
|
///
|
||||||
|
/// Messages sent internally by the actor during `on_start` are prioritized and processed
|
||||||
|
/// before any externally sent messages, even if external messages are received first.
|
||||||
|
///
|
||||||
|
/// This ensures that the actor can properly initialize before handling external messages.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// use trello_to_deck::on_actor_start;
|
||||||
|
/// struct ServerActor;
|
||||||
|
/// impl kameo::Actor for ServerActor {
|
||||||
|
/// type Mailbox = kameo::mailbox::unbounded::UnboundedMailbox<Self>;
|
||||||
|
///
|
||||||
|
/// on_actor_start!(this, actor_ref, {
|
||||||
|
/// // handle start here
|
||||||
|
/// Ok(())
|
||||||
|
/// });
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! on_actor_start {
|
||||||
|
($this:ident, $actor_ref:ident, $body:expr) => {
|
||||||
|
#[allow(unused_variables)]
|
||||||
|
#[tracing::instrument(skip_all)]
|
||||||
|
async fn on_start(
|
||||||
|
&mut self,
|
||||||
|
actor_ref: kameo::actor::ActorRef<Self>,
|
||||||
|
) -> std::result::Result<(), kameo::error::BoxError> {
|
||||||
|
tracing::debug!(?actor_ref, "{}", <Self as kameo::Actor>::name());
|
||||||
|
let $this = self;
|
||||||
|
let $actor_ref = actor_ref;
|
||||||
|
$body
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! default_on_actor_start {
|
||||||
|
($this:ident, $actor_ref:ident) => {
|
||||||
|
$crate::on_actor_start!($this, $actor_ref, { Ok(()) });
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Called swhen the actor encounters a panic or an error during "tell" message handling.
|
||||||
|
///
|
||||||
|
/// This method gives the actor an opportunity to clean up or reset its state and determine
|
||||||
|
/// whether it should be stopped or continue processing messages.
|
||||||
|
///
|
||||||
|
/// # Parameters
|
||||||
|
/// - `err`: The panic or error that occurred.
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
/// - `Some(ActorStopReason)`: Stops the actor.
|
||||||
|
/// - `None`: Allows the actor to continue processing messages.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// use trello_to_deck::on_actor_panic;
|
||||||
|
/// struct ServerActor;
|
||||||
|
/// impl kameo::Actor for ServerActor {
|
||||||
|
/// type Mailbox = kameo::mailbox::unbounded::UnboundedMailbox<Self>;
|
||||||
|
///
|
||||||
|
/// on_actor_panic!(this, actor_ref, err, {
|
||||||
|
/// // handle panic here
|
||||||
|
/// Ok(None)
|
||||||
|
/// });
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! on_actor_panic {
|
||||||
|
($this:ident, $actor_ref:ident, $err:ident, $body:expr) => {
|
||||||
|
#[allow(unused_variables)]
|
||||||
|
#[tracing::instrument(skip_all)]
|
||||||
|
async fn on_panic(
|
||||||
|
&mut self,
|
||||||
|
actor_ref: kameo::actor::WeakActorRef<Self>,
|
||||||
|
err: kameo::error::PanicError,
|
||||||
|
) -> std::result::Result<Option<kameo::error::ActorStopReason>, kameo::error::BoxError> {
|
||||||
|
tracing::debug!(?actor_ref, %err, "{}", <Self as kameo::Actor>::name());
|
||||||
|
let $this = self;
|
||||||
|
let $actor_ref = actor_ref;
|
||||||
|
let $err = err;
|
||||||
|
$body
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! default_on_actor_panic {
|
||||||
|
($this:ident, $actor_ref:ident, $err:ident) => {
|
||||||
|
$crate::on_actor_panic!($this, $actor_ref, $err, {
|
||||||
|
Ok(Some(kameo::error::ActorStopReason::Panicked($err)))
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Called when a linked actor dies.
|
||||||
|
///
|
||||||
|
/// By default, the actor will stop if the reason for the linked actor's death is anything other
|
||||||
|
/// than `Normal`. You can customize this behavior in the implementation.
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
/// Whether the actor should stop or continue processing messages.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// use trello_to_deck::on_actor_link_died;
|
||||||
|
/// struct ServerActor;
|
||||||
|
/// impl kameo::Actor for ServerActor {
|
||||||
|
/// type Mailbox = kameo::mailbox::unbounded::UnboundedMailbox<Self>;
|
||||||
|
///
|
||||||
|
/// on_actor_link_died!(this, actor_ref, id, reason, {
|
||||||
|
/// // handle link death here
|
||||||
|
/// Ok(None)
|
||||||
|
/// });
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! on_actor_link_died {
|
||||||
|
($this:ident, $actor_ref:ident, $id:ident, $reason:ident, $body:expr) => {
|
||||||
|
#[allow(unused_variables)]
|
||||||
|
#[tracing::instrument(skip_all)]
|
||||||
|
async fn on_link_died(
|
||||||
|
&mut self,
|
||||||
|
actor_ref: kameo::actor::WeakActorRef<Self>,
|
||||||
|
id: kameo::actor::ActorID,
|
||||||
|
reason: kameo::error::ActorStopReason,
|
||||||
|
) -> std::result::Result<Option<kameo::error::ActorStopReason>, kameo::error::BoxError> {
|
||||||
|
tracing::debug!(?actor_ref, %id, %reason, "{}", <Self as kameo::Actor>::name());
|
||||||
|
let $this = self;
|
||||||
|
let $actor_ref = actor_ref;
|
||||||
|
let $id = id;
|
||||||
|
let $reason = reason;
|
||||||
|
$body
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! default_on_actor_link_died {
|
||||||
|
($this:ident, $actor_ref:ident, $id:ident, $reason:ident) => {
|
||||||
|
$crate::on_actor_link_died!($this, $actor_ref, $id, $reason, {
|
||||||
|
match &$reason {
|
||||||
|
kameo::error::ActorStopReason::Normal => Ok(None),
|
||||||
|
kameo::error::ActorStopReason::Killed
|
||||||
|
| kameo::error::ActorStopReason::Panicked(_)
|
||||||
|
| kameo::error::ActorStopReason::LinkDied { .. } => {
|
||||||
|
Ok(Some(kameo::error::ActorStopReason::LinkDied {
|
||||||
|
id: $id,
|
||||||
|
reason: Box::new($reason),
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Called before the actor stops.
|
||||||
|
///
|
||||||
|
/// This allows the actor to perform any necessary cleanup or release resources before being fully stopped.
|
||||||
|
///
|
||||||
|
/// # Parameters
|
||||||
|
/// - `reason`: The reason why the actor is being stopped.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// use trello_to_deck::on_actor_stop;
|
||||||
|
/// struct ServerActor;
|
||||||
|
/// impl kameo::Actor for ServerActor {
|
||||||
|
/// type Mailbox = kameo::mailbox::unbounded::UnboundedMailbox<Self>;
|
||||||
|
///
|
||||||
|
/// on_actor_stop!(this, actor_ref, reason, {
|
||||||
|
/// // handle stop here
|
||||||
|
/// Ok(())
|
||||||
|
/// });
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! on_actor_stop {
|
||||||
|
($this:ident, $actor_ref:ident, $reason:ident, $body:expr) => {
|
||||||
|
#[allow(unused_variables)]
|
||||||
|
#[tracing::instrument(skip_all)]
|
||||||
|
async fn on_stop(
|
||||||
|
&mut self,
|
||||||
|
actor_ref: kameo::actor::WeakActorRef<Self>,
|
||||||
|
reason: kameo::error::ActorStopReason,
|
||||||
|
) -> std::result::Result<(), kameo::error::BoxError> {
|
||||||
|
tracing::debug!(?actor_ref, %reason, "{}", <Self as kameo::Actor>::name());
|
||||||
|
let $this = self;
|
||||||
|
let $actor_ref = actor_ref;
|
||||||
|
let $reason = reason;
|
||||||
|
$body
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! default_on_actor_stop {
|
||||||
|
($this:ident, $actor_ref:ident, $reason:ident) => {
|
||||||
|
$crate::on_actor_stop!($this, $actor_ref, $reason, { Ok(()) });
|
||||||
|
};
|
||||||
|
}
|
|
@ -1,4 +1,6 @@
|
||||||
//
|
//
|
||||||
|
mod actor;
|
||||||
mod newtype;
|
mod newtype;
|
||||||
mod print;
|
mod print;
|
||||||
|
mod send;
|
||||||
mod to_string;
|
mod to_string;
|
||||||
|
|
64
src/macros/send.rs
Normal file
64
src/macros/send.rs
Normal file
|
@ -0,0 +1,64 @@
|
||||||
|
//
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! tell {
|
||||||
|
($actor_ref:expr, $message:expr) => {
|
||||||
|
tell!(stringify!($actor_ref), $actor_ref, $message)
|
||||||
|
};
|
||||||
|
($actor_name:expr, $actor_ref:expr, $message:expr) => {{
|
||||||
|
tracing::debug!(actor = $actor_name, msg = stringify!($message), "send");
|
||||||
|
$actor_ref.tell($message).await
|
||||||
|
}};
|
||||||
|
}
|
||||||
|
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! ask {
|
||||||
|
($actor_ref:expr, $message:expr) => {
|
||||||
|
ask!(stringify!($actor_ref), $actor_ref, $message)
|
||||||
|
};
|
||||||
|
($actor_name:expr, $actor_ref:expr, $message:expr) => {{
|
||||||
|
tracing::debug!(actor = $actor_name, msg = stringify!($message), "send");
|
||||||
|
$actor_ref.ask($message).await
|
||||||
|
}};
|
||||||
|
}
|
||||||
|
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! subscribe {
|
||||||
|
($message_bus:expr, $actor_ref:expr) => {
|
||||||
|
subscribe!(
|
||||||
|
stringify!($message_bus),
|
||||||
|
$message_bus,
|
||||||
|
stringify!($actor_ref),
|
||||||
|
$actor_ref
|
||||||
|
)
|
||||||
|
};
|
||||||
|
($message_bus:expr, $actor_name:expr, $actor_ref:expr) => {
|
||||||
|
subscribe!(
|
||||||
|
stringify!($message_bus),
|
||||||
|
$message_bus,
|
||||||
|
$actor_name,
|
||||||
|
$actor_ref
|
||||||
|
)
|
||||||
|
};
|
||||||
|
($bus_name:expr, $message_bus:expr, $actor_ref:expr) => {
|
||||||
|
subscribe!($bus_name, $message_bus, stringify!($actor_ref), $actor_ref)
|
||||||
|
};
|
||||||
|
($bus_name:expr, $message_bus:expr, $actor_name:expr, $actor_ref:expr) => {{
|
||||||
|
tracing::debug!(msg_bus = $bus_name, actor = $actor_name, "subscribe");
|
||||||
|
$message_bus
|
||||||
|
.tell(kameo::actor::pubsub::Subscribe($actor_ref))
|
||||||
|
.await
|
||||||
|
}};
|
||||||
|
}
|
||||||
|
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! publish {
|
||||||
|
($message_bus:expr, $message:expr) => {
|
||||||
|
publish!(stringify!($message_bus), $message_bus, $message)
|
||||||
|
};
|
||||||
|
($bus_name:expr, $message_bus:expr, $message:expr) => {{
|
||||||
|
tracing::debug!(bus = $bus_name, msg = stringify!($message), "publish");
|
||||||
|
$message_bus
|
||||||
|
.tell(kameo::actor::pubsub::Publish($message))
|
||||||
|
.await
|
||||||
|
}};
|
||||||
|
}
|
|
@ -63,6 +63,7 @@ newtype!(
|
||||||
Hash,
|
Hash,
|
||||||
PartialOrd,
|
PartialOrd,
|
||||||
Ord,
|
Ord,
|
||||||
|
kameo::Reply,
|
||||||
"ID of a Nextcloud Label"
|
"ID of a Nextcloud Label"
|
||||||
);
|
);
|
||||||
newtype!(
|
newtype!(
|
||||||
|
|
Loading…
Reference in a new issue