style adjustments and adding the load function

Loading works. Needs some love for error handling and things but it works.
This commit is contained in:
Chris Cochrun 2023-09-12 06:39:52 -05:00
parent daf8511081
commit 3613586f63

View file

@ -3,9 +3,11 @@ mod service_item_model {
unsafe extern "C++" { unsafe extern "C++" {
include!(< QAbstractListModel >); include!(< QAbstractListModel >);
include!("cxx-qt-lib/qhash.h"); include!("cxx-qt-lib/qhash.h");
type QHash_i32_QByteArray = cxx_qt_lib::QHash<cxx_qt_lib::QHashPair_i32_QByteArray>; type QHash_i32_QByteArray =
cxx_qt_lib::QHash<cxx_qt_lib::QHashPair_i32_QByteArray>;
include!("cxx-qt-lib/qmap.h"); include!("cxx-qt-lib/qmap.h");
type QMap_QString_QVariant = cxx_qt_lib::QMap<cxx_qt_lib::QMapPair_QString_QVariant>; type QMap_QString_QVariant =
cxx_qt_lib::QMap<cxx_qt_lib::QMapPair_QString_QVariant>;
include!("cxx-qt-lib/qvariant.h"); include!("cxx-qt-lib/qvariant.h");
type QVariant = cxx_qt_lib::QVariant; type QVariant = cxx_qt_lib::QVariant;
include!("cxx-qt-lib/qstring.h"); include!("cxx-qt-lib/qstring.h");
@ -153,14 +155,21 @@ mod service_item_model {
#[qinvokable] #[qinvokable]
pub fn remove_item(mut self: Pin<&mut Self>, index: i32) { pub fn remove_item(mut self: Pin<&mut Self>, index: i32) {
if index < 0 || (index as usize) >= self.service_items().len() { if index < 0
|| (index as usize) >= self.service_items().len()
{
return; return;
} }
unsafe { unsafe {
self.as_mut().begin_remove_rows(
&QModelIndex::default(),
index,
index,
);
self.as_mut() self.as_mut()
.begin_remove_rows(&QModelIndex::default(), index, index); .service_items_mut()
self.as_mut().service_items_mut().remove(index as usize); .remove(index as usize);
self.as_mut().end_remove_rows(); self.as_mut().end_remove_rows();
} }
} }
@ -200,13 +209,19 @@ mod service_item_model {
self.as_mut().add_service_item(&service_item); self.as_mut().add_service_item(&service_item);
} }
fn add_service_item(mut self: Pin<&mut Self>, service_item: &ServiceItm) { fn add_service_item(
mut self: Pin<&mut Self>,
service_item: &ServiceItm,
) {
let index = self.as_ref().service_items().len() as i32; let index = self.as_ref().service_items().len() as i32;
println!("{:?}", service_item); println!("{:?}", service_item);
let service_item = service_item.clone(); let service_item = service_item.clone();
unsafe { unsafe {
self.as_mut() self.as_mut().begin_insert_rows(
.begin_insert_rows(&QModelIndex::default(), index, index); &QModelIndex::default(),
index,
index,
);
self.as_mut().service_items_mut().push(service_item); self.as_mut().service_items_mut().push(service_item);
self.as_mut().end_insert_rows(); self.as_mut().end_insert_rows();
} }
@ -250,11 +265,18 @@ mod service_item_model {
self.as_mut().insert_service_item(&service_item, index); self.as_mut().insert_service_item(&service_item, index);
} }
fn insert_service_item(mut self: Pin<&mut Self>, service_item: &ServiceItm, id: i32) { fn insert_service_item(
mut self: Pin<&mut Self>,
service_item: &ServiceItm,
id: i32,
) {
let service_item = service_item.clone(); let service_item = service_item.clone();
unsafe { unsafe {
self.as_mut() self.as_mut().begin_insert_rows(
.begin_insert_rows(&QModelIndex::default(), id, id); &QModelIndex::default(),
id,
id,
);
self.as_mut() self.as_mut()
.service_items_mut() .service_items_mut()
.insert(id as usize, service_item); .insert(id as usize, service_item);
@ -263,7 +285,10 @@ mod service_item_model {
} }
#[qinvokable] #[qinvokable]
pub fn get_item(self: Pin<&mut Self>, index: i32) -> QMap_QString_QVariant { pub fn get_item(
self: Pin<&mut Self>,
index: i32,
) -> QMap_QString_QVariant {
println!("{index}"); println!("{index}");
let mut map = QMap_QString_QVariant::default(); let mut map = QMap_QString_QVariant::default();
let idx = self.index(index, 0, &QModelIndex::default()); let idx = self.index(index, 0, &QModelIndex::default());
@ -272,7 +297,9 @@ mod service_item_model {
} }
let rn = self.as_ref().role_names(); let rn = self.as_ref().role_names();
let rn_iter = rn.iter(); let rn_iter = rn.iter();
if let Some(service_item) = self.rust().service_items.get(index as usize) { if let Some(service_item) =
self.rust().service_items.get(index as usize)
{
for i in rn_iter { for i in rn_iter {
map.insert( map.insert(
QString::from(&i.1.to_string()), QString::from(&i.1.to_string()),
@ -299,31 +326,52 @@ mod service_item_model {
} }
#[qinvokable] #[qinvokable]
pub fn move_down(mut self: Pin<&mut Self>, index: i32) -> bool { pub fn move_down(
mut self: Pin<&mut Self>,
index: i32,
) -> bool {
todo!(); todo!();
} }
#[qinvokable] #[qinvokable]
pub fn select(mut self: Pin<&mut Self>, index: i32) -> bool { pub fn select(mut self: Pin<&mut Self>, index: i32) -> bool {
let rc = self.as_ref().count() - 1; let rc = self.as_ref().count() - 1;
let tl = &self.as_ref().index(0, 0, &QModelIndex::default()); let tl =
let br = &self.as_ref().index(rc, 0, &QModelIndex::default()); &self.as_ref().index(0, 0, &QModelIndex::default());
let br =
&self.as_ref().index(rc, 0, &QModelIndex::default());
let mut vector_roles = QVector_i32::default(); let mut vector_roles = QVector_i32::default();
vector_roles.append(self.get_role(Role::SelectedRole)); vector_roles.append(self.get_role(Role::SelectedRole));
for service_item in self.as_mut().service_items_mut().iter_mut() { for service_item in
self.as_mut().service_items_mut().iter_mut()
{
// println!("service_item is deactivating {:?}", i); // println!("service_item is deactivating {:?}", i);
service_item.selected = false; service_item.selected = false;
} }
if let Some(service_item) = self.as_mut().service_items_mut().get_mut(index as usize) { if let Some(service_item) = self
.as_mut()
.service_items_mut()
.get_mut(index as usize)
{
println!("selecting-item: {:?}", index); println!("selecting-item: {:?}", index);
println!("service_item_title: {:?}", service_item.name); println!(
println!("service_item_background: {:?}", service_item.background); "service_item_title: {:?}",
service_item.name
);
println!(
"service_item_background: {:?}",
service_item.background
);
println!( println!(
"service_item_background_type: {:?}", "service_item_background_type: {:?}",
service_item.background_type service_item.background_type
); );
service_item.selected = true; service_item.selected = true;
self.as_mut().emit_data_changed(tl, br, &vector_roles); self.as_mut().emit_data_changed(
tl,
br,
&vector_roles,
);
// We use this signal generated by our signals enum to tell QML that // We use this signal generated by our signals enum to tell QML that
// the selected service_item has changed which is used to reposition views. // the selected service_item has changed which is used to reposition views.
self.as_mut().emit_selected_changed(); self.as_mut().emit_selected_changed();
@ -334,31 +382,55 @@ mod service_item_model {
} }
#[qinvokable] #[qinvokable]
pub fn select_items(mut self: Pin<&mut Self>, items: QMap_QString_QVariant) -> bool { pub fn select_items(
mut self: Pin<&mut Self>,
items: QMap_QString_QVariant,
) -> bool {
todo!(); todo!();
} }
#[qinvokable] #[qinvokable]
pub fn activate(mut self: Pin<&mut Self>, index: i32) -> bool { pub fn activate(
mut self: Pin<&mut Self>,
index: i32,
) -> bool {
let rc = self.as_ref().count() - 1; let rc = self.as_ref().count() - 1;
let tl = &self.as_ref().index(0, 0, &QModelIndex::default()); let tl =
let br = &self.as_ref().index(rc, 0, &QModelIndex::default()); &self.as_ref().index(0, 0, &QModelIndex::default());
let br =
&self.as_ref().index(rc, 0, &QModelIndex::default());
let mut vector_roles = QVector_i32::default(); let mut vector_roles = QVector_i32::default();
vector_roles.append(self.get_role(Role::ActiveRole)); vector_roles.append(self.get_role(Role::ActiveRole));
for service_item in self.as_mut().service_items_mut().iter_mut() { for service_item in
self.as_mut().service_items_mut().iter_mut()
{
// println!("service_item is deactivating {:?}", i); // println!("service_item is deactivating {:?}", i);
service_item.active = false; service_item.active = false;
} }
if let Some(service_item) = self.as_mut().service_items_mut().get_mut(index as usize) { if let Some(service_item) = self
.as_mut()
.service_items_mut()
.get_mut(index as usize)
{
println!("service_item is activating {:?}", index); println!("service_item is activating {:?}", index);
println!("service_item_title: {:?}", service_item.name); println!(
println!("service_item_background: {:?}", service_item.background); "service_item_title: {:?}",
service_item.name
);
println!(
"service_item_background: {:?}",
service_item.background
);
println!( println!(
"service_item_background_type: {:?}", "service_item_background_type: {:?}",
service_item.background_type service_item.background_type
); );
service_item.active = true; service_item.active = true;
self.as_mut().emit_data_changed(tl, br, &vector_roles); self.as_mut().emit_data_changed(
tl,
br,
&vector_roles,
);
// We use this signal generated by our signals enum to tell QML that // We use this signal generated by our signals enum to tell QML that
// the active service_item has changed which is used to reposition views. // the active service_item has changed which is used to reposition views.
self.as_mut().emit_active_changed(); self.as_mut().emit_active_changed();
@ -369,87 +441,244 @@ mod service_item_model {
} }
#[qinvokable] #[qinvokable]
pub fn deactivate(mut self: Pin<&mut Self>, index: i32) -> bool { pub fn deactivate(
mut self: Pin<&mut Self>,
index: i32,
) -> bool {
todo!(); todo!();
let rc = self.as_ref().count() - 1;
let tl =
&self.as_ref().index(0, 0, &QModelIndex::default());
let br =
&self.as_ref().index(rc, 0, &QModelIndex::default());
let mut vector_roles = QVector_i32::default();
vector_roles.append(self.get_role(Role::ActiveRole));
if let Some(service_item) = self
.as_mut()
.service_items_mut()
.get_mut(index as usize)
{
println!("service_item is activating {:?}", index);
println!(
"service_item_title: {:?}",
service_item.name
);
println!(
"service_item_background: {:?}",
service_item.background
);
println!(
"service_item_background_type: {:?}",
service_item.background_type
);
service_item.active = false;
self.as_mut().emit_data_changed(
tl,
br,
&vector_roles,
);
// We use this signal generated by our signals enum to tell QML that
// the active service_item has changed which is used to reposition views.
self.as_mut().emit_active_changed();
true
} else {
false
}
} }
#[qinvokable] #[qinvokable]
pub fn save(mut self: Pin<&mut Self>, file: QUrl) -> bool { pub fn save(mut self: Pin<&mut Self>, file: QUrl) -> bool {
todo!(); println!("file is: {file}");
let lfr = fs::File::open(
file.to_local_file().unwrap_or_default().to_string(),
);
if let Ok(lf) = &lfr {
println!("archive: {:?}", lf);
let encoder = Encoder::new(lf, 22).unwrap();
let mut tar = Builder::new(encoder);
true
} else {
false
}
} }
#[qinvokable] #[qinvokable]
pub fn load(mut self: Pin<&mut Self>, file: QUrl) -> bool { pub fn load(mut self: Pin<&mut Self>, file: QUrl) -> bool {
// todo!();
println!("file is: {file}"); println!("file is: {file}");
let lfr = fs::File::open(file.to_local_file().unwrap_or_default().to_string()); let lfr = fs::File::open(
if let Ok(lf) = lfr { file.to_local_file().unwrap_or_default().to_string(),
println!("{:?}", lf); );
let mut datadir = dirs::data_dir().unwrap();
datadir.push("lumina");
datadir.push("temp");
println!("datadir: {:?}", datadir);
fs::create_dir_all(&datadir);
if let Ok(lf) = &lfr {
println!("archive: {:?}", lf);
let dec = Decoder::new(lf).unwrap(); let dec = Decoder::new(lf).unwrap();
let mut tar = Archive::new(dec); let mut tar = Archive::new(dec);
for file in tar.entries().unwrap() { for mut file in
let mut file = file.unwrap(); tar.entries().unwrap().filter_map(|e| e.ok())
// Inspect metadata about the file {
println!("filename: {:?}", file.header().path().unwrap()); let mut file_path = datadir.clone();
println!("size: {:?}", file.header().size().unwrap()); file_path.push(file.path().unwrap());
// Inspect metadata about each file
if file.header().path().unwrap().to_str().unwrap() == "servicelist.json" { println!("filename: {:?}", file.path().unwrap());
println!("THIS ONE HERE CAPTAIN!"); println!("size: {:?}", file.size());
if !file_path.exists() {
let mut s = String::new(); file.unpack_in(&datadir);
file.read_to_string(&mut s);
let ds: Value = serde_json::from_str(&s).unwrap();
for obj in ds.as_array().unwrap() {
println!("objname: {:?}", obj.get("name").unwrap().as_str().unwrap());
println!("objtype: {:?}", obj.get("type").unwrap().as_str().unwrap());
let name = QString::from(obj.get("name").unwrap().as_str().unwrap());
let ty = QString::from(obj.get("type").unwrap().as_str().unwrap());
// both audio and background will need to know if
// it exists on disk, if not use the flat version
let audio = QString::from(obj.get("audio").unwrap().as_str().unwrap());
let background =
QString::from(obj.get("background").unwrap().as_str().unwrap());
let background_type =
QString::from(obj.get("backgroundType").unwrap().as_str().unwrap());
let font = QString::from(obj.get("font").unwrap().as_str().unwrap());
let font_size = obj.get("fontSize").unwrap().as_i64().unwrap() as i32;
let looping = obj.get("loop").unwrap().as_bool().unwrap();
let slide_count =
obj.get("slideNumber").unwrap().as_i64().unwrap() as i32;
let mut video_start_time = f32::default();
if let Some(video_start_value) = obj.get("video_start_time") {
video_start_time = video_start_value.as_f64().unwrap() as f32;
}
let mut video_end_time = f32::default();
if let Some(video_end_value) = obj.get("video_end_time") {
video_end_time = video_end_value.as_f64().unwrap() as f32;
}
let text_array = obj.get("text").unwrap().as_array().unwrap();
let mut text_list = QList_QString::default();
for txt in text_array {
text_list.append(QString::from(txt.as_str().unwrap()));
}
let text = QStringList::from(&text_list);
let service_item = ServiceItm {
name,
ty,
text,
background,
background_type,
audio,
font,
font_size,
slide_count,
looping,
video_start_time,
video_end_time,
..Default::default()
};
self.as_mut().add_service_item(&service_item);
}
println!("Loaded Service: {:?}", ds);
} }
}
let mut service_path = datadir.clone();
service_path.push("servicelist.json");
// let mut service_list =
// fs::File::open(service_path).unwrap();
let mut s = fs::read_to_string(service_path).unwrap();
// service_list.read_to_string(&mut s);
let ds: Value = serde_json::from_str(&s).unwrap();
for obj in ds.as_array().unwrap() {
println!(
"objname: {:?}",
obj.get("name").unwrap().as_str().unwrap()
);
println!(
"objtype: {:?}",
obj.get("type").unwrap().as_str().unwrap()
);
let name = QString::from(
obj.get("name").unwrap().as_str().unwrap(),
);
let ty = QString::from(
obj.get("type").unwrap().as_str().unwrap(),
);
// both audio and background will need to know if
// it exists on disk, if not use the flat version
let audio_string =
obj.get("audio").unwrap().as_str().unwrap();
let mut audio;
if !Path::new(&audio_string).exists() {
println!("#$#$#$#$#$#");
println!("The audio doesn't exist");
println!("#$#$#$#$#$#");
let string = obj
.get("flatAudio")
.unwrap()
.as_str()
.unwrap();
println!("before_audio_str: {:?}", string);
let mut audio_path = datadir.clone();
audio_path.push(string);
// Needed to ensure QML images and mpv will find the audio
let mut final_string =
audio_path.to_str().unwrap().to_owned();
final_string.insert_str(0, "file://");
audio = QString::from(&final_string);
println!("after_audio_str: {:?}", string);
} else {
audio = QString::from(audio_string);
}
let bgstr = obj
.get("background")
.unwrap()
.as_str()
.unwrap();
let mut background;
// lets test to see if the background exists on disk.
// if not we can use the flat version
if !Path::new(&bgstr).exists() {
println!("#$#$#$#$#$#");
println!("The background doesn't exist");
println!("#$#$#$#$#$#");
let string = obj
.get("flatBackground")
.unwrap()
.as_str()
.unwrap();
println!("before_bgstr: {:?}", string);
let mut bgpath = datadir.clone();
bgpath.push(string);
// Needed to ensure QML images and mpv will find the background
let mut final_string =
bgpath.to_str().unwrap().to_owned();
final_string.insert_str(0, "file://");
background = QString::from(&final_string);
println!("after_bgstr: {:?}", string);
} else {
background = QString::from(bgstr);
}
println!("realbg: {:?}", background);
let background_type = QString::from(
obj.get("backgroundType")
.unwrap()
.as_str()
.unwrap(),
);
let font = QString::from(
obj.get("font").unwrap().as_str().unwrap(),
);
let font_size = obj
.get("fontSize")
.unwrap()
.as_i64()
.unwrap()
as i32;
let looping =
obj.get("loop").unwrap().as_bool().unwrap();
let slide_count =
obj.get("slideNumber")
.unwrap()
.as_i64()
.unwrap() as i32;
let mut video_start_time = f32::default();
if let Some(video_start_value) =
obj.get("video_start_time")
{
video_start_time =
video_start_value.as_f64().unwrap()
as f32;
}
let mut video_end_time = f32::default();
if let Some(video_end_value) =
obj.get("video_end_time")
{
video_end_time =
video_end_value.as_f64().unwrap() as f32;
}
let text_array =
obj.get("text").unwrap().as_array().unwrap();
let mut text_list = QList_QString::default();
for txt in text_array {
text_list.append(QString::from(
txt.as_str().unwrap(),
));
}
let text = QStringList::from(&text_list);
let service_item = ServiceItm {
name,
ty,
text,
background,
background_type,
audio,
font,
font_size,
slide_count,
looping,
video_start_time,
video_end_time,
..Default::default()
};
self.as_mut().add_service_item(&service_item);
println!("Loaded Service: {:?}", ds);
// // files implement the Read trait // // files implement the Read trait
} }
true true
@ -496,7 +725,9 @@ mod service_item_model {
first: i32, first: i32,
last: i32, last: i32,
); );
unsafe fn end_insert_rows(self: Pin<&mut qobject::ServiceItemMod>); unsafe fn end_insert_rows(
self: Pin<&mut qobject::ServiceItemMod>,
);
unsafe fn begin_remove_rows( unsafe fn begin_remove_rows(
self: Pin<&mut qobject::ServiceItemMod>, self: Pin<&mut qobject::ServiceItemMod>,
@ -504,16 +735,25 @@ mod service_item_model {
first: i32, first: i32,
last: i32, last: i32,
); );
unsafe fn end_remove_rows(self: Pin<&mut qobject::ServiceItemMod>); unsafe fn end_remove_rows(
self: Pin<&mut qobject::ServiceItemMod>,
);
unsafe fn begin_reset_model(self: Pin<&mut qobject::ServiceItemMod>); unsafe fn begin_reset_model(
unsafe fn end_reset_model(self: Pin<&mut qobject::ServiceItemMod>); self: Pin<&mut qobject::ServiceItemMod>,
);
unsafe fn end_reset_model(
self: Pin<&mut qobject::ServiceItemMod>,
);
} }
#[cxx_qt::inherit] #[cxx_qt::inherit]
unsafe extern "C++" { unsafe extern "C++" {
#[cxx_name = "canFetchMore"] #[cxx_name = "canFetchMore"]
fn base_can_fetch_more(self: &qobject::ServiceItemMod, parent: &QModelIndex) -> bool; fn base_can_fetch_more(
self: &qobject::ServiceItemMod,
parent: &QModelIndex,
) -> bool;
fn index( fn index(
self: &qobject::ServiceItemMod, self: &qobject::ServiceItemMod,
@ -527,13 +767,17 @@ mod service_item_model {
impl qobject::ServiceItemMod { impl qobject::ServiceItemMod {
#[qinvokable(cxx_override)] #[qinvokable(cxx_override)]
fn data(&self, index: &QModelIndex, role: i32) -> QVariant { fn data(&self, index: &QModelIndex, role: i32) -> QVariant {
if let Some(service_item) = self.service_items().get(index.row() as usize) { if let Some(service_item) =
self.service_items().get(index.row() as usize)
{
return match role { return match role {
0 => QVariant::from(&service_item.name), 0 => QVariant::from(&service_item.name),
1 => QVariant::from(&service_item.ty), 1 => QVariant::from(&service_item.ty),
2 => QVariant::from(&service_item.audio), 2 => QVariant::from(&service_item.audio),
3 => QVariant::from(&service_item.background), 3 => QVariant::from(&service_item.background),
4 => QVariant::from(&service_item.background_type), 4 => {
QVariant::from(&service_item.background_type)
}
5 => QVariant::from(&service_item.text), 5 => QVariant::from(&service_item.text),
6 => QVariant::from(&service_item.font), 6 => QVariant::from(&service_item.font),
7 => QVariant::from(&service_item.font_size), 7 => QVariant::from(&service_item.font_size),
@ -541,8 +785,12 @@ mod service_item_model {
9 => QVariant::from(&service_item.active), 9 => QVariant::from(&service_item.active),
10 => QVariant::from(&service_item.selected), 10 => QVariant::from(&service_item.selected),
11 => QVariant::from(&service_item.looping), 11 => QVariant::from(&service_item.looping),
12 => QVariant::from(&service_item.video_start_time), 12 => {
13 => QVariant::from(&service_item.video_end_time), QVariant::from(&service_item.video_start_time)
}
13 => {
QVariant::from(&service_item.video_end_time)
}
_ => QVariant::default(), _ => QVariant::default(),
}; };
} }
@ -562,17 +810,33 @@ mod service_item_model {
roles.insert(0, cxx_qt_lib::QByteArray::from("name")); roles.insert(0, cxx_qt_lib::QByteArray::from("name"));
roles.insert(1, cxx_qt_lib::QByteArray::from("ty")); roles.insert(1, cxx_qt_lib::QByteArray::from("ty"));
roles.insert(2, cxx_qt_lib::QByteArray::from("audio")); roles.insert(2, cxx_qt_lib::QByteArray::from("audio"));
roles.insert(3, cxx_qt_lib::QByteArray::from("background")); roles.insert(
roles.insert(4, cxx_qt_lib::QByteArray::from("backgroundType")); 3,
cxx_qt_lib::QByteArray::from("background"),
);
roles.insert(
4,
cxx_qt_lib::QByteArray::from("backgroundType"),
);
roles.insert(5, cxx_qt_lib::QByteArray::from("text")); roles.insert(5, cxx_qt_lib::QByteArray::from("text"));
roles.insert(6, cxx_qt_lib::QByteArray::from("font")); roles.insert(6, cxx_qt_lib::QByteArray::from("font"));
roles.insert(7, cxx_qt_lib::QByteArray::from("fontSize")); roles.insert(7, cxx_qt_lib::QByteArray::from("fontSize"));
roles.insert(8, cxx_qt_lib::QByteArray::from("slideCount")); roles.insert(
8,
cxx_qt_lib::QByteArray::from("slideCount"),
);
roles.insert(9, cxx_qt_lib::QByteArray::from("active")); roles.insert(9, cxx_qt_lib::QByteArray::from("active"));
roles.insert(10, cxx_qt_lib::QByteArray::from("selected")); roles
.insert(10, cxx_qt_lib::QByteArray::from("selected"));
roles.insert(11, cxx_qt_lib::QByteArray::from("looping")); roles.insert(11, cxx_qt_lib::QByteArray::from("looping"));
roles.insert(12, cxx_qt_lib::QByteArray::from("videoStartTime")); roles.insert(
roles.insert(13, cxx_qt_lib::QByteArray::from("videoEndTime")); 12,
cxx_qt_lib::QByteArray::from("videoStartTime"),
);
roles.insert(
13,
cxx_qt_lib::QByteArray::from("videoEndTime"),
);
roles roles
} }