1use std::path::Path;
2
3use crate::{
4 Error, Result,
5 format::{DbFile, DbKind},
6};
7
8#[derive(Debug, Clone, Copy, PartialEq, Eq)]
10pub enum FileKind {
11 Regular,
13 Directory,
15 Symlink,
17}
18
19impl Default for FileKind {
20 fn default() -> Self {
21 FileKind::Regular
22 }
23}
24
25#[derive(Debug, Clone, PartialEq, Eq)]
30pub struct FileRecord {
31 pub path: String,
33 pub packages: Vec<String>,
35 pub size: u64,
37 pub kind: FileKind,
39 pub executable: bool,
41 pub target: String,
43}
44
45#[derive(Debug)]
49pub struct PackagesDb {
50 db: DbFile,
51}
52
53impl PackagesDb {
54 pub(crate) fn from_file(db: DbFile) -> Self {
55 Self { db }
56 }
57
58 pub fn open(path: impl AsRef<Path>) -> Result<Self> {
62 let db = DbFile::open(path)?;
63 if db.kind != DbKind::Packages {
64 return Err(Error::InvalidDatabase(
65 "expected a packages database (kind = packages)".into(),
66 ));
67 }
68 Ok(Self { db })
69 }
70
71 pub fn query(&self, query: &str) -> Result<Vec<FileRecord>> {
78 let bucket = DbFile::query_bucket(query);
79 let lines = self.db.bucket_lines(bucket)?;
80
81 let mut records = Vec::new();
82 for line in &lines {
83 let parts: Vec<&str> = line.splitn(7, '\t').collect();
84 if parts.is_empty() {
85 continue;
86 }
87 let path = parts[0];
88 if !path.contains(query) {
89 continue;
90 }
91
92 let record = if parts.len() >= 6 {
93 let kind = match parts[1] {
95 "d" => FileKind::Directory,
96 "s" => FileKind::Symlink,
97 _ => FileKind::Regular,
98 };
99 let size: u64 = parts[2].parse().unwrap_or(0);
100 let executable = parts[3] == "1";
101 let target = parts[4].to_owned();
102 let packages = parts[5]
103 .split(',')
104 .filter(|s| !s.is_empty())
105 .map(str::to_owned)
106 .collect();
107 FileRecord {
108 path: path.to_owned(),
109 packages,
110 size,
111 kind,
112 executable,
113 target,
114 }
115 } else if parts.len() >= 2 {
116 let packages = parts[1]
118 .split(',')
119 .filter(|s| !s.is_empty())
120 .map(str::to_owned)
121 .collect();
122 FileRecord {
123 path: path.to_owned(),
124 packages,
125 size: 0,
126 kind: FileKind::Regular,
127 executable: false,
128 target: String::new(),
129 }
130 } else {
131 continue;
132 };
133
134 records.push(record);
135 }
136 Ok(records)
137 }
138}