chgk_ledb/lib/src/source.rs

361 lines
10 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

use serde_derive::{Deserialize, Serialize};
#[derive(Debug, Default, Clone, Serialize, Deserialize, PartialEq)]
pub struct SourceQuestion {
#[serde(default)]
#[serde(skip_serializing_if = "u32_is_zero")]
pub num: u32,
pub id: String,
#[serde(alias = "Вопрос")]
pub description: String,
#[serde(alias = "Ответ")]
pub answer: String,
#[serde(alias = "Автор")]
#[serde(default)]
#[serde(skip_serializing_if = "String::is_empty")]
pub author: String,
#[serde(alias = "Комментарий")]
#[serde(default)]
#[serde(skip_serializing_if = "String::is_empty")]
pub comment: String,
#[serde(alias = "Комментарии")]
#[serde(alias = "Инфо")]
#[serde(default)]
#[serde(skip_serializing_if = "String::is_empty")]
pub comment1: String,
#[serde(alias = "Тур")]
#[serde(default)]
#[serde(skip_serializing_if = "String::is_empty")]
pub tour: String,
#[serde(alias = "Ссылка")]
#[serde(alias = "URL")]
#[serde(default)]
#[serde(skip_serializing_if = "String::is_empty")]
pub url: String,
#[serde(alias = "Дата")]
#[serde(default)]
#[serde(skip_serializing_if = "String::is_empty")]
pub date: String,
#[serde(alias = "Обработан")]
#[serde(default)]
#[serde(skip_serializing_if = "String::is_empty")]
pub processed_by: String,
#[serde(alias = "Редактор")]
#[serde(default)]
#[serde(skip_serializing_if = "String::is_empty")]
pub redacted_by: String,
#[serde(alias = "Копирайт")]
#[serde(default)]
#[serde(skip_serializing_if = "String::is_empty")]
pub copyright: String,
#[serde(alias = "Тема")]
#[serde(default)]
#[serde(skip_serializing_if = "String::is_empty")]
pub theme: String,
#[serde(alias = "Вид")]
#[serde(alias = "Тип")]
#[serde(default)]
#[serde(skip_serializing_if = "String::is_empty")]
pub kind: String,
#[serde(alias = "Источник")]
#[serde(default)]
#[serde(skip_serializing_if = "String::is_empty")]
pub source: String,
#[serde(alias = "Рейтинг")]
#[serde(default)]
#[serde(skip_serializing_if = "String::is_empty")]
pub rating: String,
}
#[derive(Debug, Default, Clone, Serialize, Deserialize, PartialEq)]
pub struct SourceQuestionsBatch {
#[serde(default)]
#[serde(skip_serializing_if = "String::is_empty")]
pub filename: String,
#[serde(alias = "Пакет")]
#[serde(alias = "Чемпионат")]
pub description: String,
#[serde(alias = "Автор")]
#[serde(default)]
#[serde(skip_serializing_if = "String::is_empty")]
pub author: String,
#[serde(alias = "Комментарий")]
#[serde(alias = "Комментарии")]
#[serde(alias = "Инфо")]
#[serde(default)]
#[serde(skip_serializing_if = "String::is_empty")]
pub comment: String,
#[serde(alias = "Ссылка")]
#[serde(alias = "URL")]
#[serde(default)]
#[serde(skip_serializing_if = "String::is_empty")]
pub url: String,
#[serde(alias = "Дата")]
#[serde(default)]
#[serde(skip_serializing_if = "String::is_empty")]
pub date: String,
#[serde(alias = "Обработан")]
#[serde(default)]
#[serde(skip_serializing_if = "String::is_empty")]
pub processed_by: String,
#[serde(alias = "Редактор")]
#[serde(default)]
#[serde(skip_serializing_if = "String::is_empty")]
pub redacted_by: String,
#[serde(alias = "Копирайт")]
#[serde(default)]
#[serde(skip_serializing_if = "String::is_empty")]
pub copyright: String,
#[serde(alias = "Тема")]
#[serde(default)]
#[serde(skip_serializing_if = "String::is_empty")]
pub theme: String,
#[serde(alias = "Вид")]
#[serde(alias = "Тип")]
#[serde(default)]
#[serde(skip_serializing_if = "String::is_empty")]
pub kind: String,
#[serde(alias = "Источник")]
#[serde(default)]
#[serde(skip_serializing_if = "String::is_empty")]
pub source: String,
#[serde(alias = "Рейтинг")]
#[serde(default)]
#[serde(skip_serializing_if = "String::is_empty")]
pub rating: String,
#[serde(alias = "Вопросы")]
pub questions: Vec<SourceQuestion>,
}
fn u32_is_zero(num: &u32) -> bool {
*num == 0
}
#[cfg(any(feature = "convert", feature = "source"))]
pub mod reader_sync {
use std::io::{Read, Seek};
use zip::ZipArchive;
use super::SourceQuestionsBatch;
pub struct SourceQuestionsZipReader<R>
where
R: Read + Seek,
{
zipfile: ZipArchive<R>,
index: Option<usize>,
}
impl<R> SourceQuestionsZipReader<R>
where
R: Read + Seek,
{
fn new(zipfile: ZipArchive<R>) -> Self {
SourceQuestionsZipReader {
zipfile,
index: None,
}
}
}
impl<R> Iterator for SourceQuestionsZipReader<R>
where
R: Read + Seek,
{
type Item = (String, Result<SourceQuestionsBatch, serde_json::Error>);
fn next(&mut self) -> Option<Self::Item> {
if self.index.is_none() && !self.zipfile.is_empty() {
self.index = Some(0);
}
match self.index {
Some(i) if i < self.zipfile.len() => {
self.index = Some(i + 1);
self.nth(i)
}
_ => None,
}
}
fn nth(&mut self, n: usize) -> Option<Self::Item> {
if self.zipfile.len() <= n {
return None;
}
self.index = Some(n + 1);
let file = self.zipfile.by_index(n).unwrap();
let name = file.mangled_name();
let name_str = name.to_str().unwrap();
let data: Result<SourceQuestionsBatch, _> = serde_json::from_reader(file);
Some((String::from(name_str), data))
}
fn size_hint(&self) -> (usize, Option<usize>) {
let len = self.zipfile.len();
let index = self.index.unwrap_or(0);
let rem = if len > index + 1 {
len - (index + 1)
} else {
0
};
(rem, Some(rem))
}
fn count(self) -> usize
where
Self: Sized,
{
self.zipfile.len()
}
}
impl<R> ExactSizeIterator for SourceQuestionsZipReader<R>
where
R: Read + Seek,
{
fn len(&self) -> usize {
self.zipfile.len()
}
}
pub trait ReadSourceQuestionsBatches<R>
where
R: Read + Seek,
{
fn source_questions(self) -> SourceQuestionsZipReader<R>;
}
impl<R> ReadSourceQuestionsBatches<R> for ZipArchive<R>
where
R: Read + Seek,
{
fn source_questions(self) -> SourceQuestionsZipReader<R> {
SourceQuestionsZipReader::new(self)
}
}
}
#[cfg(any(feature = "convert", feature = "source"))]
pub use reader_sync::{ReadSourceQuestionsBatches, SourceQuestionsZipReader};
#[cfg(any(feature = "convert_async", feature = "source_async"))]
pub mod reader_async {
use async_stream::stream;
use async_zip::tokio::read::seek::ZipFileReader;
use futures_core::stream::Stream;
use futures_util::AsyncReadExt;
use tokio::io::{AsyncRead, AsyncSeek};
use super::SourceQuestionsBatch;
pub struct SourceQuestionsZipReaderAsync<R>
where
R: AsyncRead + AsyncSeek + Unpin,
{
zipfile: ZipFileReader<R>,
index: Option<usize>,
}
impl<R> SourceQuestionsZipReaderAsync<R>
where
R: AsyncRead + AsyncSeek + Unpin,
{
fn new(zipfile: ZipFileReader<R>) -> Self {
SourceQuestionsZipReaderAsync {
zipfile,
index: None,
}
}
pub fn len(&self) -> usize {
self.zipfile.file().entries().len()
}
pub fn is_empty(&self) -> bool {
self.len() == 0
}
pub async fn get(
&mut self,
index: usize,
) -> Result<(String, Result<SourceQuestionsBatch, serde_json::Error>), String>
where
R: AsyncRead + AsyncSeek + Unpin,
{
let len = self.len();
if index >= len {
return Err(format!("get index={index}, when len={len}"));
}
let reader = self.zipfile.reader_with_entry(index).await;
if let Err(error) = reader {
return Err(format!("reader_with_entry: {error:?}"));
}
let mut reader = reader.unwrap();
let filename = reader.entry().filename().clone().into_string().unwrap();
let mut data: Vec<u8> = Vec::new();
let readed = reader.read_to_end(&mut data).await;
if let Err(error) = readed {
return Err(format!("read_to_end: {error:?}"));
}
let parsed: Result<SourceQuestionsBatch, _> = serde_json::from_slice(&data);
Ok((filename, parsed))
}
pub async fn get_next(
&mut self,
) -> Option<Result<(String, Result<SourceQuestionsBatch, serde_json::Error>), String>>
where
R: AsyncRead + AsyncSeek + Unpin,
{
if self.index.is_none() && !self.is_empty() {
self.index = Some(0);
}
if self.index.unwrap() >= self.len() {
return None;
}
let item = self.get(self.index.unwrap()).await;
self.index = Some(self.index.unwrap() + 1);
Some(item)
}
pub fn stream(
&mut self,
) -> impl Stream<Item = (String, Result<SourceQuestionsBatch, serde_json::Error>)> + '_
{
stream! {
while let Some(Ok(item)) = self.get_next().await {
yield item
}
}
}
}
pub trait ReadSourceQuestionsBatchesAsync<R>
where
R: AsyncRead + AsyncSeek + Unpin,
{
fn source_questions(self) -> SourceQuestionsZipReaderAsync<R>;
}
impl<R> ReadSourceQuestionsBatchesAsync<R> for ZipFileReader<R>
where
R: AsyncRead + AsyncSeek + Unpin,
{
fn source_questions(self) -> SourceQuestionsZipReaderAsync<R> {
SourceQuestionsZipReaderAsync::new(self)
}
}
}
#[cfg(any(feature = "convert_async", feature = "source_async"))]
pub use reader_async::{ReadSourceQuestionsBatchesAsync, SourceQuestionsZipReaderAsync};