oma-pm 0.60.0

APT package manager API abstraction library
Documentation
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
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
use std::fmt::Display;

use ahash::HashMap;
use cxx::UniquePtr;
use oma_apt::{
    BaseDep, DepType, Dependency, Package, PackageFile, Version,
    cache::Cache,
    raw::{IntoRawIter, PkgIterator, VerIterator},
    records::RecordField,
};
use oma_utils::human_bytes::HumanBytes;
use serde::{Deserialize, Serialize};
use thiserror::Error;

use crate::apt::{OmaAptError, OmaAptResult};

#[derive(Debug, Serialize, Deserialize)]
pub struct OmaDependency {
    pub name: String,
    pub comp_symbol: Option<String>,
    pub ver: Option<String>,
    pub target_ver: Option<String>,
    pub comp_ver: Option<String>,
}

impl From<&BaseDep<'_>> for OmaDependency {
    fn from(dep: &BaseDep) -> Self {
        Self {
            name: dep.name().to_owned(),
            comp_symbol: dep.comp_type().map(|x| x.to_string()),
            ver: dep.version().map(|x| x.to_string()),
            target_ver: dep.target_ver().ok().map(|x| x.to_string()),
            comp_ver: dep
                .comp_type()
                .and_then(|x| Some(format!("{x} {}", dep.version()?))),
        }
    }
}

#[derive(Debug, Serialize, Deserialize)]
pub struct OmaDependencyGroup(Vec<Vec<OmaDependency>>);

impl OmaDependencyGroup {
    pub fn inner(self) -> Vec<Vec<OmaDependency>> {
        self.0
    }
}

impl Display for OmaDependencyGroup {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        for (i, d) in self.0.iter().enumerate() {
            if d.len() == 1 {
                // 如果数组长度为一,则肯定第一个位置有值
                // 因此直接 unwrap
                let dep = d.first().unwrap();
                f.write_str(&dep.name)?;
                if let Some(comp) = &dep.comp_ver {
                    f.write_str(&format!(" ({comp})"))?;
                }
                if i != self.0.len() - 1 {
                    f.write_str(", ")?;
                }
            } else {
                let total = d.len() - 1;
                for (num, base_dep) in d.iter().enumerate() {
                    f.write_str(&base_dep.name)?;
                    if let Some(comp) = &base_dep.comp_ver {
                        f.write_str(&format!(" ({comp})"))?;
                    }
                    if i != self.0.len() - 1 {
                        if num != total {
                            f.write_str(" | ")?;
                        } else {
                            f.write_str(", ")?;
                        }
                    }
                }
            }
        }

        Ok(())
    }
}

impl OmaDependency {
    pub fn map_deps(deps: &[Dependency]) -> OmaDependencyGroup {
        let mut res = vec![];

        for dep in deps {
            if dep.is_or() {
                let mut v = vec![];
                for base_dep in dep.iter() {
                    v.push(Self::from(base_dep));
                }
                res.push(v);
            } else {
                let lone_dep = dep.first();
                res.push(vec![Self::from(lone_dep)]);
            }
        }

        OmaDependencyGroup(res)
    }
}

#[derive(Debug, Eq, PartialEq, Hash, Deserialize, Serialize)]
pub enum OmaDepType {
    Depends,
    PreDepends,
    Suggests,
    Recommends,
    Conflicts,
    Replaces,
    Obsoletes,
    Breaks,
    Enhances,
}

impl Display for OmaDepType {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "{self:?}")
    }
}

impl From<&DepType> for OmaDepType {
    fn from(v: &oma_apt::DepType) -> Self {
        match v {
            oma_apt::DepType::Depends => OmaDepType::Depends,
            oma_apt::DepType::PreDepends => OmaDepType::PreDepends,
            oma_apt::DepType::Suggests => OmaDepType::Suggests,
            oma_apt::DepType::Recommends => OmaDepType::Recommends,
            oma_apt::DepType::Conflicts => OmaDepType::Conflicts,
            oma_apt::DepType::Replaces => OmaDepType::Replaces,
            oma_apt::DepType::Obsoletes => OmaDepType::Obsoletes,
            oma_apt::DepType::DpkgBreaks => OmaDepType::Breaks,
            oma_apt::DepType::Enhances => OmaDepType::Enhances,
        }
    }
}

/// PkgInfo - For storing package and version information
///
/// Note: that this should be used before the apt `cache` drop, otherwise a segfault will occur.
pub struct OmaPackage {
    pub version_raw: UniquePtr<VerIterator>,
    pub raw_pkg: UniquePtr<PkgIterator>,
}

pub struct OmaPackageWithoutVersion {
    pub raw_pkg: UniquePtr<PkgIterator>,
}

#[derive(Debug, Error)]
#[error("BUG: pointer should is some")]
pub struct PtrIsNone;

#[derive(Debug, Deserialize, Serialize)]
/// Represents information about a software package.
pub struct PackageInfo {
    /// Name of the package.
    pub package: Box<str>,
    /// Version of the package.
    pub version: Box<str>,
    /// Section or category of the package.
    pub section: Box<str>,
    /// Maintainer of the package.
    pub maintainer: String,
    /// Size of the package after installation(in bytes).
    pub install_size: u64,
    /// Dependency map for the package.
    pub dep_map: HashMap<OmaDepType, OmaDependencyGroup>,
    /// Download size of the package (in bytes).
    pub download_size: u64,
    /// APT sources from which the package will be downloaded.
    pub apt_sources: Vec<AptSource>,
    /// Detailed description of the package.
    pub description: String,
    /// Brief description of the package.
    pub short_description: String,
}

impl Display for PackageInfo {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        let PackageInfo {
            package,
            version,
            section,
            maintainer,
            install_size,
            dep_map,
            download_size,
            apt_sources,
            description,
            ..
        } = self;

        writeln!(f, "Package: {package}")?;
        writeln!(f, "Version: {version}")?;
        writeln!(f, "Section: {section}")?;
        writeln!(f, "Maintainer: {maintainer}")?;
        writeln!(f, "Install-Size: {}", HumanBytes(*install_size))?;
        for (k, v) in dep_map {
            writeln!(f, "{k}: {v}")?;
        }
        writeln!(f, "Download-Size: {}", HumanBytes(*download_size))?;
        write!(f, "APT-Sources:")?;
        let apt_sources_without_dpkg = apt_sources
            .iter()
            .filter(|x| x.index_type.as_deref() != Some("Debian dpkg status file"))
            .collect::<Vec<_>>();

        match apt_sources_without_dpkg.len() {
            0 => writeln!(f, " {}", &apt_sources[0])?,
            1 => writeln!(f, " {}", &apt_sources_without_dpkg[0])?,
            2.. => {
                writeln!(f)?;
                for i in apt_sources_without_dpkg {
                    writeln!(f, "  {i}")?;
                }
            }
        }

        writeln!(f, "Description: {description}")?;

        Ok(())
    }
}

#[derive(Debug, Deserialize, Serialize)]
pub struct AptSource {
    pub archive: Option<Box<str>>,
    pub component: Option<Box<str>>,
    pub arch: Option<Box<str>>,
    pub index_type: Option<Box<str>>,
    pub archive_uri: String,
}

impl From<PackageFile<'_>> for AptSource {
    fn from(value: PackageFile<'_>) -> Self {
        let index = value.index_file();
        let archive_uri = index.archive_uri("");

        Self {
            archive: value.archive().map(Box::from),
            component: value.component().map(Box::from),
            arch: value.arch().map(Box::from),
            index_type: value.index_type().map(Box::from),
            archive_uri,
        }
    }
}

impl Display for AptSource {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "{}", &self.archive_uri)?;

        if let Some(archive) = &self.archive {
            write!(f, " {archive}")?;
        }

        if let Some(comp) = &self.component {
            write!(f, "/{comp}")?;
        }

        if let Some(arch) = &self.arch {
            write!(f, " {arch}")?;
        }

        if let Some(ft) = &self.index_type {
            let ft = match ft.as_ref() {
                "Debian Package Index" => "Packages",
                "Debian Translation Index" => "Translation",
                _ => "",
            };

            write!(f, " {ft}")?;
        }

        Ok(())
    }
}

impl OmaPackage {
    pub fn new(version: &Version, pkg: &Package) -> Result<Self, PtrIsNone> {
        // 直接传入 &Version 会遇到 version.uris 生命周期问题,所以这里传入 RawVersion,然后就地创建 Version
        let raw_pkg = unsafe { pkg.unique() }.make_safe().ok_or(PtrIsNone)?;
        let version_raw = unsafe { version.unique() }.make_safe().ok_or(PtrIsNone)?;

        Ok(Self {
            version_raw,
            raw_pkg,
        })
    }

    pub fn try_clone(&self) -> Result<Self, PtrIsNone> {
        Ok(Self {
            version_raw: unsafe { self.version_raw.unique() }
                .make_safe()
                .ok_or(PtrIsNone)?,
            raw_pkg: unsafe { self.raw_pkg.unique() }
                .make_safe()
                .ok_or(PtrIsNone)?,
        })
    }

    pub fn pkg_info(&self, cache: &Cache) -> OmaAptResult<PackageInfo> {
        let package: Box<str> = Box::from(self.raw_pkg.fullname(true));
        let version: Box<str> = Box::from(self.version_raw.version());
        let ver = Version::new(
            unsafe { self.version_raw.unique() }
                .make_safe()
                .ok_or(OmaAptError::PtrIsNone(PtrIsNone))?,
            cache,
        );
        let section: Box<str> = Box::from(ver.section().unwrap_or("unknown"));
        let maintainer = ver
            .get_record(RecordField::Maintainer)
            .unwrap_or_else(|| "unknown".to_string());
        let install_size = ver.installed_size();

        let deps_map = self.get_deps(cache)?;

        let download_size = ver.size();

        let pkg_files = ver.package_files().map(AptSource::from).collect::<Vec<_>>();

        let description = ver
            .description()
            .unwrap_or_else(|| "No description".to_string());

        let short_description = ver
            .summary()
            .unwrap_or_else(|| "No description".to_string());

        Ok(PackageInfo {
            package,
            version,
            section,
            maintainer,
            install_size,
            dep_map: deps_map,
            download_size,
            apt_sources: pkg_files,
            description,
            short_description,
        })
    }

    pub fn package<'a>(&'a self, cache: &'a Cache) -> Package<'a> {
        Package::new(cache, unsafe { self.raw_pkg.unique() })
    }

    pub fn version<'a>(&'a self, cache: &'a Cache) -> Version<'a> {
        Version::new(unsafe { self.version_raw.unique() }, cache)
    }

    pub fn is_candidate_version(&self, cache: &Cache) -> bool {
        self.package(cache)
            .candidate()
            .is_some_and(|cand| cand == self.version(cache))
    }

    pub fn get_deps(&self, cache: &Cache) -> OmaAptResult<HashMap<OmaDepType, OmaDependencyGroup>> {
        let map = Version::new(
            unsafe { self.version_raw.unique() }
                .make_safe()
                .ok_or(OmaAptError::PtrIsNone(PtrIsNone))?,
            cache,
        )
        .depends_map()
        .iter()
        .map(|(x, y)| (OmaDepType::from(x), OmaDependency::map_deps(y)))
        .collect::<HashMap<_, _>>();

        Ok(map)
    }

    pub fn get_rdeps(
        &self,
        cache: &Cache,
    ) -> OmaAptResult<HashMap<OmaDepType, OmaDependencyGroup>> {
        let map = Package::new(
            cache,
            unsafe { self.raw_pkg.unique() }
                .make_safe()
                .ok_or(OmaAptError::PtrIsNone(PtrIsNone))?,
        )
        .rdepends()
        .iter()
        .map(|(x, y)| (OmaDepType::from(x), OmaDependency::map_deps(y)))
        .collect::<HashMap<_, _>>();

        Ok(map)
    }

    pub fn into_oma_package_without_version(&self) -> Result<OmaPackageWithoutVersion, PtrIsNone> {
        Ok(OmaPackageWithoutVersion {
            raw_pkg: unsafe { self.raw_pkg.unique() }
                .make_safe()
                .ok_or(PtrIsNone)?,
        })
    }
}

impl OmaPackageWithoutVersion {
    pub fn package<'a>(&'a self, cache: &'a Cache) -> Package<'a> {
        Package::new(cache, unsafe { self.raw_pkg.unique() })
    }

    pub fn into_oma_package(&self, version: &Version) -> Result<OmaPackage, PtrIsNone> {
        Ok(OmaPackage {
            version_raw: unsafe { version.unique() }.make_safe().ok_or(PtrIsNone)?,
            raw_pkg: unsafe { self.raw_pkg.unique() }
                .make_safe()
                .ok_or(PtrIsNone)?,
        })
    }
}

impl TryFrom<OmaPackage> for OmaPackageWithoutVersion {
    type Error = PtrIsNone;

    fn try_from(value: OmaPackage) -> Result<Self, Self::Error> {
        value.into_oma_package_without_version()
    }
}

#[test]
fn test_pkginfo_display() {
    use crate::test::TEST_LOCK;
    use oma_apt::new_cache;

    let _lock = TEST_LOCK.lock().unwrap();
    let cache = new_cache!().unwrap();
    let pkg = cache.get("apt").unwrap();
    let version = pkg.candidate().unwrap();
    let info = OmaPackage::new(&version, &pkg).unwrap();
    let info = info.pkg_info(&cache).unwrap();
    println!("{info}");
}