use crate::tina::data::json::ToJson;
use crate::tina::data::AppResult;
use crate::{app_debug, app_system_error};
use crate::{app_error_from, tina::data::binary::StoredFileData};
use indexmap::IndexMap;
use mime::Mime;
use serde::Serialize;
use serde_json::Value;
use std::borrow::Cow;
use std::fs::File;
use std::panic::{catch_unwind, AssertUnwindSafe};
use std::path::Path;
use std::sync::{Arc, Mutex};
use xlsxwriter::prelude::{FormatAlignment, FormatVerticalAlignment};
use xlsxwriter::{Format, Workbook, Worksheet};
use super::json::JsonCaseExt;
#[allow(dead_code)]
pub struct ExcelFileData {
pub store_path: Arc<String>,
read_handle: Option<File>,
chunk_size: usize,
delete_on_drop: bool,
sheet_row_no: IndexMap<String, u32>,
copy_lock: Arc<Mutex<()>>,
}
impl ExcelFileData {
pub fn new<P: AsRef<Path>>(path: P, delete_on_drop: bool) -> ExcelFileData {
ExcelFileData {
store_path: Arc::new(path.as_ref().to_string_lossy().to_string()),
read_handle: None,
chunk_size: 8192,
delete_on_drop,
sheet_row_no: Default::default(),
copy_lock: Arc::new(Mutex::new(())),
}
}
pub fn get_content_type(&self) -> Mime {
crate::new_mime_guess::from_path(self.store_path.as_str()).first_or(mime::APPLICATION_OCTET_STREAM)
}
pub fn get_file_name(&self) -> Cow<str> {
Path::new(self.store_path.as_str()).file_name().map(|v| v.to_string_lossy()).unwrap_or_default()
}
pub fn create_write_handle(&self) -> AppResult<Workbook> {
let path = Path::new(self.store_path.as_str());
if let Some(parent) = path.parent() {
if !parent.exists() {
if let Err(err) = std::fs::create_dir_all(parent) {
return Err(app_system_error!("创建excel所在目录失败: {}, reason: {:?}", path.to_string_lossy(), err));
}
}
}
let func = AssertUnwindSafe(|| Workbook::new(self.store_path.as_str()));
let r = catch_unwind(func);
let workbook = r
.map_err(|_| app_system_error!("创建excel失败: {}", self.store_path.as_str()))?
.map_err(|_| app_system_error!("创建excel失败: {}", self.store_path.as_str()))?;
Ok(workbook)
}
fn add_format(&self, _handle: &Workbook) -> AppResult<Format> {
let func = AssertUnwindSafe(Format::new);
let format: Format = catch_unwind(func).map_err(|_| app_system_error!("excel添加format失败: {}", self.store_path.as_str()))?;
Ok(format)
}
pub fn generate_header_format(&self, handle: &Workbook) -> AppResult<Format> {
let mut format = self.add_format(handle)?;
format.set_align(FormatAlignment::Center).set_vertical_align(FormatVerticalAlignment::VerticalCenter).set_bold();
Ok(format)
}
pub fn generate_bool_format(&self, handle: &Workbook) -> AppResult<Format> {
let mut format = self.add_format(handle)?;
format.set_align(FormatAlignment::Center).set_vertical_align(FormatVerticalAlignment::VerticalCenter);
Ok(format)
}
pub fn generate_number_format(&self, handle: &Workbook) -> AppResult<Format> {
let mut format = self.add_format(handle)?;
format.set_align(FormatAlignment::Right).set_vertical_align(FormatVerticalAlignment::VerticalCenter);
Ok(format)
}
pub fn generate_simple_string_format(&self, handle: &Workbook) -> AppResult<Format> {
let mut format = self.add_format(handle)?;
format.set_align(FormatAlignment::Left).set_vertical_align(FormatVerticalAlignment::VerticalCenter);
Ok(format)
}
pub fn generate_rich_string_format(&self, handle: &Workbook) -> AppResult<Format> {
let mut format = self.add_format(handle)?;
format.set_text_wrap().set_align(FormatAlignment::Left).set_vertical_align(FormatVerticalAlignment::VerticalCenter);
Ok(format)
}
pub fn add_header<S: Serialize>(
&mut self,
handle: &Workbook,
sheet_name: &String,
header: &S,
bool_format: Option<&Format>,
number_format: Option<&Format>,
string_format: Option<&Format>,
) -> AppResult<()> {
let mut sheet = self.get_sheet(handle, sheet_name)?;
let value = header.to_json_value();
let row = self.get_sheet_row_no(sheet_name);
Self::write_value(&mut sheet, row, 0, &value, bool_format, number_format, string_format)?;
self.set_sheet_row_no(sheet_name, row + 1);
Ok(())
}
pub fn add_data<S: Serialize>(
&mut self,
handle: &Workbook,
sheet_name: &String,
data: &S,
bool_format: Option<&Format>,
number_format: Option<&Format>,
string_format: Option<&Format>,
) -> AppResult<()> {
let mut sheet = self.get_sheet(handle, sheet_name)?;
let value = data.to_json_value();
let row = self.get_sheet_row_no(sheet_name);
Self::write_value(&mut sheet, row, 0, &value, bool_format, number_format, string_format)?;
self.set_sheet_row_no(sheet_name, row + 1);
Ok(())
}
pub fn add_value(
&mut self,
handle: &Workbook,
sheet_name: &String,
value: Value,
bool_format: Option<&Format>,
number_format: Option<&Format>,
string_format: Option<&Format>,
) -> AppResult<()> {
let mut sheet = self.get_sheet(handle, sheet_name)?;
let row = self.get_sheet_row_no(sheet_name);
Self::write_value(&mut sheet, row, 0, &value, bool_format, number_format, string_format)?;
self.set_sheet_row_no(sheet_name, row + 1);
Ok(())
}
fn write_value(
sheet: &mut Worksheet,
row: u32,
mut col: u16,
value: &Value,
bool_format: Option<&Format>,
number_format: Option<&Format>,
string_format: Option<&Format>,
) -> AppResult<()> {
match value {
Value::Null => {}
Value::Bool(v) => {
sheet
.write_boolean(row, col, *v, bool_format)
.map_err(|err| app_system_error!("write failed: {}, reason: {:?}", v, err))?;
}
Value::Number(_) => {
let v = value.try_as_f64()?;
sheet
.write_number(row, col, v, number_format)
.map_err(|err| app_system_error!("write failed: {}, reason: {:?}", v, err))?;
}
Value::String(v) => {
sheet
.write_string(row, col, v.as_str(), string_format)
.map_err(|err| app_system_error!("write failed: {}, reason: {:?}", v, err))?;
}
Value::Array(v) => {
for item in v.iter() {
Self::write_value(sheet, row, col, item, bool_format, number_format, string_format)?;
col += 1;
}
}
Value::Object(v) => {
for (_, item) in v.iter() {
Self::write_value(sheet, row, col, item, bool_format, number_format, string_format)?;
col += 1;
}
}
}
Ok(())
}
fn get_sheet<'a>(&self, handle: &'a Workbook, sheet_name: &String) -> AppResult<Worksheet<'a>> {
match handle.get_worksheet(sheet_name.as_str()).map_err(app_error_from!())? {
None => {
let worksheet1 = handle
.add_worksheet(Some(sheet_name))
.map_err(|err| app_system_error!("add sheet failed: {}, reason: {:?}", sheet_name, err))?;
Ok(worksheet1)
}
Some(v) => Ok(v),
}
}
fn get_sheet_row_no(&self, sheet_name: &String) -> u32 {
self.sheet_row_no.get(sheet_name).copied().unwrap_or_default()
}
fn set_sheet_row_no(&mut self, sheet_name: &String, row_no: u32) {
self.sheet_row_no.insert(sheet_name.to_string(), row_no);
}
pub fn flush_and_close(&self, handle: Workbook) -> AppResult<()> {
handle.close().map_err(|v| app_system_error!("刷新文件内容至磁盘失败: {}, reason: {:?}", self.store_path.as_str(), v))?;
Ok(())
}
pub fn into_stored_file_data(mut self) -> StoredFileData {
let data = StoredFileData::new(self.store_path.as_str(), self.delete_on_drop);
self.delete_on_drop = false;
data
}
}
impl Drop for ExcelFileData {
fn drop(&mut self) {
if !self.delete_on_drop {
return;
}
let _lock = self.copy_lock.lock();
if Arc::strong_count(&self.store_path) <= 1 {
match std::fs::remove_file(self.store_path.as_str()) {
Ok(_) => {
app_debug!("删除文件: {}", self.store_path)
}
Err(err) => {
tracing::error!("删除文件失败: {}, reason: {:?}", self.store_path.as_str(), err);
}
}
}
}
}