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
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
use crate::utils::*;
use crate::{Callbacks, Error, Result};

use std::ffi::{c_void, CString};
use std::fmt;
use std::os::raw::c_int;
use std::ptr::NonNull;

use alpm_sys::*;
use bitflags::bitflags;

extern "C" {
    pub(crate) fn free(ptr: *mut c_void);
}

#[allow(dead_code)]
pub struct Alpm {
    handle: NonNull<alpm_handle_t>,
    pub(crate) cbs: Callbacks,
}

impl std::fmt::Debug for Alpm {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.debug_struct("Alpm").finish()
    }
}

// TODO unsound: remove
unsafe impl Send for Alpm {}

impl Drop for Alpm {
    fn drop(&mut self) {
        unsafe { alpm_release(self.as_ptr()) };
    }
}

#[derive(Debug, Eq, PartialEq, Copy, Clone, Ord, PartialOrd, Hash)]
pub struct ReleaseError;

impl fmt::Display for ReleaseError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        f.write_str("failed to release alpm")
    }
}

impl std::error::Error for ReleaseError {}

impl Alpm {
    #[doc(alias("alpm_initialize", "initialize"))]
    pub fn new<S: Into<Vec<u8>>>(root: S, db_path: S) -> Result<Alpm> {
        let mut err = alpm_errno_t::ALPM_ERR_OK;
        let root = CString::new(root).unwrap();
        let db_path = CString::new(db_path).unwrap();

        let handle = unsafe { alpm_initialize(root.as_ptr(), db_path.as_ptr(), &mut err) };

        match NonNull::new(handle) {
            None => unsafe { Err(Error::new(err)) },
            Some(handle) => Ok(Alpm {
                handle,
                cbs: Callbacks::default(),
            }),
        }
    }

    pub fn new2(root: &str, db_path: &str) -> Result<Alpm> {
        Alpm::new(root, db_path)
    }

    pub fn release(self) -> std::result::Result<(), ReleaseError> {
        if unsafe { alpm_release(self.as_ptr()) } == 0 {
            std::mem::forget(self);
            Ok(())
        } else {
            std::mem::forget(self);
            Err(ReleaseError)
        }
    }

    pub(crate) unsafe fn from_ptr(handle: *mut alpm_handle_t) -> Alpm {
        Alpm {
            handle: NonNull::new_unchecked(handle),
            cbs: Callbacks::default(),
        }
    }

    pub(crate) fn as_ptr(&self) -> *mut alpm_handle_t {
        self.handle.as_ptr()
    }

    pub(crate) fn check_ret(&self, int: c_int) -> Result<()> {
        if int != 0 {
            Err(self.last_error())
        } else {
            Ok(())
        }
    }

    pub(crate) fn check_null<T>(&self, ptr: *const T) -> Result<()> {
        if ptr.is_null() {
            Err(self.last_error())
        } else {
            Ok(())
        }
    }
}

pub fn version() -> &'static str {
    unsafe { from_cstr(alpm_version()) }
}

bitflags! {
    pub struct Capabilities: u32 {
        const NLS = alpm_caps::ALPM_CAPABILITY_NLS;
        const DOWNLOADER = alpm_caps::ALPM_CAPABILITY_DOWNLOADER;
        const SIGNATURES = alpm_caps::ALPM_CAPABILITY_SIGNATURES;
    }
}

impl Default for Capabilities {
    fn default() -> Capabilities {
        Capabilities::new()
    }
}

impl Capabilities {
    pub fn new() -> Capabilities {
        Capabilities::from_bits(unsafe { alpm_capabilities() as u32 }).unwrap()
    }

    pub fn nls(self) -> bool {
        self.intersects(Capabilities::NLS)
    }

    pub fn downloader(self) -> bool {
        self.intersects(Capabilities::DOWNLOADER)
    }

    pub fn signatures(self) -> bool {
        self.intersects(Capabilities::SIGNATURES)
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::SigLevel;

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

        drop(pkg);
        drop(db);
        assert_eq!(name, "linux");
    }

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

        drop(db);
        assert!(pkgs.len() > 10);
    }
}