Implement setting permissions, fixes #325
This commit is contained in:
parent
2c8060f93b
commit
051001b9ea
6 changed files with 186 additions and 81 deletions
7
Cargo.lock
generated
7
Cargo.lock
generated
|
|
@ -1549,7 +1549,6 @@ dependencies = [
|
||||||
"tikv-jemallocator",
|
"tikv-jemallocator",
|
||||||
"tokio",
|
"tokio",
|
||||||
"trash",
|
"trash",
|
||||||
"unix_permissions_ext",
|
|
||||||
"url",
|
"url",
|
||||||
"uzers",
|
"uzers",
|
||||||
"vergen",
|
"vergen",
|
||||||
|
|
@ -7231,12 +7230,6 @@ version = "0.2.6"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853"
|
checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "unix_permissions_ext"
|
|
||||||
version = "0.1.2"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "7497808a85e03f612f13e9c5061e4c81cdee86e6c00adfa1096690990ccd08e9"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "untrusted"
|
name = "untrusted"
|
||||||
version = "0.9.0"
|
version = "0.9.0"
|
||||||
|
|
|
||||||
|
|
@ -64,7 +64,6 @@ rust-embed = "8"
|
||||||
slotmap = "1.0.7"
|
slotmap = "1.0.7"
|
||||||
recently-used-xbel = "1.1.0"
|
recently-used-xbel = "1.1.0"
|
||||||
zip = "2.2.2"
|
zip = "2.2.2"
|
||||||
unix_permissions_ext = "0.1.2"
|
|
||||||
uzers = "0.12.1"
|
uzers = "0.12.1"
|
||||||
|
|
||||||
# Completion-based IO runtime to enable io_uring / IOCP file IO support.
|
# Completion-based IO runtime to enable io_uring / IOCP file IO support.
|
||||||
|
|
|
||||||
|
|
@ -227,6 +227,8 @@ extracted = Extracted {$items} {$items ->
|
||||||
} from "{$from}" to "{$to}"
|
} from "{$from}" to "{$to}"
|
||||||
setting-executable-and-launching = Setting "{$name}" as executable and launching
|
setting-executable-and-launching = Setting "{$name}" as executable and launching
|
||||||
set-executable-and-launched = Set "{$name}" as executable and launched
|
set-executable-and-launched = Set "{$name}" as executable and launched
|
||||||
|
setting-permissions = Setting permissions for "{$name}" to {$mode}
|
||||||
|
set-permissions = Set permissions for "{$name}" to {$mode}
|
||||||
moving = Moving {$items} {$items ->
|
moving = Moving {$items} {$items ->
|
||||||
[one] item
|
[one] item
|
||||||
*[other] items
|
*[other] items
|
||||||
|
|
|
||||||
|
|
@ -3546,6 +3546,9 @@ impl Application for App {
|
||||||
//TODO: this will block for a few ms, run in background?
|
//TODO: this will block for a few ms, run in background?
|
||||||
self.mime_app_cache.set_default(mime, id);
|
self.mime_app_cache.set_default(mime, id);
|
||||||
}
|
}
|
||||||
|
tab::Command::SetPermissions(path, mode) => {
|
||||||
|
commands.push(self.operation(Operation::SetPermissions { path, mode }));
|
||||||
|
}
|
||||||
tab::Command::WindowDrag => {
|
tab::Command::WindowDrag => {
|
||||||
if let Some(window_id) = &self.window_id_opt {
|
if let Some(window_id) = &self.window_id_opt {
|
||||||
commands.push(window::drag(*window_id));
|
commands.push(window::drag(*window_id));
|
||||||
|
|
|
||||||
|
|
@ -530,6 +530,11 @@ pub enum Operation {
|
||||||
SetExecutableAndLaunch {
|
SetExecutableAndLaunch {
|
||||||
path: PathBuf,
|
path: PathBuf,
|
||||||
},
|
},
|
||||||
|
/// Set permissions
|
||||||
|
SetPermissions {
|
||||||
|
path: PathBuf,
|
||||||
|
mode: u32,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
|
|
@ -629,6 +634,13 @@ impl Operation {
|
||||||
Self::SetExecutableAndLaunch { path } => {
|
Self::SetExecutableAndLaunch { path } => {
|
||||||
fl!("setting-executable-and-launching", name = file_name(path))
|
fl!("setting-executable-and-launching", name = file_name(path))
|
||||||
}
|
}
|
||||||
|
Self::SetPermissions { path, mode } => {
|
||||||
|
fl!(
|
||||||
|
"setting-permissions",
|
||||||
|
name = file_name(path),
|
||||||
|
mode = format!("{:#03o}", mode)
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -686,6 +698,13 @@ impl Operation {
|
||||||
Self::SetExecutableAndLaunch { path } => {
|
Self::SetExecutableAndLaunch { path } => {
|
||||||
fl!("set-executable-and-launched", name = file_name(path))
|
fl!("set-executable-and-launched", name = file_name(path))
|
||||||
}
|
}
|
||||||
|
Self::SetPermissions { path, mode } => {
|
||||||
|
fl!(
|
||||||
|
"set-permissions",
|
||||||
|
name = file_name(path),
|
||||||
|
mode = format!("{:#03o}", mode)
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -704,7 +723,8 @@ impl Operation {
|
||||||
Self::NewFile { .. }
|
Self::NewFile { .. }
|
||||||
| Self::NewFolder { .. }
|
| Self::NewFolder { .. }
|
||||||
| Self::Rename { .. }
|
| Self::Rename { .. }
|
||||||
| Self::SetExecutableAndLaunch { .. } => false,
|
| Self::SetExecutableAndLaunch { .. }
|
||||||
|
| Self::SetPermissions { .. } => false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1196,6 +1216,25 @@ impl Operation {
|
||||||
.map_err(OperationError::from_str)?;
|
.map_err(OperationError::from_str)?;
|
||||||
Ok(OperationSelection::default())
|
Ok(OperationSelection::default())
|
||||||
}
|
}
|
||||||
|
Self::SetPermissions { path, mode } => {
|
||||||
|
controller.check().await.map_err(OperationError::from_str)?;
|
||||||
|
|
||||||
|
compio::runtime::spawn_blocking(move || -> Result<(), OperationError> {
|
||||||
|
//TODO: what to do on non-Unix systems?
|
||||||
|
#[cfg(unix)]
|
||||||
|
{
|
||||||
|
use std::os::unix::fs::PermissionsExt;
|
||||||
|
let perms = fs::Permissions::from_mode(mode);
|
||||||
|
fs::set_permissions(&path, perms).map_err(OperationError::from_str)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
.map_err(wrap_compio_spawn_error)?
|
||||||
|
.map_err(OperationError::from_str)?;
|
||||||
|
Ok(OperationSelection::default())
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
controller_clone.set_progress(1.0);
|
controller_clone.set_progress(1.0);
|
||||||
|
|
|
||||||
213
src/tab.rs
213
src/tab.rs
|
|
@ -76,7 +76,6 @@ use crate::{
|
||||||
operation::Controller,
|
operation::Controller,
|
||||||
thumbnailer::thumbnailer,
|
thumbnailer::thumbnailer,
|
||||||
};
|
};
|
||||||
use unix_permissions_ext::UNIXPermissionsExt;
|
|
||||||
use uzers::{get_group_by_gid, get_user_by_uid};
|
use uzers::{get_group_by_gid, get_user_by_uid};
|
||||||
|
|
||||||
pub const DOUBLE_CLICK_DURATION: Duration = Duration::from_millis(500);
|
pub const DOUBLE_CLICK_DURATION: Duration = Duration::from_millis(500);
|
||||||
|
|
@ -89,6 +88,27 @@ const THUMBNAIL_SIZE: u32 = (ICON_SIZE_GRID as u32) * (ICON_SCALE_MAX as u32);
|
||||||
|
|
||||||
const DRAG_SCROLL_DISTANCE: f32 = 15.0;
|
const DRAG_SCROLL_DISTANCE: f32 = 15.0;
|
||||||
|
|
||||||
|
static MODE_NAMES: Lazy<Vec<String>> = Lazy::new(|| {
|
||||||
|
vec![
|
||||||
|
// Mode 0
|
||||||
|
fl!("none"),
|
||||||
|
// Mode 1
|
||||||
|
fl!("execute-only"),
|
||||||
|
// Mode 2
|
||||||
|
fl!("write-only"),
|
||||||
|
// Mode 3
|
||||||
|
fl!("write-execute"),
|
||||||
|
// Mode 4
|
||||||
|
fl!("read-only"),
|
||||||
|
// Mode 5
|
||||||
|
fl!("read-execute"),
|
||||||
|
// Mode 6
|
||||||
|
fl!("read-write"),
|
||||||
|
// Mode 7
|
||||||
|
fl!("read-write-execute"),
|
||||||
|
]
|
||||||
|
});
|
||||||
|
|
||||||
static SPECIAL_DIRS: Lazy<HashMap<PathBuf, &'static str>> = Lazy::new(|| {
|
static SPECIAL_DIRS: Lazy<HashMap<PathBuf, &'static str>> = Lazy::new(|| {
|
||||||
let mut special_dirs = HashMap::new();
|
let mut special_dirs = HashMap::new();
|
||||||
if let Some(dir) = dirs::document_dir() {
|
if let Some(dir) = dirs::document_dir() {
|
||||||
|
|
@ -348,58 +368,18 @@ fn format_size(size: u64) -> String {
|
||||||
format!("{} B", size)
|
format!("{} B", size)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
enum PermissionOwner {
|
|
||||||
Owner,
|
const MODE_SHIFT_USER: u32 = 6;
|
||||||
Group,
|
const MODE_SHIFT_GROUP: u32 = 3;
|
||||||
Other,
|
const MODE_SHIFT_OTHER: u32 = 0;
|
||||||
|
|
||||||
|
fn get_mode_part(mode: u32, shift: u32) -> u32 {
|
||||||
|
(mode >> shift) & 0o7
|
||||||
}
|
}
|
||||||
|
|
||||||
fn format_permissions_owner(metadata: &Metadata, owner: PermissionOwner) -> String {
|
fn set_mode_part(mode: u32, shift: u32, bits: u32) -> u32 {
|
||||||
match owner {
|
assert!(bits <= 0o7);
|
||||||
PermissionOwner::Owner => get_user_by_uid(metadata.uid())
|
(mode & !(0o7 << shift)) | (bits << shift)
|
||||||
.and_then(|user| user.name().to_str().map(ToOwned::to_owned))
|
|
||||||
.unwrap_or_default(),
|
|
||||||
PermissionOwner::Group => get_group_by_gid(metadata.gid())
|
|
||||||
.and_then(|group| group.name().to_str().map(ToOwned::to_owned))
|
|
||||||
.unwrap_or_default(),
|
|
||||||
PermissionOwner::Other => String::from(""),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn format_permissions(metadata: &Metadata, owner: PermissionOwner) -> String {
|
|
||||||
let mut mode = 0;
|
|
||||||
if match owner {
|
|
||||||
PermissionOwner::Owner => metadata.permissions().readable_by_owner(),
|
|
||||||
PermissionOwner::Group => metadata.permissions().readable_by_group(),
|
|
||||||
PermissionOwner::Other => metadata.permissions().readable_by_other(),
|
|
||||||
} {
|
|
||||||
mode |= 4;
|
|
||||||
}
|
|
||||||
if match owner {
|
|
||||||
PermissionOwner::Owner => metadata.permissions().writable_by_owner(),
|
|
||||||
PermissionOwner::Group => metadata.permissions().writable_by_group(),
|
|
||||||
PermissionOwner::Other => metadata.permissions().writable_by_other(),
|
|
||||||
} {
|
|
||||||
mode |= 2;
|
|
||||||
}
|
|
||||||
if match owner {
|
|
||||||
PermissionOwner::Owner => metadata.permissions().executable_by_owner(),
|
|
||||||
PermissionOwner::Group => metadata.permissions().executable_by_group(),
|
|
||||||
PermissionOwner::Other => metadata.permissions().executable_by_other(),
|
|
||||||
} {
|
|
||||||
mode |= 1;
|
|
||||||
}
|
|
||||||
match mode {
|
|
||||||
0 => fl!("none"),
|
|
||||||
1 => fl!("execute-only"),
|
|
||||||
2 => fl!("write-only"),
|
|
||||||
3 => fl!("write-execute"),
|
|
||||||
4 => fl!("read-only"),
|
|
||||||
5 => fl!("read-execute"),
|
|
||||||
6 => fl!("read-write"),
|
|
||||||
7 => fl!("read-write-execute"),
|
|
||||||
_ => unreachable!(),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn date_time_formatter(military_time: bool) -> DateTimeFormatter {
|
fn date_time_formatter(military_time: bool) -> DateTimeFormatter {
|
||||||
|
|
@ -1362,6 +1342,7 @@ pub enum Command {
|
||||||
OpenTrash,
|
OpenTrash,
|
||||||
Preview(PreviewKind),
|
Preview(PreviewKind),
|
||||||
SetOpenWith(Mime, String),
|
SetOpenWith(Mime, String),
|
||||||
|
SetPermissions(PathBuf, u32),
|
||||||
WindowDrag,
|
WindowDrag,
|
||||||
WindowToggleMaximize,
|
WindowToggleMaximize,
|
||||||
}
|
}
|
||||||
|
|
@ -1415,6 +1396,7 @@ pub enum Message {
|
||||||
SelectFirst,
|
SelectFirst,
|
||||||
SelectLast,
|
SelectLast,
|
||||||
SetOpenWith(Mime, String),
|
SetOpenWith(Mime, String),
|
||||||
|
SetPermissions(PathBuf, u32),
|
||||||
SetSort(HeadingOptions, bool),
|
SetSort(HeadingOptions, bool),
|
||||||
TabComplete(PathBuf, Vec<(String, PathBuf)>),
|
TabComplete(PathBuf, Vec<(String, PathBuf)>),
|
||||||
Thumbnail(PathBuf, ItemThumbnail),
|
Thumbnail(PathBuf, ItemThumbnail),
|
||||||
|
|
@ -1842,34 +1824,74 @@ impl Item {
|
||||||
)));
|
)));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(not(target_os = "windows"))]
|
#[cfg(unix)]
|
||||||
{
|
if let Some(path) = self.path_opt() {
|
||||||
|
use std::os::unix::fs::MetadataExt;
|
||||||
|
|
||||||
|
let mode = metadata.mode();
|
||||||
|
|
||||||
|
let user_name = get_user_by_uid(metadata.uid())
|
||||||
|
.and_then(|user| user.name().to_str().map(ToOwned::to_owned))
|
||||||
|
.unwrap_or_default();
|
||||||
|
let user_path = path.clone();
|
||||||
settings.push(
|
settings.push(
|
||||||
widget::settings::item::builder(format_permissions_owner(
|
widget::settings::item::builder(user_name)
|
||||||
metadata,
|
.description(fl!("owner"))
|
||||||
PermissionOwner::Owner,
|
.control(widget::dropdown(
|
||||||
))
|
&MODE_NAMES,
|
||||||
.description(fl!("owner"))
|
Some(get_mode_part(mode, MODE_SHIFT_USER).try_into().unwrap()),
|
||||||
.control(widget::text::body(format_permissions(
|
move |selected| {
|
||||||
metadata,
|
Message::SetPermissions(
|
||||||
PermissionOwner::Owner,
|
user_path.clone(),
|
||||||
))),
|
set_mode_part(
|
||||||
|
mode,
|
||||||
|
MODE_SHIFT_USER,
|
||||||
|
selected.try_into().unwrap(),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
},
|
||||||
|
)),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
let group_name = get_group_by_gid(metadata.gid())
|
||||||
|
.and_then(|group| group.name().to_str().map(ToOwned::to_owned))
|
||||||
|
.unwrap_or_default();
|
||||||
|
let group_path = path.clone();
|
||||||
settings.push(
|
settings.push(
|
||||||
widget::settings::item::builder(format_permissions_owner(
|
widget::settings::item::builder(group_name)
|
||||||
metadata,
|
.description(fl!("group"))
|
||||||
PermissionOwner::Group,
|
.control(widget::dropdown(
|
||||||
))
|
&MODE_NAMES,
|
||||||
.description(fl!("group"))
|
Some(get_mode_part(mode, MODE_SHIFT_GROUP).try_into().unwrap()),
|
||||||
.control(widget::text::body(format_permissions(
|
move |selected| {
|
||||||
metadata,
|
Message::SetPermissions(
|
||||||
PermissionOwner::Group,
|
group_path.clone(),
|
||||||
))),
|
set_mode_part(
|
||||||
|
mode,
|
||||||
|
MODE_SHIFT_GROUP,
|
||||||
|
selected.try_into().unwrap(),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
},
|
||||||
|
)),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
let other_path = path.clone();
|
||||||
settings.push(widget::settings::item::builder(fl!("other")).control(
|
settings.push(widget::settings::item::builder(fl!("other")).control(
|
||||||
widget::text::body(format_permissions(metadata, PermissionOwner::Other)),
|
widget::dropdown(
|
||||||
|
&MODE_NAMES,
|
||||||
|
Some(get_mode_part(mode, MODE_SHIFT_OTHER).try_into().unwrap()),
|
||||||
|
move |selected| {
|
||||||
|
Message::SetPermissions(
|
||||||
|
other_path.clone(),
|
||||||
|
set_mode_part(
|
||||||
|
mode,
|
||||||
|
MODE_SHIFT_OTHER,
|
||||||
|
selected.try_into().unwrap(),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
},
|
||||||
|
),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -3428,6 +3450,9 @@ impl Tab {
|
||||||
Message::SetOpenWith(mime, id) => {
|
Message::SetOpenWith(mime, id) => {
|
||||||
commands.push(Command::SetOpenWith(mime, id));
|
commands.push(Command::SetOpenWith(mime, id));
|
||||||
}
|
}
|
||||||
|
Message::SetPermissions(path, mode) => {
|
||||||
|
commands.push(Command::SetPermissions(path, mode));
|
||||||
|
}
|
||||||
Message::SetSort(heading_option, dir) => {
|
Message::SetSort(heading_option, dir) => {
|
||||||
if !matches!(self.location, Location::Search(..)) {
|
if !matches!(self.location, Location::Search(..)) {
|
||||||
self.sort_name = heading_option;
|
self.sort_name = heading_option;
|
||||||
|
|
@ -5963,4 +5988,48 @@ mod tests {
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn mode_calculations() {
|
||||||
|
use super::{
|
||||||
|
get_mode_part, set_mode_part, MODE_SHIFT_GROUP, MODE_SHIFT_OTHER, MODE_SHIFT_USER,
|
||||||
|
};
|
||||||
|
for user in 0..=7 {
|
||||||
|
for group in 0..=7 {
|
||||||
|
for other in 0..=7 {
|
||||||
|
let mode = (user << MODE_SHIFT_USER)
|
||||||
|
| (group << MODE_SHIFT_GROUP)
|
||||||
|
| (other << MODE_SHIFT_OTHER);
|
||||||
|
assert_eq!(
|
||||||
|
format!("{:03o}", mode),
|
||||||
|
format!("{:o}{:o}{:o}", user, group, other),
|
||||||
|
);
|
||||||
|
assert_eq!(get_mode_part(mode, MODE_SHIFT_USER), user);
|
||||||
|
assert_eq!(get_mode_part(mode, MODE_SHIFT_GROUP), group);
|
||||||
|
assert_eq!(get_mode_part(mode, MODE_SHIFT_OTHER), other);
|
||||||
|
|
||||||
|
let mode_no_user = (group << MODE_SHIFT_GROUP) | (other << MODE_SHIFT_OTHER);
|
||||||
|
assert_eq!(
|
||||||
|
format!("{:03o}", mode_no_user),
|
||||||
|
format!("0{:o}{:o}", group, other)
|
||||||
|
);
|
||||||
|
assert_eq!(set_mode_part(mode_no_user, MODE_SHIFT_USER, user), mode);
|
||||||
|
|
||||||
|
let mode_no_group = (user << MODE_SHIFT_USER) | (other << MODE_SHIFT_OTHER);
|
||||||
|
assert_eq!(
|
||||||
|
format!("{:03o}", mode_no_group),
|
||||||
|
format!("{:o}0{:o}", user, other)
|
||||||
|
);
|
||||||
|
assert_eq!(set_mode_part(mode_no_group, MODE_SHIFT_GROUP, group), mode);
|
||||||
|
|
||||||
|
let mode_no_other = (user << MODE_SHIFT_USER) | (group << MODE_SHIFT_GROUP);
|
||||||
|
assert_eq!(
|
||||||
|
format!("{:03o}", mode_no_other),
|
||||||
|
format!("{:o}{:o}0", user, group)
|
||||||
|
);
|
||||||
|
assert_eq!(set_mode_part(mode_no_other, MODE_SHIFT_OTHER, other), mode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue