1use rand::seq::IndexedRandom;
10use std::borrow::Cow;
11use std::error::Error;
12use std::fs::File;
13use std::path::Path;
14use std::thread;
15use std::time::Duration;
16use zmq::{Context, Message, SNDMORE};
17
18use crate::backend;
19use crate::backend::default_db_address;
20use crate::importer::Importer;
21use crate::models::{Corpus, NewCorpus, Task};
22use pericortex::worker::Worker;
23
24fn resolve_worker_identity(configured: &str, rng: &mut impl rand::Rng) -> String {
31 if !configured.is_empty() {
32 return configured.to_string();
33 }
34 let letters: Vec<char> = "abcdefghijklmonpqrstuvwxyz".chars().collect();
35 (1..20).map(|_| *letters.choose(rng).unwrap()).collect()
37}
38
39#[derive(Debug, Clone)]
41pub struct InitWorker {
42 pub service: String,
44 pub version: f32,
46 pub message_size: usize,
48 pub source: String,
50 pub sink: String,
52 pub backend_address: String,
56 pub identity: String,
58}
59impl Default for InitWorker {
60 fn default() -> InitWorker {
61 InitWorker {
62 service: "init".to_string(),
63 version: 0.1,
64 message_size: 100_000,
65 source: "tcp://localhost:51695".to_string(),
66 sink: "tcp://localhost:51696".to_string(),
67 backend_address: default_db_address().to_string(),
68 identity: String::new(),
69 }
70 }
71}
72impl Worker for InitWorker {
73 fn get_service(&self) -> &str { &self.service }
74 fn get_source_address(&self) -> Cow<'_, str> { Cow::Borrowed(&self.source) }
75 fn get_sink_address(&self) -> Cow<'_, str> { Cow::Borrowed(&self.sink) }
76 fn get_identity(&self) -> &str { &self.identity }
77 fn set_identity(&mut self, identity: String) { self.identity = identity; }
78 fn message_size(&self) -> usize { self.message_size }
79
80 fn convert(&self, path_opt: &Path) -> Result<File, Box<dyn Error>> {
81 let path = path_opt.to_str().unwrap().to_string();
82 let name = path
83 .rsplit_once('/')
84 .map(|x| x.1)
85 .unwrap_or(&path)
86 .to_lowercase(); let mut backend = backend::from_address(&self.backend_address);
88 let corpus = NewCorpus {
89 name,
90 path,
91 complex: true,
92 description: String::new(),
93 };
94 backend.add(&corpus).expect("Failed to create new corpus.");
96 let registered_corpus = Corpus::find_by_name(&corpus.name, &mut backend.connection)
97 .expect("Failed to create new corpus.");
98
99 let mut importer = Importer {
101 corpus: registered_corpus,
102 backend: backend::from_address(&self.backend_address),
103 ..Importer::default()
104 };
105
106 importer.process()?;
107 Err(From::from("init worker does not return a file handle."))
109 }
110
111 fn start(&mut self, limit: Option<usize>) -> Result<(), Box<dyn Error>> {
112 let mut work_counter = 0;
113 let mut rng = rand::rng();
114 let context_source = Context::new();
116 let source = context_source.socket(zmq::DEALER).unwrap();
117 let identity = resolve_worker_identity(&self.identity, &mut rng);
118 source.set_identity(identity.as_bytes()).unwrap();
119
120 source.connect(&self.get_source_address()).unwrap();
121 let context_sink = Context::new();
123 let sink = context_sink.socket(zmq::PUSH).unwrap();
124 sink.connect(&self.get_sink_address()).unwrap();
125 let mut backend = backend::from_address(&self.backend_address);
126 loop {
128 let mut taskid_msg = Message::new();
129 let mut recv_msg = Message::new();
130
131 source.send(self.get_service(), 0).unwrap();
132 source.recv(&mut taskid_msg, 0).unwrap();
133 let taskid = taskid_msg.as_str().unwrap();
134
135 source.recv(&mut recv_msg, 0).unwrap();
137
138 let task_result = Task::find(taskid.parse::<i64>().unwrap(), &mut backend.connection);
139 let task = match task_result {
140 Ok(t) => t,
141 _ => {
142 thread::sleep(Duration::new(60, 0));
144 continue;
145 },
146 };
147 let _ = self.convert(Path::new(&task.entry));
149 sink.send(&identity, SNDMORE).unwrap();
150 sink.send(self.get_service(), SNDMORE).unwrap();
151 sink.send(taskid, SNDMORE).unwrap();
152 sink.send(Vec::new(), 0).unwrap();
153
154 work_counter += 1;
155 if let Some(upper_bound) = limit
156 && work_counter >= upper_bound
157 {
158 thread::sleep(Duration::from_millis(500));
160 break;
161 };
162 }
163 Ok(())
164 }
165}
166
167#[cfg(test)]
168mod tests {
169 use super::resolve_worker_identity;
170
171 #[test]
172 fn configured_identity_is_honored_verbatim() {
173 let mut rng = rand::rng();
175 assert_eq!(
176 resolve_worker_identity("arxiv-host3:init:1", &mut rng),
177 "arxiv-host3:init:1"
178 );
179 }
180
181 #[test]
182 fn empty_identity_falls_back_to_a_random_handle() {
183 let mut rng = rand::rng();
184 let id = resolve_worker_identity("", &mut rng);
185 assert_eq!(id.len(), 19, "preserves the historical 19-char length");
186 assert!(
187 id.chars().all(|c| c.is_ascii_lowercase()),
188 "random handle is lowercase letters only"
189 );
190 }
191}