1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
use crate::utils::*;

use alpm_sys::*;

use std::cell::UnsafeCell;
use std::ffi::CString;
use std::fmt;
use std::marker::PhantomData;
use std::slice;

#[repr(transparent)]
pub struct File {
    inner: alpm_file_t,
}

unsafe impl Send for File {}
unsafe impl Sync for File {}

impl fmt::Debug for File {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        f.debug_struct("File")
            .field("name", &self.name())
            .field("size", &self.size())
            .field("mode", &self.mode())
            .finish()
    }
}

impl File {
    pub fn name(&self) -> &str {
        unsafe { from_cstr(self.inner.name) }
    }

    pub fn size(&self) -> i64 {
        #[allow(clippy::useless_conversion)]
        self.inner.size.into()
    }

    pub fn mode(&self) -> u32 {
        #[allow(clippy::useless_conversion)]
        self.inner.mode.into()
    }
}

pub struct FileList<'h> {
    inner: UnsafeCell<alpm_filelist_t>,
    _marker: PhantomData<&'h ()>,
}

// TODO: unsafe cell is only used to get a mut pointer even though
// it's never actually mutated
// Upstream code should ask for a const pointer
#[allow(clippy::non_send_fields_in_send_ty)]
unsafe impl<'h> Send for FileList<'h> {}
unsafe impl<'h> Sync for FileList<'h> {}

impl<'h> fmt::Debug for FileList<'h> {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        f.debug_struct("File")
            .field("files", &self.files())
            .finish()
    }
}

impl<'h> FileList<'h> {
    pub(crate) unsafe fn new<'a>(files: alpm_filelist_t) -> FileList<'a> {
        FileList {
            inner: UnsafeCell::new(files),
            _marker: PhantomData,
        }
    }

    pub(crate) fn as_ptr(&self) -> *mut alpm_filelist_t {
        self.inner.get()
    }

    pub fn files(&self) -> &[File] {
        let files = unsafe { *self.as_ptr() };
        if files.files.is_null() {
            &[]
        } else {
            unsafe { slice::from_raw_parts(files.files as *const File, files.count) }
        }
    }

    pub fn contains<S: Into<Vec<u8>>>(&self, path: S) -> Option<File> {
        let path = CString::new(path).unwrap();
        let file = unsafe { alpm_filelist_contains(self.as_ptr(), path.as_ptr()) };

        if file.is_null() {
            None
        } else {
            let file = unsafe { *file };
            Some(File { inner: file })
        }
    }
}

#[cfg(test)]
mod tests {
    use crate::{Alpm, SigLevel};

    #[test]
    fn test_files() {
        let handle = Alpm::new("/", "tests/db").unwrap();
        let db = handle.register_syncdb("core", SigLevel::NONE).unwrap();
        let pkg = db.pkg("linux").unwrap();
        let files = pkg.files();

        assert!(files.files().is_empty());

        let db = handle.localdb();
        let pkg = db.pkg("linux").unwrap();
        let files = pkg.files();

        assert!(!files.files().is_empty());

        let file = files.contains("boot/").unwrap();
        assert_eq!(file.name(), "boot/");
        assert!(files.contains("aaaaa/").is_none());
    }
}