Skip to content

Commit

Permalink
Merge pull request #68 from webosbrew/feature/file-ls-helper
Browse files Browse the repository at this point in the history
Feature/file ls helper
  • Loading branch information
mariotaku authored Feb 13, 2023
2 parents e53501f + dae69a4 commit 9afffc6
Show file tree
Hide file tree
Showing 17 changed files with 148 additions and 159 deletions.
2 changes: 1 addition & 1 deletion .editorconfig
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ trim_trailing_whitespace = true
[*.ts]
quote_type = single

[*.rs]
[*.{rs,py}]
indent_size = 4

[*.md]
Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "dev-manager-desktop",
"version": "1.9.8",
"version": "1.9.9",
"description": "Device Manager for webOS",
"homepage": "https://github.com/webosbrew/dev-manager-desktop",
"author": {
Expand Down
10 changes: 10 additions & 0 deletions src-tauri/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions src-tauri/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ reqwest = { version = "0.11.14", features = ["json"] }
async-trait = "0.1.63"
sha2 = "0.10.6"
hex = "0.4.3"
file-mode = "0.1.2"

[features]
# by default Tauri runs in production mode
Expand Down
69 changes: 69 additions & 0 deletions src-tauri/res/scripts/files_ls.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
# coding: utf-8 -*-
import os
import stat
import sys
from os import path
from sys import stdout


class B:
def __init__(self, value):
self.value = value

def __repr__(self):
return 'true' if self.value else 'false'


class S():
def __init__(self, value):
self.value = value

def __repr__(self):
escaped = "".join(map(lambda i: S.escape_ch(self.value[i]), range(0, len(self.value))))
return '"' + escaped + '"'

@staticmethod
def escape_ch(ch):
# type: (str) -> str
if ch in '\x08\x09\x0a\x0c\x0d\x22\x2f\x5c':
return '\\' + ch
else:
return ch


def file_item(parent, name):
# type: (str, str) -> dict
abspath = path.join(parent, name)
lstat = os.lstat(abspath)

item = {
S('name'): S(name),
S('abspath'): S(abspath)
}
if stat.S_ISLNK(lstat.st_mode):
target = os.readlink(abspath)
broken = False
try:
lstat = os.stat(abspath)
except OSError:
broken = True
item[S('link')] = {
S('target'): S(target),
S('broken'): B(broken)
}
item[S('stat')] = {
S('mode'): lstat.st_mode,
S('uid'): lstat.st_uid,
S('gid'): lstat.st_gid,
S('size'): int(lstat.st_size),
S('atime'): lstat.st_atime,
S('mtime'): lstat.st_mtime,
S('ctime'): lstat.st_ctime,
}
return item


if __name__ == '__main__':
d = sys.argv[1]
entries = list(map(lambda x: file_item(d, x), os.listdir(d)))
stdout.write(str(entries))
2 changes: 1 addition & 1 deletion src-tauri/src/device_manager/io.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use std::fs::{create_dir_all, File, Metadata, Permissions};
use std::fs::{create_dir_all, File};
use std::io::{BufReader, BufWriter, ErrorKind};
use std::path::PathBuf;
use std::{env, fs};
Expand Down
1 change: 0 additions & 1 deletion src-tauri/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ mod device_manager;
mod plugins;
mod session_manager;
mod error;
mod ssh_files;

fn main() {
env_logger::init();
Expand Down
4 changes: 2 additions & 2 deletions src-tauri/src/plugins/cmd.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ async fn exec(
command: String,
stdin: Option<Vec<u8>>,
) -> Result<Vec<u8>, Error> {
return manager.exec(device, &command, stdin).await;
return manager.exec(device, &command, stdin.as_deref()).await;
}

#[tauri::command]
Expand All @@ -41,7 +41,7 @@ async fn proc_worker<R: Runtime>(
let manager = app.state::<SessionManager>();
let proc = Arc::new(manager.spawn(device, &command).await?);
let proc_ev = proc.clone();
let handler = app.once_global(format!("cmd-interrupt-{}", token), move |ev| {
let handler = app.once_global(format!("cmd-interrupt-{}", token), move |_| {
log::info!("interrupting proc");
let proc_ev = proc_ev.clone();
tokio::spawn(async move {
Expand Down
139 changes: 50 additions & 89 deletions src-tauri/src/plugins/file.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
use std::env::temp_dir;
use std::iter::zip;
use std::path::Path;

use serde::Serialize;
use serde::{Deserialize, Serialize};
use tauri::plugin::{Builder, TauriPlugin};
use tauri::{Runtime, State};
use tokio::fs;
Expand All @@ -13,7 +12,8 @@ use crate::device_manager::Device;
use crate::error::Error;
use crate::plugins::cmd::escape_path;
use crate::session_manager::SessionManager;
use crate::ssh_files::ls::for_entries;

use file_mode::Mode;

#[tauri::command]
async fn ls(
Expand All @@ -25,45 +25,15 @@ async fn ls(
return Err(Error::new("Absolute path required"));
}
log::info!("ls {}", path);
let mut entries: Vec<String> = String::from_utf8(
manager
.exec(
device.clone(),
&format!("find {} -maxdepth 1 -print0", escape_path(&path)),
None,
)
.await?,
)
.unwrap()
.split('\0')
.map(|l| String::from(l))
.collect();
// Last line is empty, remove it
entries.pop();
entries.sort();
let mut items = Vec::<FileItem>::new();
let mut ls_legacy = false;
for chunk in entries.chunks(100) {
let details = match for_entries(&manager, device.clone(), chunk, ls_legacy).await {
Ok(details) => details,
Err(Error::Unsupported) => {
if ls_legacy {
return Err(Error::Unsupported);
}
ls_legacy = true;
for_entries(&manager, device.clone(), chunk, true).await?
}
Err(e) => return Err(e),
};
let mut group: Vec<FileItem> = zip(chunk, details)
.skip(1)
.map(|(entry, line)| {
return FileItem::new(&path, &entry, &line);
})
.collect();
items.append(&mut group);
}
return Ok(items);
let output = manager
.exec(
device.clone(),
&format!("python - {}", escape_path(&path)),
Some(include_bytes!("../../res/scripts/files_ls.py")),
)
.await?;
let entries = serde_json::from_slice::<Vec<FileEntry>>(&output)?;
return Ok(entries.iter().map(|e| e.into()).collect());
}

#[tauri::command]
Expand All @@ -88,7 +58,7 @@ async fn write(
.exec(
device,
&format!("dd of={}", escape_path(&path)),
Some(content),
Some(content.as_slice()),
)
.await?;
return Ok(());
Expand Down Expand Up @@ -120,7 +90,11 @@ async fn put(
let mut buf = Vec::<u8>::new();
file.read_to_end(&mut buf).await?;
manager
.exec(device, &format!("dd of={}", escape_path(&path)), Some(buf))
.exec(
device,
&format!("dd of={}", escape_path(&path)),
Some(buf.as_slice()),
)
.await?;
return Ok(());
}
Expand Down Expand Up @@ -161,62 +135,49 @@ pub struct FileItem {
user: String,
group: String,
size: usize,
mtime: String,
mtime: f64,
abspath: String,
link: Option<LinkInfo>,
}

#[derive(Serialize, Clone, Debug)]
#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct LinkInfo {
target: String,
broken: Option<bool>,
}

impl FileItem {
fn basename(dir: &str, path: &str) -> String {
let without_dir = &path[dir.len()..];
return String::from(without_dir.strip_prefix("/").unwrap_or(without_dir));
}

fn new(path: &str, entry: &str, line: &str) -> FileItem {
let basename = Self::basename(path, entry);
let info_name_index = line.find('/').unwrap();
let info_cols: Vec<&str> = line[..info_name_index - 1]
.split_ascii_whitespace()
.collect();
let perm = *info_cols.get(0).unwrap();
let file_type = String::from(&perm[..1]);
#[derive(Deserialize, Clone, Debug)]
pub(crate) struct FileEntry {
name: String,
stat: FileStat,
abspath: String,
link: Option<LinkInfo>,
}

let size = if file_type == "-" {
info_cols[4].parse::<usize>().unwrap()
} else {
0
};
#[derive(Deserialize, Clone, Debug)]
pub(crate) struct FileStat {
mode: u32,
uid: u32,
gid: u32,
size: u32,
atime: f64,
ctime: f64,
mtime: f64,
}

let link: Option<LinkInfo> = if file_type == "l" {
let info_name = String::from(&line[info_name_index..]);
let basename_n_arrows = basename.matches(" -> ").count();
let segs: Vec<&str> = info_name.split(" -> ").collect();
Some(LinkInfo {
target: segs[basename_n_arrows + 1..].join(" -> "),
})
} else {
None
};
impl From<&FileEntry> for FileItem {
fn from(value: &FileEntry) -> FileItem {
let mode = format!("{}", Mode::from(value.stat.mode));
return FileItem {
filename: basename,
r#type: file_type,
mode: String::from(&perm[1..]),
user: String::from(*info_cols.get(2).unwrap()),
group: String::from(*info_cols.get(3).unwrap()),
mtime: format!(
"{}T{}{}",
info_cols.get(info_cols.len() - 3).unwrap(),
info_cols.get(info_cols.len() - 2).unwrap(),
info_cols.get(info_cols.len() - 1).unwrap()
),
abspath: String::from(entry),
size,
link,
filename: value.name.clone(),
r#type: String::from(&mode[..1]),
mode: String::from(&mode[1..]),
user: format!("{}", value.stat.uid),
group: format!("{}", value.stat.gid),
size: value.stat.size as usize,
mtime: value.stat.mtime,
abspath: value.abspath.clone(),
link: value.link.clone(),
};
}
}
5 changes: 2 additions & 3 deletions src-tauri/src/session_manager/connection.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ pub(crate) struct Connection {
pub(crate) type ConnectionsMap = HashMap<String, Arc<Connection>>;

impl Connection {
pub async fn exec(&self, command: &str, stdin: &Option<Vec<u8>>) -> Result<Vec<u8>, Error> {
pub async fn exec(&self, command: &str, stdin: Option<&[u8]>) -> Result<Vec<u8>, Error> {
let mut ch = self.open_cmd_channel().await?;
let id = ch.id();
log::debug!("{id}: Exec {{ command: {command} }}");
Expand All @@ -32,8 +32,7 @@ impl Connection {
return Err(Error::NegativeReply);
}
if let Some(data) = stdin {
let data = data.clone();
ch.data(&*data).await?;
ch.data(data).await?;
ch.eof().await?;
}
let mut stdout: Vec<u8> = Vec::new();
Expand Down
4 changes: 2 additions & 2 deletions src-tauri/src/session_manager/manager.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,11 @@ impl SessionManager {
&self,
device: Device,
command: &str,
stdin: Option<Vec<u8>>,
stdin: Option<&[u8]>,
) -> Result<Vec<u8>, Error> {
loop {
let conn = self.conn_obtain(device.clone()).await?;
match conn.exec(command, &stdin).await {
match conn.exec(command, stdin).await {
Err(Error::NeedsReconnect) => continue,
e => return e,
};
Expand Down
Loading

0 comments on commit 9afffc6

Please sign in to comment.