Compare commits

..

1 commit

Author SHA1 Message Date
timvisee
9ce61fe32e
Start with APKBUILD for Alpine package 2019-03-13 18:10:17 +01:00
83 changed files with 1850 additions and 10847 deletions

3
.gitattributes vendored
View file

@ -1,3 +0,0 @@
# Mark generated completion files as vendored
contrib/completions/* linguist-vendored
contrib/completions/gen_completions linguist-vendored=false

7
.github/FUNDING.yml vendored
View file

@ -1,7 +0,0 @@
# Funding links
github:
- timvisee
custom:
- "https://timvisee.com/donate"
patreon: timvisee
ko_fi: timvisee

View file

@ -14,10 +14,8 @@ stages:
- release
- package
# Variable defaults
variables:
RUST_VERSION: stable
RUST_TARGET: x86_64-unknown-linux-gnu
# Cache rust/cargo/build artifacts
cache:
@ -39,20 +37,18 @@ before_script:
rustc --version
cargo --version
# Check on stable, beta and nightly
# Variable defaults
variables:
RUST_VERSION: stable
RUST_TARGET: x86_64-unknown-linux-gnu
# Check on stable, beta and nightly
.check-base: &check-base
stage: check
script:
- cargo check --verbose
- cargo check --no-default-features --features send3,crypto-ring --verbose
- cargo check --no-default-features --features send2,crypto-openssl --verbose
- cargo check --no-default-features --features send3,crypto-openssl --verbose
- cargo check --no-default-features --features send2,send3,crypto-openssl --verbose
- cargo check --no-default-features --features send3,crypto-ring,archive --verbose
- cargo check --no-default-features --features send3,crypto-ring,history --verbose
- cargo check --no-default-features --features send3,crypto-ring,qrcode --verbose
- cargo check --no-default-features --features send3,crypto-ring,urlshorten --verbose
- cargo check --no-default-features --features send3,crypto-ring,infer-command --verbose
- cargo check --no-default-features --features send2 --verbose
- cargo check --no-default-features --features send3 --verbose
- cargo check --features no-color --verbose
check-stable:
<<: *check-base
@ -64,15 +60,14 @@ check-nightly:
<<: *check-base
variables:
RUST_VERSION: nightly
check-msrv:
check-old:
<<: *check-base
variables:
RUST_VERSION: "1.63.0"
RUST_VERSION: "1.32.0"
# Build using Rust stable
build-x86_64-linux-gnu:
stage: build
needs: []
script:
- cargo build --target=$RUST_TARGET --release --verbose
- mv target/$RUST_TARGET/release/ffsend ./ffsend-$RUST_TARGET
@ -86,7 +81,6 @@ build-x86_64-linux-gnu:
# Build a static version
build-x86_64-linux-musl:
stage: build
needs: []
variables:
RUST_TARGET: x86_64-unknown-linux-musl
script:
@ -94,11 +88,11 @@ build-x86_64-linux-musl:
- rustup target add $RUST_TARGET
# Build OpenSSL statically
- apt-get install -y build-essential wget musl-tools
- wget https://www.openssl.org/source/old/1.1.1/openssl-1.1.1k.tar.gz
- tar xzvf openssl-1.1.1k.tar.gz
- cd openssl-1.1.1k
- ./config no-async -fPIC --openssldir=/usr/local/ssl --prefix=/usr/local
- apt install -y build-essential wget musl-tools
- wget https://www.openssl.org/source/openssl-1.0.2o.tar.gz
- tar xzvf openssl-1.0.2o.tar.gz
- cd openssl-1.0.2o
- ./config -fPIC --openssldir=/usr/local/ssl --prefix=/usr/local
- make
- make install
- cd ..
@ -122,7 +116,6 @@ build-x86_64-linux-musl:
# Run the unit tests through Cargo
test-cargo:
stage: test
needs: []
dependencies: []
script:
- cargo test --verbose
@ -163,7 +156,7 @@ release-crate:
# Snap release
release-snap:
image: snapcore/snapcraft:stable
image: snapcore/snapcraft:edge
stage: release
dependencies: []
only:
@ -172,7 +165,6 @@ release-snap:
script:
# Prepare the environment
- apt-get update -y
- apt-get install python3 -yqq
- cd pkg/snap
# Update version number in snapcraft.yaml
@ -188,7 +180,7 @@ release-snap:
# Publish snap package
- echo "Publishing snap package..."
- echo "$SNAPCRAFT_LOGIN" | base64 -d > snapcraft.login
- snapcraft whoami
- snapcraft login --with snapcraft.login
- snapcraft push --release=stable ffsend_*_amd64.snap
artifacts:
name: ffsend-snap-x86_64
@ -211,7 +203,7 @@ release-github:
- apt-get install -y curl wget gzip netbase
# Download github-release binary
- wget https://github.com/tfausak/github-release/releases/download/1.2.5/github-release-linux.gz -O github-release.gz
- wget $(curl -s https://api.github.com/repos/tfausak/github-release/releases/latest | grep 'browser_' | cut -d\" -f4 | grep 'linux') -O github-release.gz
- gunzip github-release.gz
- chmod a+x ./github-release
@ -220,46 +212,10 @@ release-github:
- ./github-release upload --token "$GITHUB_TOKEN" --owner timvisee --repo ffsend --tag "$CI_COMMIT_REF_NAME" --file ./ffsend-x86_64-unknown-linux-gnu --name ffsend-$CI_COMMIT_REF_NAME-linux-x64
- ./github-release upload --token "$GITHUB_TOKEN" --owner timvisee --repo ffsend --tag "$CI_COMMIT_REF_NAME" --file ./ffsend-x86_64-unknown-linux-musl --name ffsend-$CI_COMMIT_REF_NAME-linux-x64-static
# Publish a Docker image
release-docker:
image: docker:git
stage: release
only:
- /^v(\d+\.)*\d+$/
dependencies:
- build-x86_64-linux-musl
services:
- docker:dind
variables:
RUST_TARGET: x86_64-unknown-linux-musl
DOCKER_HOST: tcp://docker:2375
# DOCKER_DRIVER: overlay2
before_script: []
script:
# Place binary in Docker directory, change to it
- mv ./ffsend-$RUST_TARGET ./pkg/docker/ffsend
- cd ./pkg/docker
# Build the Docker image, run it once to test
- docker build -t timvisee/ffsend:latest ./
- docker run --rm timvisee/ffsend:latest -V
# Retag version
- VERSION=$(echo $CI_COMMIT_REF_NAME | cut -c 2-)
- echo "Determined Docker image version number 'v$VERSION', retagging image..."
- docker tag timvisee/ffsend:latest timvisee/ffsend:$VERSION
# Authenticate and push the Docker images
- echo "$DOCKER_PASSWORD" | docker login -u "$DOCKER_USER" --password-stdin
- docker push timvisee/ffsend:$VERSION
- docker push timvisee/ffsend:latest
# AUR packages release
package-aur:
image: archlinux
image: archlinux/base
stage: package
needs:
- release-github
dependencies: []
only:
- /^v(\d+\.)*\d+$/
@ -267,81 +223,31 @@ package-aur:
script:
- cd ./pkg/aur
# Determine the version number we're releasing for
# Update version number in PKGBUILD
- VERSION=$(echo $CI_COMMIT_REF_NAME | cut -c 2-)
- echo "Determined binary version number 'v$VERSION'"
# Determine remote URLs and SHA checksums
- echo "Determining SHA checksums for remote files..."
- URL_BIN=https://github.com/timvisee/ffsend/releases/download/v$VERSION/ffsend-v$VERSION-linux-x64-static
- URL_SOURCE=https://gitlab.com/timvisee/ffsend/-/archive/v$VERSION/ffsend-v$VERSION.tar.gz
- URL_BASH_COMPLETION=https://gitlab.com/timvisee/ffsend/raw/v$VERSION/contrib/completions/ffsend.bash
- URL_ZSH_COMPLETION=https://gitlab.com/timvisee/ffsend/raw/v$VERSION/contrib/completions/_ffsend
- URL_FISH_COMPLETION=https://gitlab.com/timvisee/ffsend/raw/v$VERSION/contrib/completions/ffsend.fish
- URL_LICENSE=https://gitlab.com/timvisee/ffsend/raw/v$VERSION/LICENSE
- 'echo "Selected binary URL: $URL_BIN"'
- 'echo "Selected source URL: $URL_SOURCE"'
- echo "Determining sha256sum for remote binary..."
- 'SHA_BIN=$(curl -sSL "$URL_BIN" | sha256sum | cut -d" " -f1)'
- 'echo "Got sha256sum: $SHA_BIN"'
- 'SHA_BASH_COMPLETION=$(curl -sSL "$URL_BASH_COMPLETION" | sha256sum | cut -d" " -f1)'
- 'echo "Got sha256sums of bash completion: $SHA_BASH_COMPLETION"'
- 'SHA_ZSH_COMPLETION=$(curl -sSL "$URL_ZSH_COMPLETION" | sha256sum | cut -d" " -f1)'
- 'echo "Got sha256sums of ZSH completion: $SHA_ZSH_COMPLETION"'
- 'SHA_FISH_COMPLETION=$(curl -sSL "$URL_FISH_COMPLETION" | sha256sum | cut -d" " -f1)'
- 'echo "Got sha256sums of fish completion: $SHA_FISH_COMPLETION"'
- 'SHA_LICENSE=$(curl -sSL "$URL_LICENSE" | sha256sum | cut -d" " -f1)'
- 'echo "Got sha256sums of LICENSE: $SHA_LICENSE"'
- echo "Determining sha256sum for remote source..."
- 'SHA_SOURCE=$(curl -sSL "$URL_SOURCE" | sha256sum | cut -d" " -f1)'
- 'echo "Got sha256sum: $SHA_SOURCE"'
# Update PKGBUILD parameters: version, source URL and SHA sum
- echo "Updating PKGBUILDS with release information..."
- sed "s/^pkgver=.*\$/pkgver=$VERSION/" -i ffsend/PKGBUILD
- sed "s/^pkgver=.*\$/pkgver=$VERSION/" -i ffsend-bin/PKGBUILD
- sed "s/^pkgver=.*\$/pkgver=$VERSION.$CI_COMMIT_SHORT_SHA/" -i ffsend-git/PKGBUILD
- sed "s/^source=(\".*\").*\$/source=(\"$(echo $URL_SOURCE | sed 's/\//\\\//g')\")/" -i ffsend/PKGBUILD
- sed "s/\(\"ffsend-v\$pkgver::\).*\"/\1$(echo $URL_BIN | sed 's/\//\\\//g')\"/" -i ffsend-bin/PKGBUILD
- sed "s/\(\"ffsend-v\$pkgver.bash::\).*\"/\1$(echo $URL_BASH_COMPLETION | sed 's/\//\\\//g')\"/" -i ffsend-bin/PKGBUILD
- sed "s/\(\"ffsend-v\$pkgver.zsh::\).*\"/\1$(echo $URL_ZSH_COMPLETION | sed 's/\//\\\//g')\"/" -i ffsend-bin/PKGBUILD
- sed "s/\(\"ffsend-v\$pkgver.fish::\).*\"/\1$(echo $URL_FISH_COMPLETION | sed 's/\//\\\//g')\"/" -i ffsend-bin/PKGBUILD
- sed "s/\(\"LICENSE-v\$pkgver::\).*\"/\1$(echo $URL_LICENSE | sed 's/\//\\\//g')\"/" -i ffsend-bin/PKGBUILD
- sed "s/^sha256sums=.*\$/sha256sums=('$SHA_SOURCE')/" -i ffsend/PKGBUILD
- sed "s/^sha256sums=.*\$/sha256sums=('$SHA_BIN' '$SHA_BASH_COMPLETION' '$SHA_ZSH_COMPLETION' '$SHA_FISH_COMPLETION' '$SHA_LICENSE')/" -i ffsend-bin/PKGBUILD
# Get SHA hash for local and remote file w/o version, update if it has changed
- 'SHA_STRIP_LOCAL=$(cat ffsend-git/PKGBUILD | sed /^pkgver=.\*/d | sha256sum | cut -d" " -f1)'
- 'SHA_STRIP_REMOTE=$(curl -sSL "https://aur.archlinux.org/cgit/aur.git/plain/PKGBUILD?h=ffsend-git" | sed /^pkgver=.\*/d | sha256sum | cut -d" " -f1)'
- echo "Determined binary version number 'v$VERSION', updating PKGBUILDs..."
- sed "s/^pkgver=.*\$/pkgver=$VERSION/" -i bin/PKGBUILD
- sed "s/^pkgver=.*\$/pkgver=$VERSION/" -i git/PKGBUILD
# Install dependencies
- echo "Installing required build packages..."
- pacman -Syu --noconfirm sudo base-devel binutils openssh rust cargo cmake git openssl
# Make AUR package
- echo "Making AUR package..."
- mkdir -p /.cargo
- chmod -R 777 /.cargo
- cd ffsend-bin/
- cd bin/
- echo "Making binary package..."
- sudo -u nobody makepkg -c
- sudo -u nobody makepkg --printsrcinfo > .SRCINFO
- cd ../ffsend
- echo "Making main source package..."
- cd ../git
- echo "Making git source package..."
- sudo -u nobody makepkg -c
- sudo -u nobody makepkg --printsrcinfo > .SRCINFO
# Make git package if different than the remote
- |
if [ ! "$SHA_STRIP_LOCAL" == "$SHA_STRIP_REMOTE" ]; then
cd ../ffsend-git
echo "Making git source package..."
sudo -u nobody makepkg -c
sudo -u nobody makepkg --printsrcinfo > .SRCINFO
else
echo "Not making git source package, it has not changed"
fi
- cd ..
# Set up SSH for publishing
# Set up SSH for publishing
- mkdir -p /root/.ssh
- cp ./aur.pub /root/.ssh/id_rsa.pub
- echo "$AUR_SSH_PRIVATE" > /root/.ssh/id_rsa
@ -355,37 +261,60 @@ package-aur:
- git config --global user.name "timvisee"
- git config --global user.email "tim@visee.me"
# Publish main package: clone AUR repo, commit update and push
- git clone ssh://aur@aur.archlinux.org/ffsend.git aur-ffsend
- cd aur-ffsend
- cp ../ffsend/{PKGBUILD,.SRCINFO} ./
- git add PKGBUILD .SRCINFO
- git commit -m "Release v$VERSION"
- git push
- cd ..
# Publish binary package: clone AUR repo, commit update and push
- git clone ssh://aur@aur.archlinux.org/ffsend-bin.git aur-ffsend-bin
# Publish binary package: clone AUR repo, commit update and push
- git clone ssh://aur@aur.archlinux.org/ffsend.git aur-ffsend-bin
- cd aur-ffsend-bin
- cp ../ffsend-bin/{PKGBUILD,.SRCINFO} ./
- cp ../bin/{PKGBUILD,.SRCINFO} ./
- git add PKGBUILD .SRCINFO
- git commit -m "Release v$VERSION"
- git push
- cd ..
# Publish git package: clone AUR repo, commit update and push
# Only publish it if it is different than the remote
- |
if [ ! "$SHA_STRIP_LOCAL" == "$SHA_STRIP_REMOTE" ]; then
git clone ssh://aur@aur.archlinux.org/ffsend-git.git aur-ffsend-git
cd aur-ffsend-git
cp ../ffsend-git/{PKGBUILD,.SRCINFO} ./
git add PKGBUILD .SRCINFO
git commit -m "Update PKGBUILD for release v$VERSION"
git push
cd ..
else
echo "Not pushing git package, it has not changed"
fi
# Publish git package: clone AUR repo, commit update and push
- git clone ssh://aur@aur.archlinux.org/ffsend-git.git aur-ffsend-git
- cd aur-ffsend-git
- cp ../git/{PKGBUILD,.SRCINFO} ./
- git add PKGBUILD .SRCINFO
- git commit -m "Release v$VERSION"
- git push
- cd ..
# Alpine APK package release
package-alpine:
image: alpine:latest
stage: package
only:
- /^v(\d+\.)*\d+$/
dependencies:
- build-x86_64-linux-musl
variables:
RUST_TARGET: x86_64-unknown-linux-musl
before_script: []
script:
# Rename ffsend binary, move into Alpine package directory
- mv ./ffsend-$RUST_TARGET ./ffsend
- cd ./pkg/alpine
# Install SDK, prepare user, create build directories, configure abuild
- apk add alpine-sdk
- adduser -D ffsend
- addgroup ffsend abuild
- echo "ffsend ALL=(ALL) ALL" >> /etc/sudoers
- mkdir -p /var/cache/distfiles
- chmod a+w /var/cache/distfiles
- echo 'PACKAGER="timvisee <tim@visee.me>"' >> /etc/abuild.conf
- echo 'MAINTAINER="$PACKAGER"' >> /etc/abuild.conf
# Switch to new user, configure git, clone aports repository
- su - ffsend
- git config --global user.name timvisee
- git config --global user.email tim@visee.me
- git clone git://git.alpinelinux.org/aports
# TODO: configure with APKBUILD guide: https://wiki.alpinelinux.org/wiki/Creating_an_Alpine_package
# TODO: configure version number
# TODO: build package
# TODO: publish package
# TODO: add job to test ffsend{-git} AUR packages

View file

@ -10,6 +10,13 @@ stages:
- name: release
if: tag =~ ^v(\d+\.)*\d+$
# Explicitly install and update OpenSSL dependencies
addons:
homebrew:
packages:
- openssl@1.1
update: true
jobs:
include:
- stage: release
@ -31,11 +38,3 @@ jobs:
# Create the release, upload binary
- ./github-release release --token "$GITHUB_TOKEN" --owner timvisee --repo ffsend --tag "$TRAVIS_TAG" --title "ffsend $TRAVIS_TAG"
- ./github-release upload --token "$GITHUB_TOKEN" --owner timvisee --repo ffsend --tag "$TRAVIS_TAG" --file ./ffsend --name ffsend-$TRAVIS_TAG-macos
# TODO: disabled for now to throttle updates, manually enable on major release
# # Update homebrew package
# - git config --global user.name "timvisee"
# - git config --global user.email "$GIT_EMAIL"
# - git config --global credential.helper store
# - echo "https://timvisee:$HOMEBREW_GITHUB_API_TOKEN@github.com" >> ~/.git-credentials
# - brew bump-formula-pr --url="https://github.com/timvisee/ffsend/archive/$TRAVIS_TAG.tar.gz" --message="Automated release pull request using continuous integration." --no-browse -f -v ffsend

View file

@ -1,132 +0,0 @@
# Contributing
**Repository:**
- [GitLab repository][gitlab]
- _Mirror: [GitHub repository][github]_
**Issues:** (bug reporting, feature requests, enhancements, etc.)
- [GitLab issue board][gitlab-issues]
- _Alternatively: [GitHub issue board][github-issues]_
**Pull/merge requests:** (fixes, implemented features, etc.)
- [GitLab merge requests][gitlab-mr]
- _Alternatively: [GitHub pull requests][github-pr]_
Contributions to the `ffsend` project are welcome!
When contributing new features, alternative implementations or bigger
improvements, please first discuss the change you wish to make via an issue
or email.
Small changes such as fixed commands, fixed spelling or dependency updates
are always welcome without discussion.
The `ffsend` repository is primarily hosted on [GitLab][gitlab].
[GitHub][github] hosts a mirror, for publicity and findability.
Please open any issues or pull requests on the [GitLab][gitlab] pages if possible.
Otherwise opening these on [GitHub][github] is fine, though they might be
manually moved over to [GitLab][gitlab].
Please note we have a code of conduct, please follow it in all your interactions
with the project.
## Pull Request Process
1. Ensure you've discussed your improvements in an issue when working on a
bigger change
2. Ensure your branch is up-to-date with the latest [`master`][branch-master]
3. Ensure the project builds with your changes: `cargo build`
4. Ensure the project tests succeed with your changes: `cargo test`
5. Update the `README.md` with details of significant changes, this includes new
compiler features, command-line arguments, environment variables or new
package installation instructions.
6. Submit your pull request.
7. Fix any issues continuous integration might report.
Additional notes:
- Do not change version numbers, this is done by @timvisee
## Code of Conduct
### Our Pledge
In the interest of fostering an open and welcoming environment, we as
contributors and maintainers pledge to making participation in our project and
our community a harassment-free experience for everyone, regardless of age, body
size, disability, ethnicity, gender identity and expression, level of experience,
nationality, personal appearance, race, religion, or sexual identity and
orientation.
### Our Standards
Examples of behavior that contributes to creating a positive environment
include:
* Using welcoming and inclusive language
* Being respectful of differing viewpoints and experiences
* Gracefully accepting constructive criticism
* Focusing on what is best for the community
* Showing empathy towards other community members
Examples of unacceptable behavior by participants include:
* The use of sexualized language or imagery and unwelcome sexual attention or
advances
* Trolling, insulting/derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or electronic
address, without explicit permission
* Other conduct which could reasonably be considered inappropriate in a
professional setting
### Our Responsibilities
Project maintainers are responsible for clarifying the standards of acceptable
behavior and are expected to take appropriate and fair corrective action in
response to any instances of unacceptable behavior.
Project maintainers have the right and responsibility to remove, edit, or
reject comments, commits, code, wiki edits, issues, and other contributions
that are not aligned to this Code of Conduct, or to ban temporarily or
permanently any contributor for other behaviors that they deem inappropriate,
threatening, offensive, or harmful.
### Scope
This Code of Conduct applies both within project spaces and in public spaces
when an individual is representing the project or its community. Examples of
representing a project or community include using an official project e-mail
address, posting via an official social media account, or acting as an appointed
representative at an online or offline event. Representation of a project may be
further defined and clarified by project maintainers.
### Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported by contacting the project owner at 3a4fb3964f@sinenomine.email. All
complaints will be reviewed and investigated and will result in a response that
is deemed necessary and appropriate to the circumstances. The project team is
obligated to maintain confidentiality with regard to the reporter of an incident.
Further details of specific enforcement policies may be posted separately.
Project maintainers who do not follow or enforce the Code of Conduct in good
faith may face temporary or permanent repercussions as determined by other
members of the project's leadership.
### Attribution
This Code of Conduct is adapted from the [Contributor Covenant][coc-homepage], version 1.4,
available at [https://contributor-covenant.org/version/1/4][coc-version]
## License
This project is released under the GNU GPL-3.0 license.
Check out the [LICENSE](LICENSE) file for more information.
[branch-master]: https://gitlab.com/timvisee/ffsend/tree/master
[gitlab]: https://gitlab.com/timvisee/ffsend
[gitlab-issues]: https://gitlab.com/timvisee/ffsend/issues
[gitlab-mr]: https://gitlab.com/timvisee/ffsend/merge_requests
[github]: https://github.com/timvisee/ffsend
[github-issues]: https://github.com/timvisee/ffsend/issues
[github-pr]: https://github.com/timvisee/ffsend/pulls
[coc-homepage]: https://contributor-covenant.org
[coc-version]: https://contributor-covenant.org/version/1/4/

3231
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -1,15 +1,14 @@
[package]
name = "ffsend"
version = "0.2.76"
rust-version = "1.63.0"
authors = ["Tim Visee <3a4fb3964f@sinenomine.email>"]
version = "0.2.29"
authors = ["Tim Visee <timvisee@gmail.com>"]
license = "GPL-3.0"
readme = "README.md"
homepage = "https://timvisee.com/projects/ffsend"
homepage = "https://gitlab.com/timvisee/ffsend"
repository = "https://gitlab.com/timvisee/ffsend"
description = """\
Easily and securely share files from the command line.\n\
A fully featured Send client.\
A fully featured Firefox Send client.\
"""
keywords = ["send", "firefox", "cli"]
categories = [
@ -20,16 +19,9 @@ categories = [
"network-programming",
]
exclude = [
"/.github",
"/contrib",
"/pkg",
"/res",
"/*.yml",
"/CONTRIBUTING.md",
"/SECURITY.md",
"res/*",
]
edition = "2018"
build = "build.rs"
[package.metadata.deb]
section = "utility"
@ -50,86 +42,53 @@ license-file = ["LICENSE", "3"]
depends = "$auto, libssl1.1, ca-certificates, xclip"
maintainer-scripts = "pkg/deb"
[badges]
gitlab = { repository = "timvisee/ffsend", branch = "master" }
[[bin]]
name = "ffsend"
path = "src/main.rs"
[features]
default = ["archive", "clipboard", "crypto-ring", "history", "infer-command", "qrcode", "send3", "urlshorten"]
default = ["archive", "clipboard", "history", "send2", "send3"]
# Compile with file archiving support
archive = ["tar"]
# Support for putting share URLs in clipboard
clipboard = ["clip", "which"]
# Compile with file history support
history = []
# Support for Send v2
send2 = ["ffsend-api/send2"]
# Support for Send v3
send3 = ["ffsend-api/send3"]
# Use OpenSSL as cryptography backend
crypto-openssl = ["ffsend-api/crypto-openssl"]
# Use ring as cryptography backend
crypto-ring = ["ffsend-api/crypto-ring"]
# Support for generating QR codes for share URLs
qrcode = ["qr2term"]
# Support for shortening share URLs
urlshorten = ["urlshortener"]
# Support for inferring subcommand when linking binary
infer-command = []
# Compile without colored output support
no-color = ["colored/no-color"]
# Automatic using build.rs: use xclip/xsel binary method for clipboard support
clipboard-bin = ["clipboard"]
# Support for Firefox Send v2
send2 = ["ffsend-api/send2"]
# Automatic using build.rs: use native clipboard crate for clipboard support
clipboard-crate = ["clipboard"]
# Support for Firefox Send v3
send3 = ["ffsend-api/send3"]
[dependencies]
chbs = "0.1.0"
chbs = "0.0.8"
chrono = "0.4"
clap = "2.33"
colored = "2.0"
derive_builder = "0.10"
directories = "4.0"
clap = "2.31"
colored = "1.7"
derive_builder = "0.7"
directories = "1.0"
failure = "0.1"
ffsend-api = { version = "0.7.3", default-features = false }
ffsend-api = { version = "0.2.3", default-features = false }
fs2 = "0.4"
lazy_static = "1.4"
open = "2"
lazy_static = "1.0"
open = "1"
openssl-probe = "0.1"
pathdiff = "0.2"
pbr = "1"
prettytable-rs = { version = "0.10.0", default-features = false }
qr2term = { version = "0.2", optional = true }
rand = "0.8"
regex = "1.5"
rpassword = "5"
serde = "1"
serde_derive = "1"
prettytable-rs = "0.8"
rpassword = "2.1"
serde = "1.0"
serde_derive = "1.0"
tar = { version = "0.4", optional = true }
tempfile = "3"
toml = "0.5"
urlshortener = { version = "3", optional = true }
version-compare = "0.1"
toml = "0.4"
version-compare = "0.0.6"
[target.'cfg(any(target_os = "linux", target_os = "freebsd", target_os = "dragonfly", target_os = "openbsd", target_os = "netbsd"))'.dependencies]
which = { version = "4.0", optional = true }
[target.'cfg(not(any(target_os = "linux", target_os = "freebsd", target_os = "dragonfly", target_os = "openbsd", target_os = "netbsd")))'.dependencies]
# Aliased to clip to prevent name collision with clipboard feature
clip = { version = "0.5", optional = true, package = "clipboard" }
[package.metadata.docs.rs]
targets = ["x86_64-unknown-linux-gnu"]
[target.'cfg(not(target_os = "linux"))'.dependencies]
clipboard = { version = "0.5", optional = true }

View file

@ -1,7 +1,7 @@
GNU GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
Copyright (C) 2018 Tim Visee. <https://timvisee.com/>
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
@ -645,7 +645,7 @@ the "copyright" line and a pointer to where the full notice is found.
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
along with this program. If not, see <http://www.gnu.org/licenses/>.
Also add information on how to contact you by electronic and paper mail.
@ -664,11 +664,11 @@ might be different; for a GUI interface, you would use an "about box".
You should also get your employer (if you work as a programmer) or school,
if any, to sign a "copyright disclaimer" for the program, if necessary.
For more information on this, and how to apply and follow the GNU GPL, see
<https://www.gnu.org/licenses/>.
<http://www.gnu.org/licenses/>.
The GNU General Public License does not permit incorporating your program
into proprietary programs. If your program is a subroutine library, you
may consider it more useful to permit linking proprietary applications with
the library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License. But first, please read
<https://www.gnu.org/licenses/why-not-lgpl.html>.
<http://www.gnu.org/philosophy/why-not-lgpl.html>.

584
README.md
View file

@ -1,26 +1,23 @@
[![Build status on GitLab CI][gitlab-ci-master-badge]][gitlab-ci-link]
[![Newest release on crates.io][crate-version-badge]][crate-link]
[![Number of downloads on crates.io][crate-download-badge]][crate-link]
[![Project license][crate-license-badge]](LICENSE)
[crate-download-badge]: https://img.shields.io/crates/d/ffsend.svg
[crate-license-badge]: https://img.shields.io/crates/l/ffsend.svg
[crate-link]: https://crates.io/crates/ffsend
[crate-version-badge]: https://img.shields.io/crates/v/ffsend.svg
[gitlab-ci-link]: https://gitlab.com/timvisee/ffsend/pipelines
[gitlab-ci-master-badge]: https://gitlab.com/timvisee/ffsend/badges/master/pipeline.svg
*Notice: the default Send host is provided by [@timvisee][timvisee]
([info](https://gitlab.com/timvisee/ffsend/-/issues/111)).
Please consider to [donate] and help keep it running.*
# ffsend
# ffsend [WIP]
> Easily and securely share files from the command line.
> A [Send][send] client.
> A fully featured [Firefox Send][send] client.
Easily and securely share files and directories from the command line through a
safe, private and encrypted link using a single simple command.
Files are shared using the [Send][send] service and may be up
to 1GB. Others are able to download these files with this tool, or through
to 2GB. Others are able to download these files with this tool, or through
their web browser.
[![ffsend usage demo][usage-demo-svg]][usage-demo-asciinema]
@ -36,7 +33,7 @@ Find out more about security [here](#security).
- [Features](#features)
- [Usage](#usage)
- [Requirements](#requirements)
- [Install](#install) ([Linux](#linux-all-distributions), [macOS](#macos), [Windows](#windows), [FreeBSD](#freebsd), [Android](#android), [_Other OS/arch_](#other-os-or-architecture))
- [Install](#install) ([Linux](#linux-all-distributions), [macOS](#macos), [Windows](#windows), [_Other OS/arch_](#other-os-or-architecture))
- [Build](#build)
- [Configuration and environment](#configuration-and-environment)
- [Security](#security)
@ -45,25 +42,26 @@ Find out more about security [here](#security).
- [License](#license)
The public [Send][send] service that is used as default host is provided by
[@timvisee][timvisee] ([info](https://gitlab.com/timvisee/ffsend/-/issues/111)).
This application is not affiliated with [Firefox][firefox] or
[Mozilla][mozilla] in any way.
[Mozilla][mozilla].
This application is not affiliated with [Mozilla][mozilla], [Firefox][firefox]
or [Firefox Send][send] in any way.
_Note: this tool is currently in beta, as some extra desired features are yet to be implemented_
_Note: this tool is currently in alpha_
## Features
- Fully featured and friendly command line tool
- Upload and download files and directories securely, always encrypted on the client
- Upload and download files and directories securely
- Always encrypted on the client
- Additional password protection, generation and configurable download limits
- Supports old and new Firefox Send server versions
- File and directory archiving and extraction
- Built-in share URL shortener and QR code generator
- Supports Send v3 (current) and v2
- History tracking your files for easy management
- Ability to use your own Send hosts
- Ability to use custom Send hosts
- Inspect or delete shared files
- Accurate error reporting
- Streaming encryption and uploading/downloading, very low memory footprint
- Intended for use in [scripts](#scriptability) without interaction
- Intended for use in scripts without interaction
- Upcomming: Firefox Account integration (higher download counts, longer expiry times)
For a list of upcoming features and ideas, take a look at the
current [open issues](https://gitlab.com/timvisee/ffsend/issues) over on GitLab.
@ -74,36 +72,35 @@ Easily upload and download:
```bash
# Simple upload
$ ffsend upload my-file.txt
https://send.vis.ee/#sample-share-url
Share link: https://send.firefox.com/#sample-share-url
# Advanced upload
# - Specify a download limit of 1
# - Specify upload expiry time of 5 minutes
# - Specify a download limit of 20
# - Enter a password to encrypt the file
# - Archive the file before uploading
# - Copy the shareable link to your clipboard
# - Open the shareable link in your browser
$ ffsend upload --downloads 1 --expiry-time 5m --password --archive --copy --open my-file.txt
$ ffsend upload --downloads 20 --password --archive --copy --open my-file.txt
Password: ******
https://send.vis.ee/#sample-share-url
Share link: https://send.firefox.com/#sample-share-url
# Upload to your own host
$ ffsend u -h https://example.com/ my-file.txt
https://example.com/#sample-share-url
Share link: https://example.com/#sample-share-url
# Simple download
$ ffsend download https://send.vis.ee/#sample-share-url
$ ffsend download https://send.firefox.com/#sample-share-url
```
Inspect remote files:
```bash
# Check if a file exists
$ ffsend exists https://send.vis.ee/#sample-share-url
$ ffsend exists https://send.firefox.com/#sample-share-url
Exists: yes
# Fetch remote file info
$ ffsend info https://send.vis.ee/#sample-share-url
$ ffsend info https://send.firefox.com/#sample-share-url
ID: b087066715
Name: my-file.txt
Size: 12 KiB
@ -113,61 +110,55 @@ Expiry: 18h2m (64928s)
```
Other commands include:
```bash
# View your file history
$ ffsend history
# LINK EXPIRE
1 https://send.vis.ee/#sample-share-url 23h57m
2 https://send.vis.ee/#other-sample-url 17h38m
3 https://example.com/#sample-share-url 37m30s
# LINK EXPIRY OWNER TOKEN
1 https://send.firefox.com/#sample-share-url 23h57m eea9f544f6d5df8a5afd
2 https://send.firefox.com/#other-sample-url 17h38m 1e9fef63fee3baaf54ce
3 https://example.com/#sample-share-url 37m30s 8eb28bc1bc85cfdab0e4
# Change the password after uploading
$ ffsend password https://send.vis.ee/#sample-share-url
$ ffsend password https://send.firefox.com/#sample-share-url
Password: ******
# Delete a file
$ ffsend delete https://send.vis.ee/#sample-share-url
$ ffsend delete https://send.firefox.com/#sample-share-url
```
Use the `--help` flag, `help` subcommand, or see the [help](#help) section for
all available subcommands.
## Requirements
- Linux, macOS, Windows, FreeBSD, Android (other BSDs might work)
- Linux, Windows or macOS
- A terminal :sunglasses:
- Internet connection
- Linux:
- Internet connection for uploading and downloading
- Linux specific:
- OpenSSL & CA certificates:
- Ubuntu, Debian and derivatives: `apt install openssl ca-certificates`
- Optional: `xclip` or `xsel` for clipboard support
- Optional: `xclip` for clipboard support
- Ubuntu, Debian and derivatives: `apt install xclip`
- CentOS/Red Hat/openSUSE/Fedora: `yum install xclip`
- Arch: `pacman -S xclip`
- Windows specific:
- Optional OpenSSL with `crypto-openssl` feature: [» Installer][openssl-windows-installer] (`v1.1.0j` or above)
- OpenSSL v1.1.0j: [» Installer][openssl-windows-installer]
- macOS specific:
- Optional OpenSSL with `crypto-openssl` feature: `brew install openssl@1.1`
- FreeBSD specific:
- OpenSSL: `pkg install openssl`
- CA certificates: `pkg install ca_root_nss`
- Optional `xclip` & `xsel` for clipboard support: `pkg install xclip xsel-conrad`
- Android specific:
- Termux: [» Termux][termux]
- OpenSSL: `brew install openssl@1.1`
## Install
Because `ffsend` is still in early stages, only limited installation options are
available right now. Feel free to contribute additional packages.
<!-- Before installing, make sure you meet all requirements listed
[here](#requirements) -->
Because `ffsend` is still in alpha, only limited installation options are
available right now.
Make sure you meet and install the [requirements](#requirements).
See the operating system specific instructions below:
- [Linux](#linux-all-distributions)
- [macOS](#macos)
- [Windows](#windows)
- [FreeBSD](#freebsd)
- [Android](#android)
- [_Other OS or architecture_](#other-os-or-architecture)
* [Linux](#linux-all-distributions)
* [macOS](#macos)
* [Windows](#windows)
* [_Other OS or architecture_](#other-os-or-architecture)
### Linux (all distributions)
Using the [snap](#linux-snap-package) package is recommended if supported.
@ -181,69 +172,26 @@ More packages options will be coming soon.
#### Linux: snap package
_Note: The `ffsend` `snap` package is isolated, and can only access files in
your home directory. Choose a different installation option if you don't want
this limitation._
_Note: due to how `snap` is configured by default, you won't be able to use the
package from some contexts such as through SSH without manual modifications. If
you're experiencing problems, please refer to a different installation method
such as the [prebuilt binaries](#linux-prebuilt-binaries), or open an issue._
your home directory. Use a different installation option instead if you don't
want this._
`ffsend`][snapcraft-ffsend]
```bash
snap install ffsend
ffsend --help
```
#### Linux: Arch AUR packages
`ffsend-bin`][aur-ffsend-bin] (precompiled binary, latest release, recommended)
`ffsend`][aur-ffsend] (compiles from source, latest release)
`ffsend-git`][aur-ffsend-git] (compiles from source, latest `master` commit)
`ffsend`][aur-ffsend] (binary package)
`ffsend-git`][aur-ffsend-git] (compile from source package)
```bash
yay -S ffsend
yaourt -S ffsend
# or
aurto add ffsend-bin
sudo pacman -S ffsend-bin
# or using any other AUR helper
ffsend --help
```
#### Linux: Nix package
_Note: The Nix package is currently not automatically updated, and might be
slightly outdated._
[» ffsend][nix-ffsend]
```bash
nix-channel --update
nix-env --install ffsend
ffsend --help
```
#### Linux: Fedora package
_Note: The Fedora package is maintained by contributors, and might be
slightly outdated._
[» ffsend][fedora-ffsend]
```bash
sudo dnf install ffsend
ffsend --help
```
#### Linux: Alpine package
_Note: The Alpine package is maintained by contributors, it might be outdated.
Choose a different installation method if an important update is missing._
[» ffsend][alpine-ffsend]
```bash
apk add ffsend --repository=http://dl-cdn.alpinelinux.org/alpine/edge/testing
yay ffsend
# or
aurget -S ffsend
# or using any other AUR installer
ffsend --help
```
@ -251,8 +199,6 @@ ffsend --help
#### Linux: Prebuilt binaries
Check out the [latest release][github-latest-release] assets for Linux binaries.
Use the `ffsend-v*-linux-x64-static` binary, to minimize the chance for issues.
If it isn't available yet, you may use an artifact from a
[previous version][github-releases] instead, until it is available.
Make sure you meet and install the [requirements](#requirements) before you
continue.
@ -268,56 +214,30 @@ mv ./ffsend-* ./ffsend
chmod a+x ./ffsend
# Move binary into path, to make it easily usable
sudo mv ./ffsend /usr/local/bin/
sudo mv ./ffsend /usr/bin/
ffsend --help
```
### macOS
Using the [`homebrew` package](#macos-homebrew-package) is recommended.
Alternatively you may install it via [MacPorts](#macos-macports), or manually using the
[prebuilt binaries](#macos-prebuilt-binaries).
Currently only a [prebuilt binary](#macos-prebuilt-binaries) is available for macOS.
#### macOS: homebrew package
Make sure you've [`homebrew`][homebrew] installed, and run:
```bash
brew install ffsend
ffsend --help
```
#### macOS: MacPorts
_Note: ffsend in MacPorts is currently not automatically updated, and might be
slightly outdated._
Once you have [MacPorts](https://www.macports.org) installed, you can run:
```bash
sudo port selfupdate
sudo port install ffsend
```
#### macOS: Nix package
_Note: The Nix package is currently not automatically updated, and might be
slightly outdated._
```bash
nix-channel --update
nix-env --install ffsend
ffsend --help
```
A `homebrew` package will be coming soon.
#### macOS: Prebuilt binaries
Check out the [latest release][github-latest-release] assets for a macOS binary.
If it isn't available yet, you may use an artifact from a
[previous version][github-releases] instead, until it is available.
Then, mark the downloaded binary as an executable.
You then may want to move it into `/usr/local/bin/` to make the `ffsend` command
You must install `openssl` through [`homebrew`][homebrew] which `ffsend` depends
on.
Then, mark the downloaded binary as as executable.
You then may want to move it into `/usr/bin/` to make the `ffsend` command
globally available:
```bash
# Install openssl dependency
brew install openssl@1.1
# Rename file to ffsend
mv ./ffsend-* ./ffsend
@ -325,15 +245,13 @@ mv ./ffsend-* ./ffsend
chmod a+x ./ffsend
# Move binary into path, to make it easily usable
sudo mv ./ffsend /usr/local/bin/
sudo mv ./ffsend /usr/bin/
ffsend
```
### Windows
Using the [`scoop` package](#windows-scoop-package) is recommended.
Alternatively you may install it manually using the
[prebuilt binaries](#windows-prebuilt-binaries).
Currently only [prebuilt binaries](#windows-prebuilt-binaries) are available for Windows.
If you're using the [Windows Subsystem for Linux][wsl], it's highly recommended
to install the [prebuilt Linux binary](#prebuilt-binaries-for-linux) instead.
@ -341,188 +259,110 @@ to install the [prebuilt Linux binary](#prebuilt-binaries-for-linux) instead.
Only 64-bit (`x86_64`) binaries are provided.
For other architectures and configurations you may [compile from source](#build).
A `chocolatey` package along with an `.msi` installer will be coming soon.
#### Windows: scoop package
Make sure you've [`scoop`][scoop-install] installed, and run:
```bash
scoop install ffsend
ffsend --help
```
A `chocolatey` and a `scoop` package along with an `.msi` installer will be coming soon.
#### Windows: Prebuilt binaries
Check out the [latest release][github-latest-release] assets for Windows binaries.
Use the `ffsend-v*-windows-x64-static` binary, to minimize the chance for issues.
If it isn't available yet, you may use an artifact from a
[previous version][github-releases] instead, until it is available.
You must install OpenSSL 1.1.0j which `ffsend` depends on. The installer for
this can be found here: [» Installer][openssl-windows-installer]
You can use `ffsend` from the command line in the same directory:
```cmd
.\ffsend.exe --help
```
To make it globally invocable as `ffsend`, you must make the binary available in
To make it globally invokable as `ffsend`, you must make the binary available in
your systems `PATH`. The easiest solution is to move it into `System32`:
```cmd
move .\ffsend.exe C:\Windows\System32\ffsend.exe
```
### FreeBSD
`ffsend`][freshports-ffsend]
_Note: The FreeBSD package is currently maintained by FreeBSD contributors,
and might be slightly outdated._
```sh
# Precompiled binary.
pkg install ffsend
# Compiles and installs from source.
cd /usr/ports/www/ffsend && make install
```
### Android
`ffsend` can be used on Android through Termux, install it first:
[» Termux][termux]
_Note: The Android package is currently maintained by Termux contributors,
and might be slightly outdated._
```sh
# Install package.
pkg install ffsend
ffsend help
```
### Other OS or architecture
If your system runs Docker, you can use the [docker image](#docker-image).
There are currently no other binaries or packages available.
You can [build the project from source](#build) instead.
#### Docker image
A Docker image is available for using `ffsend` running in a container.
Mount a directory to `/data`, so it's accessible for `ffsend` in the container,
and use the command as you normally would.
`timvisee/ffsend`][docker-hub-ffsend]
```bash
# Invoke without arguments
docker run --rm -it -v $(pwd):/data timvisee/ffsend
# Upload my-file.txt
docker run --rm -it -v $(pwd):/data timvisee/ffsend upload my-file.txt
# Download from specified link
docker run --rm -it -v $(pwd):/data timvisee/ffsend download https://send.vis.ee/#sample-share-url
# Show help
docker run --rm -it -v $(pwd):/data timvisee/ffsend help
# To update the used image
docker pull timvisee/ffsend
```
On Linux or macOS you might define a alias in your shell configuration, to make
it invocable as `ffsend`:
```bash
alias ffsend='docker run --rm -it -v "$(pwd):/data" timvisee/ffsend'
```
_Note: This implementation is limited to accessing the paths you make available
through the specified mount._
## Build
To build and install `ffsend` yourself, you meet the following requirements
before proceeding:
### Build requirements
- Runtime [requirements](#requirements)
- Regular [requirements](#requirements)
- [`git`][git]
- [`rust`][rust] `v1.63` (MSRV) or higher (install using [`rustup`][rustup])
- [OpenSSL][openssl] or [LibreSSL][libressl] libraries/headers:
- Linux:
- Ubuntu, Debian and derivatives: `apt install build-essential cmake pkg-config libssl-dev`
- CentOS/Red Hat/openSUSE: `yum install gcc gcc-c++ make cmake openssl-devel`
- Arch: `pacman -S openssl base-devel`
- [`rust`][rust] `v1.32` or higher (install using [`rustup`][rustup])
- [OpenSSL][openssl] or [LibreSSL][libressl] libraries and headers must be available
- Linux:
- Ubuntu, Debian and derivatives: `apt install build-essential cmake pkg-config libssl-dev`
- CentOS/Red Hat/openSUSE: `yum install gcc gcc-c++ make cmake openssl-devel`
- Arch: `pacman -S openssl base-devel`
- Gentoo: `emerge -a dev-util/pkgconfig dev-util/cmake dev-libs/openssl`
- Fedora: `dnf install gcc gcc-c++ make cmake openssl-devel`
- Or see instructions [here](https://github.com/sfackler/rust-openssl#linux)
- Windows:
- Optional with `crypto-openssl` feature: See instructions here [here](https://github.com/sfackler/rust-openssl#windows-msvc)
- macOS:
- Optional with `crypto-openssl` feature: `brew install cmake pkg-config openssl` or see instructions [here](https://github.com/sfackler/rust-openssl#osx)
- FreeBSD:
- `pkg install rust gmake pkgconf python36 libxcb xclip ca_root_nss xsel-conrad`
- It is a better idea to use & modify the existing `ffsend` port, which manages dependencies for you.
- Fedora: `dnf install gcc gcc-c++ make cmake openssl-devel`
- Or see instructions [here](https://github.com/sfackler/rust-openssl#linux)
- Windows:
- See instructions here [here](https://github.com/sfackler/rust-openssl#windows-msvc)
- macOS:
- Using `brew`: `brew install cmake pkg-config openssl`
- Or see instructions [here](https://github.com/sfackler/rust-openssl#osx)
### Compile and install
Then, walk through one of the following steps to compile and install `ffsend`:
- Compile and install it directly from cargo:
```bash
# Compile and install from cargo
cargo install ffsend -f
```bash
# Compile and install from cargo
cargo install ffsend -f
# Start using ffsend
ffsend --help
```
# Start using ffsend
ffsend --help
```
- Or clone the repository and install it with `cargo`:
```bash
# Clone the project
git clone https://github.com/timvisee/ffsend.git
cd ffsend
```bash
# Clone the project
git clone https://github.com/timvisee/ffsend.git
cd ffsend
# Compile and install
cargo install --path . -f
# Compile and install
cargo install -f
# Start using ffsend
ffsend --help
# Start using ffsend
ffsend --help
# or run it directly from cargo
cargo run --release -- --help
```
# or run it directly from cargo
cargo run --release -- --help
```
- Or clone the repository and invoke the binary directly (Linux/macOS):
```bash
# Clone the project
git clone https://github.com/timvisee/ffsend.git
cd ffsend
```bash
# Clone the project
git clone https://github.com/timvisee/ffsend.git
cd ffsend
# Build the project (release version)
cargo build --release
# Build the project (release version)
cargo build --release
# Start using ffsend
./target/release/ffsend --help
```
# Start using ffsend
./target/release/ffsend --help
```
### Compile features / use flags
Different use flags are available for `ffsend` to toggle whether to include
various features.
The following features are available, some of which are enabled by default:
| Feature | Enabled | Description |
| :-------------: | :-----: | :--------------------------------------------------------- |
| `send2` | Default | Support for Send v2 servers |
| `send3` | Default | Support for Send v3 servers |
| `crypto-ring` | Default | Use ring as cryptography backend |
| `crypto-openssl`| | Use OpenSSL as cryptography backend |
| `clipboard` | Default | Support for copying links to the clipboard |
| `history` | Default | Support for tracking files in history |
| `archive` | Default | Support for archiving and extracting uploads and downloads |
| `qrcode` | Default | Support for rendering a QR code for a share URL |
| `urlshorten` | Default | Support for shortening share URLs |
| `infer-command` | Default | Support for inferring subcommand based on binary name |
| `no-color` | | Compile without color support in error and help messages |
| Feature | Enabled | Description |
| :---------: | :-----: | :--------------------------------------------------------- |
| `send2` | Default | Support for Firefox Send v2 servers |
| `send3` | Default | Support for Firefox Send v3 servers |
| `clipboard` | Default | Support for copying links to the clipboard |
| `history` | Default | Support for tracking files in history |
| `archive` | Default | Support for archiving and extracting uploads and downloads |
| `no-color` | | Compile without color support in error and help messages |
To enable features during building or installation, specify them with
`--features <features...>` when using `cargo`.
@ -535,11 +375,11 @@ Here are some examples:
cargo install --features no-color
cargo build --release --features no-color
# No default features, except required
cargo install --no-default-features --features send3,crypto-ring
# None of the features
cargo install --no-default-features
# With history and clipboard support
cargo install --no-default--features --features send3,crypto-ring,history,clipboard
# Only history and clipboard support
cargo install --no-default--features --features history,clipboard
```
For Windows systems it is recommended to provide the `no-color` flag, as color
@ -550,16 +390,13 @@ The following environment variables may be used to configure the following
defaults. The CLI flag is shown along with it, to better describe the relation
to command line arguments:
| Variable | CLI flag | Description |
| :------------------------ | :----------------------------: | :-------------------------------------------- |
| `FFSEND_HISTORY` | `--history <FILE>` | History file path |
| `FFSEND_HOST` | `--host <URL>` | Upload host |
| `FFSEND_TIMEOUT` | `--timeout <SECONDS>` | Request timeout (0 to disable) |
| `FFSEND_TRANSFER_TIMEOUT` | `--transfer-timeout <SECONDS>` | Transfer timeout (0 to disable) |
| `FFSEND_EXPIRY_TIME` | `--expiry-time <SECONDS>` | Default upload expiry time |
| `FFSEND_DOWNLOAD_LIMIT` | `--download-limit <DOWNLOADS>` | Default download limit |
| `FFSEND_API` | `--api <VERSION>` | Server API version, `-` to lookup |
| `FFSEND_BASIC_AUTH` | `--basic-auth <USER:PASSWORD>` | Basic HTTP authentication credentials to use. |
| Variable | CLI flag | Description |
| :------------------------ | :----------------------------: | :-------------------------------- |
| `FFSEND_HISTORY` | `--history <FILE>` | History file path |
| `FFSEND_HOST` | `--host <URL>` | Upload host |
| `FFSEND_TIMEOUT` | `--timeout <SECONDS>` | Request timeout (0 to disable) |
| `FFSEND_TRANSFER_TIMEOUT` | `--transfer-timeout <SECONDS>` | Transfer timeout (0 to disable) |
| `FFSEND_API` | `--api <VERSION>` | Server API version, `-` to lookup |
These environment variables may be used to toggle a flag, simply by making them
available. The actual value of these variables is ignored, and variables may be
@ -579,128 +416,13 @@ empty.
| `FFSEND_QUIET` | `--quiet` | Log quiet information |
| `FFSEND_VERBOSE` | `--verbose` | Log verbose information |
Some environment variables may be set at compile time to tweak some defaults.
| Variable | Description |
| :----------- | :------------------------------------------------------------------------- |
| `XCLIP_PATH` | Set fixed `xclip` binary path when using `clipboard-bin` (Linux, &ast;BSD) |
| `XSEL_PATH` | Set fixed `xsel` binary path when using `clipboard-bin` (Linux, &ast;BSD) |
At this time, no configuration or _dotfile_ file support is available.
This will be something added in a later release.
### Binary for each subcommand: `ffput`, `ffget`
`ffsend` supports having a separate binaries for single subcommands, such as
having `ffput` and `ffget` just for to upload and download using `ffsend`.
This allows simple and direct commands like:
```bash
ffput my-file.txt
ffget https://send.vis.ee/#sample-share-url
```
This works for a predefined list of binary names:
- `ffput``ffsend upload ...`
- `ffget``ffsend download ...`
- `ffdel``ffsend delete ...`
- _This list is defined in [`src/config.rs`](./src/config.rs) as `INFER_COMMANDS`_
You can use the following methods to set up these single-command binaries:
- Create a properly named symbolic link (recommended)
- Create a properly named hard link
- Clone the `ffsend` binary, and rename it
On Linux and macOS you can use the following command to set up symbolic links in
the current directory:
```bash
ln -s $(which ffsend) ./ffput
ln -s $(which ffsend) ./ffget
```
Support for this feature is only available when `ffsend` is compiled with the
[`infer-command`](#compile-features--use-flags) feature flag.
This is usually enabled by default.
To verify support is available with an existing installation, make sure the
feature is listed when invoking `ffsend debug`.
Note that the `snap` package does currently not support this due to how this
package format works.
### Scriptability
`ffsend` is optimized for use in automated scripts. It provides some specialized
arguments to control `ffsend` without user interaction.
- `--no-interact` (`-I`): do not allow user interaction. For prompts not having
a default value, the application will quit with an error, unless `--yes`
or `--force` is provided.
This should **always** be given when using automated scripting.
Example: when uploading a directory, providing this flag will stop the
archive question prompt form popping up, and will archive the directory as
default option.
- `--yes` (`-y`): assume the yes option for yes/no prompt by default.
Example: when downloading a file that already exists, providing this flag
will assume yes when asking to overwrite a file.
- `--force` (`-f`): force to continue with the action, skips any warnings that
would otherwise quit the application.
Example: when uploading a file that is too big, providing this flag will
ignore the file size warning and forcefully continues.
- `--quiet` (`-q`): be quiet, print as little information as possible.
Example: when uploading a file, providing this flag will only output the
final share URL.
Generally speaking, use the following rules when automating:
- Always provide `--no-interact` (`-I`).
- Provide any combination of `--yes` (`-y`) and `--force` (`-f`) for actions you
want to complete no matter what.
- When passing share URLs along, provide the `--quiet` (`-q`) flag, when
uploading for example.
These flags can also automatically be set by defining environment variables as
specified here:
[» Configuration and environment](#configuration-and-environment)
Here are some examples commands in `bash`:
```bash
# Stop on error
set -e
# Upload a file
# -I: no interaction
# -y: assume yes
# -q: quiet output, just return the share link
URL=$(ffsend -Iy upload -q my-file.txt)
# Render file information
# -I: no interaction
# -f: force, just show the info
ffsend -If info $URL
# Set a password for the uploaded file
ffsend -I password $URL --password="secret"
# Use the following flags automatically from now on
# -I: no interaction
# -f: force
# -y: yes
export FFSEND_NO_INTERACT=1 FFSEND_FORCE=1 FFSEND_YES=1
# Download the uploaded file, overwriting the local variant due to variables
ffsend download $URL --password="secret"
```
For more information on these arguments, invoke `ffsend help` and check out:
[» Configuration and environment](#configuration-and-environment)
For other questions regarding automation or feature requests, be sure to
[open](https://github.com/timvisee/ffsend/issues/) an issue.
## Security
In short; the `ffsend` tool and the [Send][send] service can be considered
secure, and may be used to share sensitive files. Note though that the
created share link for an upload will allow anyone to download the file.
created share link for an upload will allow anyone to download the file.
Make sure you don't share this link with unauthorized people.
For more detailed information on encryption, please read the rest of the
@ -744,13 +466,10 @@ documentation [here][send-encryption].
```
$ ffsend help
ffsend 0.2.72
Tim Visee <3a4fb3964f@sinenomine.email>
ffsend 0.2.29
Tim Visee <timvisee.com>
Easily and securely share files from the command line.
A fully featured Send client.
The default public Send host is provided by Tim Visee, @timvisee.
Please consider to donate and help keep it running: https://vis.ee/donate
A fully featured Firefox Send client.
USAGE:
ffsend [FLAGS] [OPTIONS] [SUBCOMMAND]
@ -766,8 +485,6 @@ FLAGS:
-y, --yes Assume yes for prompts
OPTIONS:
-A, --api <VERSION> Server API version to use, '-' to lookup [env: FFSEND_API]
--basic-auth <USER:PASSWORD> Protected proxy HTTP basic authentication credentials (not FxA) [env: FFSEND_BASIC_AUTH]
-H, --history <FILE> Use the specified history file [env: FFSEND_HISTORY]
-t, --timeout <SECONDS> Request timeout (0 to disable) [env: FFSEND_TIMEOUT]
-T, --transfer-timeout <SECONDS> Transfer timeout (0 to disable) [env: FFSEND_TRANSFER_TIMEOUT]
@ -776,9 +493,8 @@ SUBCOMMANDS:
upload Upload files [aliases: u, up]
download Download files [aliases: d, down]
debug View debug information [aliases: dbg]
delete Delete a shared file [aliases: del, rm]
delete Delete a shared file [aliases: del]
exists Check whether a remote file exists [aliases: e]
generate Generate assets [aliases: gen]
help Prints this message or the help of the given subcommand(s)
history View file history [aliases: h]
info Fetch info about a shared file [aliases: i]
@ -786,19 +502,20 @@ SUBCOMMANDS:
password Change the password of a shared file [aliases: pass, p]
version Determine the Send server version [aliases: v]
This application is not affiliated with Firefox or Mozilla.
The public Send service that is used as default host is provided by Mozilla.
This application is not affiliated with Mozilla, Firefox or Firefox Send.
```
## Special thanks
- to all `ffsend` source/package contributors
- to [Mozilla][mozilla] for building the amazing [Firefox Send][mozilla-send] service ([fork][timvisee-send])
- to [Mozilla][mozilla] for building and hosting the amazing
[Firefox Send][send] service
- to everyone involved with [asciinema][asciinema] and [svg-term][svg-term] for
providing tools to make great visual demos
- to everyone involved in all crate dependencies used
## License
This project is released under the GNU GPL-3.0 license.
Check out the [LICENSE](LICENSE) file for more information.
Check out the [LICENSE](LICENSE) file for more information.
[usage-demo-asciinema]: https://asciinema.org/a/182225
[usage-demo-svg]: https://cdn.rawgit.com/timvisee/ffsend/6e8ef55b/res/demo.svg
@ -807,29 +524,16 @@ Check out the [LICENSE](LICENSE) file for more information.
[libressl]: https://libressl.org/
[mozilla]: https://mozilla.org/
[openssl]: https://www.openssl.org/
[openssl-windows-installer]: https://u.visee.me/dl/openssl/Win64OpenSSL_Light-1_1_0j.exe
[termux]: https://termux.com/
[openssl-windows-installer]: https://slproweb.com/download/Win64OpenSSL_Light-1_1_0j.exe
[rust]: https://rust-lang.org/
[rustup]: https://rustup.rs/
[send]: https://github.com/timvisee/send
[mozilla-send]: https://github.com/mozilla/send
[timvisee-send]: https://github.com/timvisee/send
[send-encryption]: https://github.com/timvisee/send/blob/master/docs/encryption.md
[send]: https://send.firefox.com/
[send-encryption]: https://github.com/mozilla/send/blob/master/docs/encryption.md
[asciinema]: https://asciinema.org/
[svg-term]: https://github.com/marionebl/svg-term-cli
[github-releases]: https://github.com/timvisee/ffsend/releases
[github-latest-release]: https://github.com/timvisee/ffsend/releases/latest
[nix-ffsend]: https://nixos.org/nixos/packages.html?attr=ffsend&channel=nixos-unstable&query=ffsend
[fedora-ffsend]: https://src.fedoraproject.org/rpms/rust-ffsend
[aur-ffsend]: https://aur.archlinux.org/packages/ffsend/
[aur-ffsend-bin]: https://aur.archlinux.org/packages/ffsend-bin/
[aur-ffsend-git]: https://aur.archlinux.org/packages/ffsend-git/
[alpine-ffsend]: https://pkgs.alpinelinux.org/packages?name=ffsend&branch=edge
[snapcraft-ffsend]: https://snapcraft.io/ffsend
[homebrew]: https://brew.sh/
[wsl]: https://docs.microsoft.com/en-us/windows/wsl/install-win10
[docker-hub-ffsend]: https://hub.docker.com/r/timvisee/ffsend
[scoop-install]: https://scoop.sh/#installs-in-seconds
[freshports-ffsend]: https://www.freshports.org/www/ffsend
[timvisee]: https://timvisee.com/
[donate]: https://timvisee.com/donate

View file

@ -1,18 +0,0 @@
# Security Policy
## Supported Versions
| Version | Supported |
| ------- | ------------------ |
| 0.2.x | :white_check_mark: |
## Reporting a Vulnerability
To report a vulnerability in this project, or in one if it's dependency;
please [open](https://gitlab.com/timvisee/ffsend/issues/new) a new issue
on GitLab (or on [GitHub](https://github.com/timvisee/ffsend/issues/new)) describing the situation.
For confidential issues, you may send an email to the main developer (@timvisee) at: `3a4fb3964f@sinenomine.email`
Or see other methods of contacting over on: [timvisee.com/contact](https://timvisee.com/contact)
To contribute code for fixing a vulnerability, please refer to the [contribution](./CONTRIBUTING.md) document.

View file

@ -12,6 +12,7 @@ branches:
# Build for the x86_64 Windows target
platform: x64
environment:
SSL_CERT_FILE: "C:\\OpenSSL\\cacert.pem"
RUSTUP_USE_HYPER: 1
CARGO_HTTP_CHECK_REVOKE: false
GITHUB_TOKEN:
@ -22,14 +23,23 @@ environment:
matrix:
- TARGET: x86_64-pc-windows-msvc
BITS: 64
OPENSSL_VERSION: 1_1_0j
OPENSSL_DIR: C:\OpenSSL
# Extract release binary artifacts
artifacts:
- path: .\ffsend*.exe
- path: .\ffsend.*.nupkg
# Install dependencies: Rust
# Install dependencies: Rust and OpenSSL
install:
# Install OpenSSL
- mkdir C:\OpenSSL
- ps: if (Test-Path env:OPENSSL_VERSION) { Start-FileDownload "http://slproweb.com/download/Win${env:BITS}OpenSSL-${env:OPENSSL_VERSION}.exe" }
- if defined OPENSSL_VERSION Win%BITS%OpenSSL-%OPENSSL_VERSION%.exe /SILENT /VERYSILENT /SP- /DIR="C:\OpenSSL"
- appveyor DownloadFile https://curl.haxx.se/ca/cacert.pem -FileName C:\OpenSSL\cacert.pem
- choco install openssl.light --version 1.1.0.80000000 -y -f
# Install Rust
- appveyor-retry appveyor DownloadFile https://win.rustup.rs/ -FileName rustup-init.exe
- rustup-init.exe -y --default-host x86_64-pc-windows-msvc --default-toolchain stable
@ -63,8 +73,7 @@ build_script:
- cd ..\..\..\
# Create the release, upload the binaries
# TODO: fix the line below, which is causing CI to error
# - ps: Invoke-Expression -Command '$ErrorActionPreference = "SilentlyContinue"; .\github-release.exe release --token $env:GITHUB_TOKEN --owner timvisee --repo ffsend --tag $env:APPVEYOR_REPO_TAG_NAME --title "ffsend $env:APPVEYOR_REPO_TAG_NAME"; exit 0'
- ps: .\github-release.exe release --token $env:GITHUB_TOKEN --owner timvisee --repo ffsend --tag $env:APPVEYOR_REPO_TAG_NAME --title "ffsend $env:APPVEYOR_REPO_TAG_NAME"
- ps: .\github-release.exe upload --token $env:GITHUB_TOKEN --owner timvisee --repo ffsend --tag $env:APPVEYOR_REPO_TAG_NAME --file .\ffsend-$env:TARGET.exe --name ffsend-$env:APPVEYOR_REPO_TAG_NAME-windows-x64.exe
- ps: .\github-release.exe upload --token $env:GITHUB_TOKEN --owner timvisee --repo ffsend --tag $env:APPVEYOR_REPO_TAG_NAME --file .\ffsend-$env:TARGET-static.exe --name ffsend-$env:APPVEYOR_REPO_TAG_NAME-windows-x64-static.exe
- ps: .\github-release.exe upload --token $env:GITHUB_TOKEN --owner timvisee --repo ffsend --tag $env:APPVEYOR_REPO_TAG_NAME --file (Get-Item .\ffsend.*.nupkg).FullName --name ffsend-$env:APPVEYOR_REPO_TAG_NAME.nupkg

View file

@ -1,37 +0,0 @@
fn main() {
#[cfg(all(
feature = "clipboard",
any(
target_os = "linux",
target_os = "freebsd",
target_os = "dragonfly",
target_os = "openbsd",
target_os = "netbsd",
)
))]
{
// Select clipboard binary method
#[cfg(not(feature = "clipboard-crate"))]
println!("cargo:rustc-cfg=feature=\"clipboard-bin\"");
// xclip and xsel paths are inserted at compile time
println!("cargo:rerun-if-env-changed=XCLIP_PATH");
println!("cargo:rerun-if-env-changed=XSEL_PATH");
}
#[cfg(all(
feature = "clipboard",
not(any(
target_os = "linux",
target_os = "freebsd",
target_os = "dragonfly",
target_os = "openbsd",
target_os = "netbsd",
))
))]
{
// Select clipboard crate method
#[cfg(not(feature = "clipboard-bin"))]
println!("cargo:rustc-cfg=feature=\"clipboard-crate\"");
}
}

File diff suppressed because it is too large Load diff

View file

@ -1,515 +0,0 @@
using namespace System.Management.Automation
using namespace System.Management.Automation.Language
Register-ArgumentCompleter -Native -CommandName 'ffsend' -ScriptBlock {
param($wordToComplete, $commandAst, $cursorPosition)
$commandElements = $commandAst.CommandElements
$command = @(
'ffsend'
for ($i = 1; $i -lt $commandElements.Count; $i++) {
$element = $commandElements[$i]
if ($element -isnot [StringConstantExpressionAst] -or
$element.StringConstantType -ne [StringConstantType]::BareWord -or
$element.Value.StartsWith('-')) {
break
}
$element.Value
}) -join ';'
$completions = @(switch ($command) {
'ffsend' {
[CompletionResult]::new('-t', 't', [CompletionResultType]::ParameterName, 'Request timeout (0 to disable)')
[CompletionResult]::new('--timeout', 'timeout', [CompletionResultType]::ParameterName, 'Request timeout (0 to disable)')
[CompletionResult]::new('-T', 'T', [CompletionResultType]::ParameterName, 'Transfer timeout (0 to disable)')
[CompletionResult]::new('--transfer-timeout', 'transfer-timeout', [CompletionResultType]::ParameterName, 'Transfer timeout (0 to disable)')
[CompletionResult]::new('-A', 'A', [CompletionResultType]::ParameterName, 'Server API version to use, ''-'' to lookup')
[CompletionResult]::new('--api', 'api', [CompletionResultType]::ParameterName, 'Server API version to use, ''-'' to lookup')
[CompletionResult]::new('--basic-auth', 'basic-auth', [CompletionResultType]::ParameterName, 'Protected proxy HTTP basic authentication credentials (not FxA)')
[CompletionResult]::new('-H', 'H', [CompletionResultType]::ParameterName, 'Use the specified history file')
[CompletionResult]::new('--history', 'history', [CompletionResultType]::ParameterName, 'Use the specified history file')
[CompletionResult]::new('-f', 'f', [CompletionResultType]::ParameterName, 'Force the action, ignore warnings')
[CompletionResult]::new('--force', 'force', [CompletionResultType]::ParameterName, 'Force the action, ignore warnings')
[CompletionResult]::new('-I', 'I', [CompletionResultType]::ParameterName, 'Not interactive, do not prompt')
[CompletionResult]::new('--no-interact', 'no-interact', [CompletionResultType]::ParameterName, 'Not interactive, do not prompt')
[CompletionResult]::new('-y', 'y', [CompletionResultType]::ParameterName, 'Assume yes for prompts')
[CompletionResult]::new('--yes', 'yes', [CompletionResultType]::ParameterName, 'Assume yes for prompts')
[CompletionResult]::new('-q', 'q', [CompletionResultType]::ParameterName, 'Produce output suitable for logging and automation')
[CompletionResult]::new('--quiet', 'quiet', [CompletionResultType]::ParameterName, 'Produce output suitable for logging and automation')
[CompletionResult]::new('-v', 'v', [CompletionResultType]::ParameterName, 'Enable verbose information and logging')
[CompletionResult]::new('--verbose', 'verbose', [CompletionResultType]::ParameterName, 'Enable verbose information and logging')
[CompletionResult]::new('-i', 'i', [CompletionResultType]::ParameterName, 'Don''t update local history for actions')
[CompletionResult]::new('--incognito', 'incognito', [CompletionResultType]::ParameterName, 'Don''t update local history for actions')
[CompletionResult]::new('-h', 'h', [CompletionResultType]::ParameterName, 'Prints help information')
[CompletionResult]::new('--help', 'help', [CompletionResultType]::ParameterName, 'Prints help information')
[CompletionResult]::new('-V', 'V', [CompletionResultType]::ParameterName, 'Prints version information')
[CompletionResult]::new('--version', 'version', [CompletionResultType]::ParameterName, 'Prints version information')
[CompletionResult]::new('debug', 'debug', [CompletionResultType]::ParameterValue, 'View debug information')
[CompletionResult]::new('delete', 'delete', [CompletionResultType]::ParameterValue, 'Delete a shared file')
[CompletionResult]::new('download', 'download', [CompletionResultType]::ParameterValue, 'Download files')
[CompletionResult]::new('exists', 'exists', [CompletionResultType]::ParameterValue, 'Check whether a remote file exists')
[CompletionResult]::new('generate', 'generate', [CompletionResultType]::ParameterValue, 'Generate assets')
[CompletionResult]::new('info', 'info', [CompletionResultType]::ParameterValue, 'Fetch info about a shared file')
[CompletionResult]::new('parameters', 'parameters', [CompletionResultType]::ParameterValue, 'Change parameters of a shared file')
[CompletionResult]::new('password', 'password', [CompletionResultType]::ParameterValue, 'Change the password of a shared file')
[CompletionResult]::new('upload', 'upload', [CompletionResultType]::ParameterValue, 'Upload files')
[CompletionResult]::new('version', 'version', [CompletionResultType]::ParameterValue, 'Determine the Send server version')
[CompletionResult]::new('history', 'history', [CompletionResultType]::ParameterValue, 'View file history')
[CompletionResult]::new('help', 'help', [CompletionResultType]::ParameterValue, 'Prints this message or the help of the given subcommand(s)')
break
}
'ffsend;debug' {
[CompletionResult]::new('-h', 'h', [CompletionResultType]::ParameterName, 'The remote host to upload to')
[CompletionResult]::new('--host', 'host', [CompletionResultType]::ParameterName, 'The remote host to upload to')
[CompletionResult]::new('-t', 't', [CompletionResultType]::ParameterName, 'Request timeout (0 to disable)')
[CompletionResult]::new('--timeout', 'timeout', [CompletionResultType]::ParameterName, 'Request timeout (0 to disable)')
[CompletionResult]::new('-T', 'T', [CompletionResultType]::ParameterName, 'Transfer timeout (0 to disable)')
[CompletionResult]::new('--transfer-timeout', 'transfer-timeout', [CompletionResultType]::ParameterName, 'Transfer timeout (0 to disable)')
[CompletionResult]::new('-A', 'A', [CompletionResultType]::ParameterName, 'Server API version to use, ''-'' to lookup')
[CompletionResult]::new('--api', 'api', [CompletionResultType]::ParameterName, 'Server API version to use, ''-'' to lookup')
[CompletionResult]::new('--basic-auth', 'basic-auth', [CompletionResultType]::ParameterName, 'Protected proxy HTTP basic authentication credentials (not FxA)')
[CompletionResult]::new('-H', 'H', [CompletionResultType]::ParameterName, 'Use the specified history file')
[CompletionResult]::new('--history', 'history', [CompletionResultType]::ParameterName, 'Use the specified history file')
[CompletionResult]::new('--help', 'help', [CompletionResultType]::ParameterName, 'Prints help information')
[CompletionResult]::new('-V', 'V', [CompletionResultType]::ParameterName, 'Prints version information')
[CompletionResult]::new('--version', 'version', [CompletionResultType]::ParameterName, 'Prints version information')
[CompletionResult]::new('-f', 'f', [CompletionResultType]::ParameterName, 'Force the action, ignore warnings')
[CompletionResult]::new('--force', 'force', [CompletionResultType]::ParameterName, 'Force the action, ignore warnings')
[CompletionResult]::new('-I', 'I', [CompletionResultType]::ParameterName, 'Not interactive, do not prompt')
[CompletionResult]::new('--no-interact', 'no-interact', [CompletionResultType]::ParameterName, 'Not interactive, do not prompt')
[CompletionResult]::new('-y', 'y', [CompletionResultType]::ParameterName, 'Assume yes for prompts')
[CompletionResult]::new('--yes', 'yes', [CompletionResultType]::ParameterName, 'Assume yes for prompts')
[CompletionResult]::new('-q', 'q', [CompletionResultType]::ParameterName, 'Produce output suitable for logging and automation')
[CompletionResult]::new('--quiet', 'quiet', [CompletionResultType]::ParameterName, 'Produce output suitable for logging and automation')
[CompletionResult]::new('-v', 'v', [CompletionResultType]::ParameterName, 'Enable verbose information and logging')
[CompletionResult]::new('--verbose', 'verbose', [CompletionResultType]::ParameterName, 'Enable verbose information and logging')
[CompletionResult]::new('-i', 'i', [CompletionResultType]::ParameterName, 'Don''t update local history for actions')
[CompletionResult]::new('--incognito', 'incognito', [CompletionResultType]::ParameterName, 'Don''t update local history for actions')
break
}
'ffsend;delete' {
[CompletionResult]::new('-o', 'o', [CompletionResultType]::ParameterName, 'Specify the file owner token')
[CompletionResult]::new('--owner', 'owner', [CompletionResultType]::ParameterName, 'Specify the file owner token')
[CompletionResult]::new('-t', 't', [CompletionResultType]::ParameterName, 'Request timeout (0 to disable)')
[CompletionResult]::new('--timeout', 'timeout', [CompletionResultType]::ParameterName, 'Request timeout (0 to disable)')
[CompletionResult]::new('-T', 'T', [CompletionResultType]::ParameterName, 'Transfer timeout (0 to disable)')
[CompletionResult]::new('--transfer-timeout', 'transfer-timeout', [CompletionResultType]::ParameterName, 'Transfer timeout (0 to disable)')
[CompletionResult]::new('-A', 'A', [CompletionResultType]::ParameterName, 'Server API version to use, ''-'' to lookup')
[CompletionResult]::new('--api', 'api', [CompletionResultType]::ParameterName, 'Server API version to use, ''-'' to lookup')
[CompletionResult]::new('--basic-auth', 'basic-auth', [CompletionResultType]::ParameterName, 'Protected proxy HTTP basic authentication credentials (not FxA)')
[CompletionResult]::new('-H', 'H', [CompletionResultType]::ParameterName, 'Use the specified history file')
[CompletionResult]::new('--history', 'history', [CompletionResultType]::ParameterName, 'Use the specified history file')
[CompletionResult]::new('-h', 'h', [CompletionResultType]::ParameterName, 'Prints help information')
[CompletionResult]::new('--help', 'help', [CompletionResultType]::ParameterName, 'Prints help information')
[CompletionResult]::new('-V', 'V', [CompletionResultType]::ParameterName, 'Prints version information')
[CompletionResult]::new('--version', 'version', [CompletionResultType]::ParameterName, 'Prints version information')
[CompletionResult]::new('-f', 'f', [CompletionResultType]::ParameterName, 'Force the action, ignore warnings')
[CompletionResult]::new('--force', 'force', [CompletionResultType]::ParameterName, 'Force the action, ignore warnings')
[CompletionResult]::new('-I', 'I', [CompletionResultType]::ParameterName, 'Not interactive, do not prompt')
[CompletionResult]::new('--no-interact', 'no-interact', [CompletionResultType]::ParameterName, 'Not interactive, do not prompt')
[CompletionResult]::new('-y', 'y', [CompletionResultType]::ParameterName, 'Assume yes for prompts')
[CompletionResult]::new('--yes', 'yes', [CompletionResultType]::ParameterName, 'Assume yes for prompts')
[CompletionResult]::new('-q', 'q', [CompletionResultType]::ParameterName, 'Produce output suitable for logging and automation')
[CompletionResult]::new('--quiet', 'quiet', [CompletionResultType]::ParameterName, 'Produce output suitable for logging and automation')
[CompletionResult]::new('-v', 'v', [CompletionResultType]::ParameterName, 'Enable verbose information and logging')
[CompletionResult]::new('--verbose', 'verbose', [CompletionResultType]::ParameterName, 'Enable verbose information and logging')
[CompletionResult]::new('-i', 'i', [CompletionResultType]::ParameterName, 'Don''t update local history for actions')
[CompletionResult]::new('--incognito', 'incognito', [CompletionResultType]::ParameterName, 'Don''t update local history for actions')
break
}
'ffsend;download' {
[CompletionResult]::new('-p', 'p', [CompletionResultType]::ParameterName, 'Unlock a password protected file')
[CompletionResult]::new('--password', 'password', [CompletionResultType]::ParameterName, 'Unlock a password protected file')
[CompletionResult]::new('-o', 'o', [CompletionResultType]::ParameterName, 'Output file or directory')
[CompletionResult]::new('--output', 'output', [CompletionResultType]::ParameterName, 'Output file or directory')
[CompletionResult]::new('-t', 't', [CompletionResultType]::ParameterName, 'Request timeout (0 to disable)')
[CompletionResult]::new('--timeout', 'timeout', [CompletionResultType]::ParameterName, 'Request timeout (0 to disable)')
[CompletionResult]::new('-T', 'T', [CompletionResultType]::ParameterName, 'Transfer timeout (0 to disable)')
[CompletionResult]::new('--transfer-timeout', 'transfer-timeout', [CompletionResultType]::ParameterName, 'Transfer timeout (0 to disable)')
[CompletionResult]::new('-A', 'A', [CompletionResultType]::ParameterName, 'Server API version to use, ''-'' to lookup')
[CompletionResult]::new('--api', 'api', [CompletionResultType]::ParameterName, 'Server API version to use, ''-'' to lookup')
[CompletionResult]::new('--basic-auth', 'basic-auth', [CompletionResultType]::ParameterName, 'Protected proxy HTTP basic authentication credentials (not FxA)')
[CompletionResult]::new('-H', 'H', [CompletionResultType]::ParameterName, 'Use the specified history file')
[CompletionResult]::new('--history', 'history', [CompletionResultType]::ParameterName, 'Use the specified history file')
[CompletionResult]::new('-e', 'e', [CompletionResultType]::ParameterName, 'Extract an archived file')
[CompletionResult]::new('--extract', 'extract', [CompletionResultType]::ParameterName, 'Extract an archived file')
[CompletionResult]::new('-h', 'h', [CompletionResultType]::ParameterName, 'Prints help information')
[CompletionResult]::new('--help', 'help', [CompletionResultType]::ParameterName, 'Prints help information')
[CompletionResult]::new('-V', 'V', [CompletionResultType]::ParameterName, 'Prints version information')
[CompletionResult]::new('--version', 'version', [CompletionResultType]::ParameterName, 'Prints version information')
[CompletionResult]::new('-f', 'f', [CompletionResultType]::ParameterName, 'Force the action, ignore warnings')
[CompletionResult]::new('--force', 'force', [CompletionResultType]::ParameterName, 'Force the action, ignore warnings')
[CompletionResult]::new('-I', 'I', [CompletionResultType]::ParameterName, 'Not interactive, do not prompt')
[CompletionResult]::new('--no-interact', 'no-interact', [CompletionResultType]::ParameterName, 'Not interactive, do not prompt')
[CompletionResult]::new('-y', 'y', [CompletionResultType]::ParameterName, 'Assume yes for prompts')
[CompletionResult]::new('--yes', 'yes', [CompletionResultType]::ParameterName, 'Assume yes for prompts')
[CompletionResult]::new('-q', 'q', [CompletionResultType]::ParameterName, 'Produce output suitable for logging and automation')
[CompletionResult]::new('--quiet', 'quiet', [CompletionResultType]::ParameterName, 'Produce output suitable for logging and automation')
[CompletionResult]::new('-v', 'v', [CompletionResultType]::ParameterName, 'Enable verbose information and logging')
[CompletionResult]::new('--verbose', 'verbose', [CompletionResultType]::ParameterName, 'Enable verbose information and logging')
[CompletionResult]::new('-i', 'i', [CompletionResultType]::ParameterName, 'Don''t update local history for actions')
[CompletionResult]::new('--incognito', 'incognito', [CompletionResultType]::ParameterName, 'Don''t update local history for actions')
break
}
'ffsend;exists' {
[CompletionResult]::new('-t', 't', [CompletionResultType]::ParameterName, 'Request timeout (0 to disable)')
[CompletionResult]::new('--timeout', 'timeout', [CompletionResultType]::ParameterName, 'Request timeout (0 to disable)')
[CompletionResult]::new('-T', 'T', [CompletionResultType]::ParameterName, 'Transfer timeout (0 to disable)')
[CompletionResult]::new('--transfer-timeout', 'transfer-timeout', [CompletionResultType]::ParameterName, 'Transfer timeout (0 to disable)')
[CompletionResult]::new('-A', 'A', [CompletionResultType]::ParameterName, 'Server API version to use, ''-'' to lookup')
[CompletionResult]::new('--api', 'api', [CompletionResultType]::ParameterName, 'Server API version to use, ''-'' to lookup')
[CompletionResult]::new('--basic-auth', 'basic-auth', [CompletionResultType]::ParameterName, 'Protected proxy HTTP basic authentication credentials (not FxA)')
[CompletionResult]::new('-H', 'H', [CompletionResultType]::ParameterName, 'Use the specified history file')
[CompletionResult]::new('--history', 'history', [CompletionResultType]::ParameterName, 'Use the specified history file')
[CompletionResult]::new('-h', 'h', [CompletionResultType]::ParameterName, 'Prints help information')
[CompletionResult]::new('--help', 'help', [CompletionResultType]::ParameterName, 'Prints help information')
[CompletionResult]::new('-V', 'V', [CompletionResultType]::ParameterName, 'Prints version information')
[CompletionResult]::new('--version', 'version', [CompletionResultType]::ParameterName, 'Prints version information')
[CompletionResult]::new('-f', 'f', [CompletionResultType]::ParameterName, 'Force the action, ignore warnings')
[CompletionResult]::new('--force', 'force', [CompletionResultType]::ParameterName, 'Force the action, ignore warnings')
[CompletionResult]::new('-I', 'I', [CompletionResultType]::ParameterName, 'Not interactive, do not prompt')
[CompletionResult]::new('--no-interact', 'no-interact', [CompletionResultType]::ParameterName, 'Not interactive, do not prompt')
[CompletionResult]::new('-y', 'y', [CompletionResultType]::ParameterName, 'Assume yes for prompts')
[CompletionResult]::new('--yes', 'yes', [CompletionResultType]::ParameterName, 'Assume yes for prompts')
[CompletionResult]::new('-q', 'q', [CompletionResultType]::ParameterName, 'Produce output suitable for logging and automation')
[CompletionResult]::new('--quiet', 'quiet', [CompletionResultType]::ParameterName, 'Produce output suitable for logging and automation')
[CompletionResult]::new('-v', 'v', [CompletionResultType]::ParameterName, 'Enable verbose information and logging')
[CompletionResult]::new('--verbose', 'verbose', [CompletionResultType]::ParameterName, 'Enable verbose information and logging')
[CompletionResult]::new('-i', 'i', [CompletionResultType]::ParameterName, 'Don''t update local history for actions')
[CompletionResult]::new('--incognito', 'incognito', [CompletionResultType]::ParameterName, 'Don''t update local history for actions')
break
}
'ffsend;generate' {
[CompletionResult]::new('-t', 't', [CompletionResultType]::ParameterName, 'Request timeout (0 to disable)')
[CompletionResult]::new('--timeout', 'timeout', [CompletionResultType]::ParameterName, 'Request timeout (0 to disable)')
[CompletionResult]::new('-T', 'T', [CompletionResultType]::ParameterName, 'Transfer timeout (0 to disable)')
[CompletionResult]::new('--transfer-timeout', 'transfer-timeout', [CompletionResultType]::ParameterName, 'Transfer timeout (0 to disable)')
[CompletionResult]::new('-A', 'A', [CompletionResultType]::ParameterName, 'Server API version to use, ''-'' to lookup')
[CompletionResult]::new('--api', 'api', [CompletionResultType]::ParameterName, 'Server API version to use, ''-'' to lookup')
[CompletionResult]::new('--basic-auth', 'basic-auth', [CompletionResultType]::ParameterName, 'Protected proxy HTTP basic authentication credentials (not FxA)')
[CompletionResult]::new('-H', 'H', [CompletionResultType]::ParameterName, 'Use the specified history file')
[CompletionResult]::new('--history', 'history', [CompletionResultType]::ParameterName, 'Use the specified history file')
[CompletionResult]::new('-h', 'h', [CompletionResultType]::ParameterName, 'Prints help information')
[CompletionResult]::new('--help', 'help', [CompletionResultType]::ParameterName, 'Prints help information')
[CompletionResult]::new('-V', 'V', [CompletionResultType]::ParameterName, 'Prints version information')
[CompletionResult]::new('--version', 'version', [CompletionResultType]::ParameterName, 'Prints version information')
[CompletionResult]::new('-f', 'f', [CompletionResultType]::ParameterName, 'Force the action, ignore warnings')
[CompletionResult]::new('--force', 'force', [CompletionResultType]::ParameterName, 'Force the action, ignore warnings')
[CompletionResult]::new('-I', 'I', [CompletionResultType]::ParameterName, 'Not interactive, do not prompt')
[CompletionResult]::new('--no-interact', 'no-interact', [CompletionResultType]::ParameterName, 'Not interactive, do not prompt')
[CompletionResult]::new('-y', 'y', [CompletionResultType]::ParameterName, 'Assume yes for prompts')
[CompletionResult]::new('--yes', 'yes', [CompletionResultType]::ParameterName, 'Assume yes for prompts')
[CompletionResult]::new('-q', 'q', [CompletionResultType]::ParameterName, 'Produce output suitable for logging and automation')
[CompletionResult]::new('--quiet', 'quiet', [CompletionResultType]::ParameterName, 'Produce output suitable for logging and automation')
[CompletionResult]::new('-v', 'v', [CompletionResultType]::ParameterName, 'Enable verbose information and logging')
[CompletionResult]::new('--verbose', 'verbose', [CompletionResultType]::ParameterName, 'Enable verbose information and logging')
[CompletionResult]::new('-i', 'i', [CompletionResultType]::ParameterName, 'Don''t update local history for actions')
[CompletionResult]::new('--incognito', 'incognito', [CompletionResultType]::ParameterName, 'Don''t update local history for actions')
[CompletionResult]::new('completions', 'completions', [CompletionResultType]::ParameterValue, 'Shell completions')
[CompletionResult]::new('help', 'help', [CompletionResultType]::ParameterValue, 'Prints this message or the help of the given subcommand(s)')
break
}
'ffsend;generate;completions' {
[CompletionResult]::new('-o', 'o', [CompletionResultType]::ParameterName, 'Shell completion files output directory')
[CompletionResult]::new('--output', 'output', [CompletionResultType]::ParameterName, 'Shell completion files output directory')
[CompletionResult]::new('-t', 't', [CompletionResultType]::ParameterName, 'Request timeout (0 to disable)')
[CompletionResult]::new('--timeout', 'timeout', [CompletionResultType]::ParameterName, 'Request timeout (0 to disable)')
[CompletionResult]::new('-T', 'T', [CompletionResultType]::ParameterName, 'Transfer timeout (0 to disable)')
[CompletionResult]::new('--transfer-timeout', 'transfer-timeout', [CompletionResultType]::ParameterName, 'Transfer timeout (0 to disable)')
[CompletionResult]::new('-A', 'A', [CompletionResultType]::ParameterName, 'Server API version to use, ''-'' to lookup')
[CompletionResult]::new('--api', 'api', [CompletionResultType]::ParameterName, 'Server API version to use, ''-'' to lookup')
[CompletionResult]::new('--basic-auth', 'basic-auth', [CompletionResultType]::ParameterName, 'Protected proxy HTTP basic authentication credentials (not FxA)')
[CompletionResult]::new('-H', 'H', [CompletionResultType]::ParameterName, 'Use the specified history file')
[CompletionResult]::new('--history', 'history', [CompletionResultType]::ParameterName, 'Use the specified history file')
[CompletionResult]::new('-h', 'h', [CompletionResultType]::ParameterName, 'Prints help information')
[CompletionResult]::new('--help', 'help', [CompletionResultType]::ParameterName, 'Prints help information')
[CompletionResult]::new('-V', 'V', [CompletionResultType]::ParameterName, 'Prints version information')
[CompletionResult]::new('--version', 'version', [CompletionResultType]::ParameterName, 'Prints version information')
[CompletionResult]::new('-f', 'f', [CompletionResultType]::ParameterName, 'Force the action, ignore warnings')
[CompletionResult]::new('--force', 'force', [CompletionResultType]::ParameterName, 'Force the action, ignore warnings')
[CompletionResult]::new('-I', 'I', [CompletionResultType]::ParameterName, 'Not interactive, do not prompt')
[CompletionResult]::new('--no-interact', 'no-interact', [CompletionResultType]::ParameterName, 'Not interactive, do not prompt')
[CompletionResult]::new('-y', 'y', [CompletionResultType]::ParameterName, 'Assume yes for prompts')
[CompletionResult]::new('--yes', 'yes', [CompletionResultType]::ParameterName, 'Assume yes for prompts')
[CompletionResult]::new('-q', 'q', [CompletionResultType]::ParameterName, 'Produce output suitable for logging and automation')
[CompletionResult]::new('--quiet', 'quiet', [CompletionResultType]::ParameterName, 'Produce output suitable for logging and automation')
[CompletionResult]::new('-v', 'v', [CompletionResultType]::ParameterName, 'Enable verbose information and logging')
[CompletionResult]::new('--verbose', 'verbose', [CompletionResultType]::ParameterName, 'Enable verbose information and logging')
[CompletionResult]::new('-i', 'i', [CompletionResultType]::ParameterName, 'Don''t update local history for actions')
[CompletionResult]::new('--incognito', 'incognito', [CompletionResultType]::ParameterName, 'Don''t update local history for actions')
break
}
'ffsend;generate;help' {
[CompletionResult]::new('-t', 't', [CompletionResultType]::ParameterName, 'Request timeout (0 to disable)')
[CompletionResult]::new('--timeout', 'timeout', [CompletionResultType]::ParameterName, 'Request timeout (0 to disable)')
[CompletionResult]::new('-T', 'T', [CompletionResultType]::ParameterName, 'Transfer timeout (0 to disable)')
[CompletionResult]::new('--transfer-timeout', 'transfer-timeout', [CompletionResultType]::ParameterName, 'Transfer timeout (0 to disable)')
[CompletionResult]::new('-A', 'A', [CompletionResultType]::ParameterName, 'Server API version to use, ''-'' to lookup')
[CompletionResult]::new('--api', 'api', [CompletionResultType]::ParameterName, 'Server API version to use, ''-'' to lookup')
[CompletionResult]::new('--basic-auth', 'basic-auth', [CompletionResultType]::ParameterName, 'Protected proxy HTTP basic authentication credentials (not FxA)')
[CompletionResult]::new('-H', 'H', [CompletionResultType]::ParameterName, 'Use the specified history file')
[CompletionResult]::new('--history', 'history', [CompletionResultType]::ParameterName, 'Use the specified history file')
[CompletionResult]::new('-h', 'h', [CompletionResultType]::ParameterName, 'Prints help information')
[CompletionResult]::new('--help', 'help', [CompletionResultType]::ParameterName, 'Prints help information')
[CompletionResult]::new('-V', 'V', [CompletionResultType]::ParameterName, 'Prints version information')
[CompletionResult]::new('--version', 'version', [CompletionResultType]::ParameterName, 'Prints version information')
[CompletionResult]::new('-f', 'f', [CompletionResultType]::ParameterName, 'Force the action, ignore warnings')
[CompletionResult]::new('--force', 'force', [CompletionResultType]::ParameterName, 'Force the action, ignore warnings')
[CompletionResult]::new('-I', 'I', [CompletionResultType]::ParameterName, 'Not interactive, do not prompt')
[CompletionResult]::new('--no-interact', 'no-interact', [CompletionResultType]::ParameterName, 'Not interactive, do not prompt')
[CompletionResult]::new('-y', 'y', [CompletionResultType]::ParameterName, 'Assume yes for prompts')
[CompletionResult]::new('--yes', 'yes', [CompletionResultType]::ParameterName, 'Assume yes for prompts')
[CompletionResult]::new('-q', 'q', [CompletionResultType]::ParameterName, 'Produce output suitable for logging and automation')
[CompletionResult]::new('--quiet', 'quiet', [CompletionResultType]::ParameterName, 'Produce output suitable for logging and automation')
[CompletionResult]::new('-v', 'v', [CompletionResultType]::ParameterName, 'Enable verbose information and logging')
[CompletionResult]::new('--verbose', 'verbose', [CompletionResultType]::ParameterName, 'Enable verbose information and logging')
[CompletionResult]::new('-i', 'i', [CompletionResultType]::ParameterName, 'Don''t update local history for actions')
[CompletionResult]::new('--incognito', 'incognito', [CompletionResultType]::ParameterName, 'Don''t update local history for actions')
break
}
'ffsend;info' {
[CompletionResult]::new('-o', 'o', [CompletionResultType]::ParameterName, 'Specify the file owner token')
[CompletionResult]::new('--owner', 'owner', [CompletionResultType]::ParameterName, 'Specify the file owner token')
[CompletionResult]::new('-p', 'p', [CompletionResultType]::ParameterName, 'Unlock a password protected file')
[CompletionResult]::new('--password', 'password', [CompletionResultType]::ParameterName, 'Unlock a password protected file')
[CompletionResult]::new('-t', 't', [CompletionResultType]::ParameterName, 'Request timeout (0 to disable)')
[CompletionResult]::new('--timeout', 'timeout', [CompletionResultType]::ParameterName, 'Request timeout (0 to disable)')
[CompletionResult]::new('-T', 'T', [CompletionResultType]::ParameterName, 'Transfer timeout (0 to disable)')
[CompletionResult]::new('--transfer-timeout', 'transfer-timeout', [CompletionResultType]::ParameterName, 'Transfer timeout (0 to disable)')
[CompletionResult]::new('-A', 'A', [CompletionResultType]::ParameterName, 'Server API version to use, ''-'' to lookup')
[CompletionResult]::new('--api', 'api', [CompletionResultType]::ParameterName, 'Server API version to use, ''-'' to lookup')
[CompletionResult]::new('--basic-auth', 'basic-auth', [CompletionResultType]::ParameterName, 'Protected proxy HTTP basic authentication credentials (not FxA)')
[CompletionResult]::new('-H', 'H', [CompletionResultType]::ParameterName, 'Use the specified history file')
[CompletionResult]::new('--history', 'history', [CompletionResultType]::ParameterName, 'Use the specified history file')
[CompletionResult]::new('-h', 'h', [CompletionResultType]::ParameterName, 'Prints help information')
[CompletionResult]::new('--help', 'help', [CompletionResultType]::ParameterName, 'Prints help information')
[CompletionResult]::new('-V', 'V', [CompletionResultType]::ParameterName, 'Prints version information')
[CompletionResult]::new('--version', 'version', [CompletionResultType]::ParameterName, 'Prints version information')
[CompletionResult]::new('-f', 'f', [CompletionResultType]::ParameterName, 'Force the action, ignore warnings')
[CompletionResult]::new('--force', 'force', [CompletionResultType]::ParameterName, 'Force the action, ignore warnings')
[CompletionResult]::new('-I', 'I', [CompletionResultType]::ParameterName, 'Not interactive, do not prompt')
[CompletionResult]::new('--no-interact', 'no-interact', [CompletionResultType]::ParameterName, 'Not interactive, do not prompt')
[CompletionResult]::new('-y', 'y', [CompletionResultType]::ParameterName, 'Assume yes for prompts')
[CompletionResult]::new('--yes', 'yes', [CompletionResultType]::ParameterName, 'Assume yes for prompts')
[CompletionResult]::new('-q', 'q', [CompletionResultType]::ParameterName, 'Produce output suitable for logging and automation')
[CompletionResult]::new('--quiet', 'quiet', [CompletionResultType]::ParameterName, 'Produce output suitable for logging and automation')
[CompletionResult]::new('-v', 'v', [CompletionResultType]::ParameterName, 'Enable verbose information and logging')
[CompletionResult]::new('--verbose', 'verbose', [CompletionResultType]::ParameterName, 'Enable verbose information and logging')
[CompletionResult]::new('-i', 'i', [CompletionResultType]::ParameterName, 'Don''t update local history for actions')
[CompletionResult]::new('--incognito', 'incognito', [CompletionResultType]::ParameterName, 'Don''t update local history for actions')
break
}
'ffsend;parameters' {
[CompletionResult]::new('-o', 'o', [CompletionResultType]::ParameterName, 'Specify the file owner token')
[CompletionResult]::new('--owner', 'owner', [CompletionResultType]::ParameterName, 'Specify the file owner token')
[CompletionResult]::new('-d', 'd', [CompletionResultType]::ParameterName, 'The file download limit')
[CompletionResult]::new('--download-limit', 'download-limit', [CompletionResultType]::ParameterName, 'The file download limit')
[CompletionResult]::new('-t', 't', [CompletionResultType]::ParameterName, 'Request timeout (0 to disable)')
[CompletionResult]::new('--timeout', 'timeout', [CompletionResultType]::ParameterName, 'Request timeout (0 to disable)')
[CompletionResult]::new('-T', 'T', [CompletionResultType]::ParameterName, 'Transfer timeout (0 to disable)')
[CompletionResult]::new('--transfer-timeout', 'transfer-timeout', [CompletionResultType]::ParameterName, 'Transfer timeout (0 to disable)')
[CompletionResult]::new('-A', 'A', [CompletionResultType]::ParameterName, 'Server API version to use, ''-'' to lookup')
[CompletionResult]::new('--api', 'api', [CompletionResultType]::ParameterName, 'Server API version to use, ''-'' to lookup')
[CompletionResult]::new('--basic-auth', 'basic-auth', [CompletionResultType]::ParameterName, 'Protected proxy HTTP basic authentication credentials (not FxA)')
[CompletionResult]::new('-H', 'H', [CompletionResultType]::ParameterName, 'Use the specified history file')
[CompletionResult]::new('--history', 'history', [CompletionResultType]::ParameterName, 'Use the specified history file')
[CompletionResult]::new('-h', 'h', [CompletionResultType]::ParameterName, 'Prints help information')
[CompletionResult]::new('--help', 'help', [CompletionResultType]::ParameterName, 'Prints help information')
[CompletionResult]::new('-V', 'V', [CompletionResultType]::ParameterName, 'Prints version information')
[CompletionResult]::new('--version', 'version', [CompletionResultType]::ParameterName, 'Prints version information')
[CompletionResult]::new('-f', 'f', [CompletionResultType]::ParameterName, 'Force the action, ignore warnings')
[CompletionResult]::new('--force', 'force', [CompletionResultType]::ParameterName, 'Force the action, ignore warnings')
[CompletionResult]::new('-I', 'I', [CompletionResultType]::ParameterName, 'Not interactive, do not prompt')
[CompletionResult]::new('--no-interact', 'no-interact', [CompletionResultType]::ParameterName, 'Not interactive, do not prompt')
[CompletionResult]::new('-y', 'y', [CompletionResultType]::ParameterName, 'Assume yes for prompts')
[CompletionResult]::new('--yes', 'yes', [CompletionResultType]::ParameterName, 'Assume yes for prompts')
[CompletionResult]::new('-q', 'q', [CompletionResultType]::ParameterName, 'Produce output suitable for logging and automation')
[CompletionResult]::new('--quiet', 'quiet', [CompletionResultType]::ParameterName, 'Produce output suitable for logging and automation')
[CompletionResult]::new('-v', 'v', [CompletionResultType]::ParameterName, 'Enable verbose information and logging')
[CompletionResult]::new('--verbose', 'verbose', [CompletionResultType]::ParameterName, 'Enable verbose information and logging')
[CompletionResult]::new('-i', 'i', [CompletionResultType]::ParameterName, 'Don''t update local history for actions')
[CompletionResult]::new('--incognito', 'incognito', [CompletionResultType]::ParameterName, 'Don''t update local history for actions')
break
}
'ffsend;password' {
[CompletionResult]::new('-p', 'p', [CompletionResultType]::ParameterName, 'Specify a password, do not prompt')
[CompletionResult]::new('--password', 'password', [CompletionResultType]::ParameterName, 'Specify a password, do not prompt')
[CompletionResult]::new('-o', 'o', [CompletionResultType]::ParameterName, 'Specify the file owner token')
[CompletionResult]::new('--owner', 'owner', [CompletionResultType]::ParameterName, 'Specify the file owner token')
[CompletionResult]::new('-t', 't', [CompletionResultType]::ParameterName, 'Request timeout (0 to disable)')
[CompletionResult]::new('--timeout', 'timeout', [CompletionResultType]::ParameterName, 'Request timeout (0 to disable)')
[CompletionResult]::new('-T', 'T', [CompletionResultType]::ParameterName, 'Transfer timeout (0 to disable)')
[CompletionResult]::new('--transfer-timeout', 'transfer-timeout', [CompletionResultType]::ParameterName, 'Transfer timeout (0 to disable)')
[CompletionResult]::new('-A', 'A', [CompletionResultType]::ParameterName, 'Server API version to use, ''-'' to lookup')
[CompletionResult]::new('--api', 'api', [CompletionResultType]::ParameterName, 'Server API version to use, ''-'' to lookup')
[CompletionResult]::new('--basic-auth', 'basic-auth', [CompletionResultType]::ParameterName, 'Protected proxy HTTP basic authentication credentials (not FxA)')
[CompletionResult]::new('-H', 'H', [CompletionResultType]::ParameterName, 'Use the specified history file')
[CompletionResult]::new('--history', 'history', [CompletionResultType]::ParameterName, 'Use the specified history file')
[CompletionResult]::new('-P', 'P', [CompletionResultType]::ParameterName, 'Protect the file with a generated passphrase')
[CompletionResult]::new('--gen-passphrase', 'gen-passphrase', [CompletionResultType]::ParameterName, 'Protect the file with a generated passphrase')
[CompletionResult]::new('-h', 'h', [CompletionResultType]::ParameterName, 'Prints help information')
[CompletionResult]::new('--help', 'help', [CompletionResultType]::ParameterName, 'Prints help information')
[CompletionResult]::new('-V', 'V', [CompletionResultType]::ParameterName, 'Prints version information')
[CompletionResult]::new('--version', 'version', [CompletionResultType]::ParameterName, 'Prints version information')
[CompletionResult]::new('-f', 'f', [CompletionResultType]::ParameterName, 'Force the action, ignore warnings')
[CompletionResult]::new('--force', 'force', [CompletionResultType]::ParameterName, 'Force the action, ignore warnings')
[CompletionResult]::new('-I', 'I', [CompletionResultType]::ParameterName, 'Not interactive, do not prompt')
[CompletionResult]::new('--no-interact', 'no-interact', [CompletionResultType]::ParameterName, 'Not interactive, do not prompt')
[CompletionResult]::new('-y', 'y', [CompletionResultType]::ParameterName, 'Assume yes for prompts')
[CompletionResult]::new('--yes', 'yes', [CompletionResultType]::ParameterName, 'Assume yes for prompts')
[CompletionResult]::new('-q', 'q', [CompletionResultType]::ParameterName, 'Produce output suitable for logging and automation')
[CompletionResult]::new('--quiet', 'quiet', [CompletionResultType]::ParameterName, 'Produce output suitable for logging and automation')
[CompletionResult]::new('-v', 'v', [CompletionResultType]::ParameterName, 'Enable verbose information and logging')
[CompletionResult]::new('--verbose', 'verbose', [CompletionResultType]::ParameterName, 'Enable verbose information and logging')
[CompletionResult]::new('-i', 'i', [CompletionResultType]::ParameterName, 'Don''t update local history for actions')
[CompletionResult]::new('--incognito', 'incognito', [CompletionResultType]::ParameterName, 'Don''t update local history for actions')
break
}
'ffsend;upload' {
[CompletionResult]::new('-p', 'p', [CompletionResultType]::ParameterName, 'Protect the file with a password')
[CompletionResult]::new('--password', 'password', [CompletionResultType]::ParameterName, 'Protect the file with a password')
[CompletionResult]::new('-d', 'd', [CompletionResultType]::ParameterName, 'The file download limit')
[CompletionResult]::new('--download-limit', 'download-limit', [CompletionResultType]::ParameterName, 'The file download limit')
[CompletionResult]::new('-e', 'e', [CompletionResultType]::ParameterName, 'The file expiry time')
[CompletionResult]::new('--expiry-time', 'expiry-time', [CompletionResultType]::ParameterName, 'The file expiry time')
[CompletionResult]::new('-h', 'h', [CompletionResultType]::ParameterName, 'The remote host to upload to')
[CompletionResult]::new('--host', 'host', [CompletionResultType]::ParameterName, 'The remote host to upload to')
[CompletionResult]::new('-n', 'n', [CompletionResultType]::ParameterName, 'Rename the file being uploaded')
[CompletionResult]::new('--name', 'name', [CompletionResultType]::ParameterName, 'Rename the file being uploaded')
[CompletionResult]::new('-t', 't', [CompletionResultType]::ParameterName, 'Request timeout (0 to disable)')
[CompletionResult]::new('--timeout', 'timeout', [CompletionResultType]::ParameterName, 'Request timeout (0 to disable)')
[CompletionResult]::new('-T', 'T', [CompletionResultType]::ParameterName, 'Transfer timeout (0 to disable)')
[CompletionResult]::new('--transfer-timeout', 'transfer-timeout', [CompletionResultType]::ParameterName, 'Transfer timeout (0 to disable)')
[CompletionResult]::new('-A', 'A', [CompletionResultType]::ParameterName, 'Server API version to use, ''-'' to lookup')
[CompletionResult]::new('--api', 'api', [CompletionResultType]::ParameterName, 'Server API version to use, ''-'' to lookup')
[CompletionResult]::new('--basic-auth', 'basic-auth', [CompletionResultType]::ParameterName, 'Protected proxy HTTP basic authentication credentials (not FxA)')
[CompletionResult]::new('-H', 'H', [CompletionResultType]::ParameterName, 'Use the specified history file')
[CompletionResult]::new('--history', 'history', [CompletionResultType]::ParameterName, 'Use the specified history file')
[CompletionResult]::new('-P', 'P', [CompletionResultType]::ParameterName, 'Protect the file with a generated passphrase')
[CompletionResult]::new('--gen-passphrase', 'gen-passphrase', [CompletionResultType]::ParameterName, 'Protect the file with a generated passphrase')
[CompletionResult]::new('-o', 'o', [CompletionResultType]::ParameterName, 'Open the share link in your browser')
[CompletionResult]::new('--open', 'open', [CompletionResultType]::ParameterName, 'Open the share link in your browser')
[CompletionResult]::new('-D', 'D', [CompletionResultType]::ParameterName, 'Delete local file after upload')
[CompletionResult]::new('--delete', 'delete', [CompletionResultType]::ParameterName, 'Delete local file after upload')
[CompletionResult]::new('-a', 'a', [CompletionResultType]::ParameterName, 'Archive the upload in a single file')
[CompletionResult]::new('--archive', 'archive', [CompletionResultType]::ParameterName, 'Archive the upload in a single file')
[CompletionResult]::new('-c', 'c', [CompletionResultType]::ParameterName, 'Copy the share link to your clipboard')
[CompletionResult]::new('--copy', 'copy', [CompletionResultType]::ParameterName, 'Copy the share link to your clipboard')
[CompletionResult]::new('-C', 'C', [CompletionResultType]::ParameterName, 'Copy the ffsend download command to your clipboard')
[CompletionResult]::new('--copy-cmd', 'copy-cmd', [CompletionResultType]::ParameterName, 'Copy the ffsend download command to your clipboard')
[CompletionResult]::new('-S', 'S', [CompletionResultType]::ParameterName, 'Shorten share URLs with a public service')
[CompletionResult]::new('--shorten', 'shorten', [CompletionResultType]::ParameterName, 'Shorten share URLs with a public service')
[CompletionResult]::new('-Q', 'Q', [CompletionResultType]::ParameterName, 'Print a QR code for the share URL')
[CompletionResult]::new('--qrcode', 'qrcode', [CompletionResultType]::ParameterName, 'Print a QR code for the share URL')
[CompletionResult]::new('--help', 'help', [CompletionResultType]::ParameterName, 'Prints help information')
[CompletionResult]::new('-V', 'V', [CompletionResultType]::ParameterName, 'Prints version information')
[CompletionResult]::new('--version', 'version', [CompletionResultType]::ParameterName, 'Prints version information')
[CompletionResult]::new('-f', 'f', [CompletionResultType]::ParameterName, 'Force the action, ignore warnings')
[CompletionResult]::new('--force', 'force', [CompletionResultType]::ParameterName, 'Force the action, ignore warnings')
[CompletionResult]::new('-I', 'I', [CompletionResultType]::ParameterName, 'Not interactive, do not prompt')
[CompletionResult]::new('--no-interact', 'no-interact', [CompletionResultType]::ParameterName, 'Not interactive, do not prompt')
[CompletionResult]::new('-y', 'y', [CompletionResultType]::ParameterName, 'Assume yes for prompts')
[CompletionResult]::new('--yes', 'yes', [CompletionResultType]::ParameterName, 'Assume yes for prompts')
[CompletionResult]::new('-q', 'q', [CompletionResultType]::ParameterName, 'Produce output suitable for logging and automation')
[CompletionResult]::new('--quiet', 'quiet', [CompletionResultType]::ParameterName, 'Produce output suitable for logging and automation')
[CompletionResult]::new('-v', 'v', [CompletionResultType]::ParameterName, 'Enable verbose information and logging')
[CompletionResult]::new('--verbose', 'verbose', [CompletionResultType]::ParameterName, 'Enable verbose information and logging')
[CompletionResult]::new('-i', 'i', [CompletionResultType]::ParameterName, 'Don''t update local history for actions')
[CompletionResult]::new('--incognito', 'incognito', [CompletionResultType]::ParameterName, 'Don''t update local history for actions')
break
}
'ffsend;version' {
[CompletionResult]::new('-h', 'h', [CompletionResultType]::ParameterName, 'The remote host to upload to')
[CompletionResult]::new('--host', 'host', [CompletionResultType]::ParameterName, 'The remote host to upload to')
[CompletionResult]::new('-t', 't', [CompletionResultType]::ParameterName, 'Request timeout (0 to disable)')
[CompletionResult]::new('--timeout', 'timeout', [CompletionResultType]::ParameterName, 'Request timeout (0 to disable)')
[CompletionResult]::new('-T', 'T', [CompletionResultType]::ParameterName, 'Transfer timeout (0 to disable)')
[CompletionResult]::new('--transfer-timeout', 'transfer-timeout', [CompletionResultType]::ParameterName, 'Transfer timeout (0 to disable)')
[CompletionResult]::new('-A', 'A', [CompletionResultType]::ParameterName, 'Server API version to use, ''-'' to lookup')
[CompletionResult]::new('--api', 'api', [CompletionResultType]::ParameterName, 'Server API version to use, ''-'' to lookup')
[CompletionResult]::new('--basic-auth', 'basic-auth', [CompletionResultType]::ParameterName, 'Protected proxy HTTP basic authentication credentials (not FxA)')
[CompletionResult]::new('-H', 'H', [CompletionResultType]::ParameterName, 'Use the specified history file')
[CompletionResult]::new('--history', 'history', [CompletionResultType]::ParameterName, 'Use the specified history file')
[CompletionResult]::new('--help', 'help', [CompletionResultType]::ParameterName, 'Prints help information')
[CompletionResult]::new('-V', 'V', [CompletionResultType]::ParameterName, 'Prints version information')
[CompletionResult]::new('--version', 'version', [CompletionResultType]::ParameterName, 'Prints version information')
[CompletionResult]::new('-f', 'f', [CompletionResultType]::ParameterName, 'Force the action, ignore warnings')
[CompletionResult]::new('--force', 'force', [CompletionResultType]::ParameterName, 'Force the action, ignore warnings')
[CompletionResult]::new('-I', 'I', [CompletionResultType]::ParameterName, 'Not interactive, do not prompt')
[CompletionResult]::new('--no-interact', 'no-interact', [CompletionResultType]::ParameterName, 'Not interactive, do not prompt')
[CompletionResult]::new('-y', 'y', [CompletionResultType]::ParameterName, 'Assume yes for prompts')
[CompletionResult]::new('--yes', 'yes', [CompletionResultType]::ParameterName, 'Assume yes for prompts')
[CompletionResult]::new('-q', 'q', [CompletionResultType]::ParameterName, 'Produce output suitable for logging and automation')
[CompletionResult]::new('--quiet', 'quiet', [CompletionResultType]::ParameterName, 'Produce output suitable for logging and automation')
[CompletionResult]::new('-v', 'v', [CompletionResultType]::ParameterName, 'Enable verbose information and logging')
[CompletionResult]::new('--verbose', 'verbose', [CompletionResultType]::ParameterName, 'Enable verbose information and logging')
[CompletionResult]::new('-i', 'i', [CompletionResultType]::ParameterName, 'Don''t update local history for actions')
[CompletionResult]::new('--incognito', 'incognito', [CompletionResultType]::ParameterName, 'Don''t update local history for actions')
break
}
'ffsend;history' {
[CompletionResult]::new('-R', 'R', [CompletionResultType]::ParameterName, 'Remove history entry')
[CompletionResult]::new('--rm', 'rm', [CompletionResultType]::ParameterName, 'Remove history entry')
[CompletionResult]::new('-t', 't', [CompletionResultType]::ParameterName, 'Request timeout (0 to disable)')
[CompletionResult]::new('--timeout', 'timeout', [CompletionResultType]::ParameterName, 'Request timeout (0 to disable)')
[CompletionResult]::new('-T', 'T', [CompletionResultType]::ParameterName, 'Transfer timeout (0 to disable)')
[CompletionResult]::new('--transfer-timeout', 'transfer-timeout', [CompletionResultType]::ParameterName, 'Transfer timeout (0 to disable)')
[CompletionResult]::new('-A', 'A', [CompletionResultType]::ParameterName, 'Server API version to use, ''-'' to lookup')
[CompletionResult]::new('--api', 'api', [CompletionResultType]::ParameterName, 'Server API version to use, ''-'' to lookup')
[CompletionResult]::new('--basic-auth', 'basic-auth', [CompletionResultType]::ParameterName, 'Protected proxy HTTP basic authentication credentials (not FxA)')
[CompletionResult]::new('-H', 'H', [CompletionResultType]::ParameterName, 'Use the specified history file')
[CompletionResult]::new('--history', 'history', [CompletionResultType]::ParameterName, 'Use the specified history file')
[CompletionResult]::new('-C', 'C', [CompletionResultType]::ParameterName, 'Clear all history')
[CompletionResult]::new('--clear', 'clear', [CompletionResultType]::ParameterName, 'Clear all history')
[CompletionResult]::new('-h', 'h', [CompletionResultType]::ParameterName, 'Prints help information')
[CompletionResult]::new('--help', 'help', [CompletionResultType]::ParameterName, 'Prints help information')
[CompletionResult]::new('-V', 'V', [CompletionResultType]::ParameterName, 'Prints version information')
[CompletionResult]::new('--version', 'version', [CompletionResultType]::ParameterName, 'Prints version information')
[CompletionResult]::new('-f', 'f', [CompletionResultType]::ParameterName, 'Force the action, ignore warnings')
[CompletionResult]::new('--force', 'force', [CompletionResultType]::ParameterName, 'Force the action, ignore warnings')
[CompletionResult]::new('-I', 'I', [CompletionResultType]::ParameterName, 'Not interactive, do not prompt')
[CompletionResult]::new('--no-interact', 'no-interact', [CompletionResultType]::ParameterName, 'Not interactive, do not prompt')
[CompletionResult]::new('-y', 'y', [CompletionResultType]::ParameterName, 'Assume yes for prompts')
[CompletionResult]::new('--yes', 'yes', [CompletionResultType]::ParameterName, 'Assume yes for prompts')
[CompletionResult]::new('-q', 'q', [CompletionResultType]::ParameterName, 'Produce output suitable for logging and automation')
[CompletionResult]::new('--quiet', 'quiet', [CompletionResultType]::ParameterName, 'Produce output suitable for logging and automation')
[CompletionResult]::new('-v', 'v', [CompletionResultType]::ParameterName, 'Enable verbose information and logging')
[CompletionResult]::new('--verbose', 'verbose', [CompletionResultType]::ParameterName, 'Enable verbose information and logging')
[CompletionResult]::new('-i', 'i', [CompletionResultType]::ParameterName, 'Don''t update local history for actions')
[CompletionResult]::new('--incognito', 'incognito', [CompletionResultType]::ParameterName, 'Don''t update local history for actions')
break
}
'ffsend;help' {
[CompletionResult]::new('-t', 't', [CompletionResultType]::ParameterName, 'Request timeout (0 to disable)')
[CompletionResult]::new('--timeout', 'timeout', [CompletionResultType]::ParameterName, 'Request timeout (0 to disable)')
[CompletionResult]::new('-T', 'T', [CompletionResultType]::ParameterName, 'Transfer timeout (0 to disable)')
[CompletionResult]::new('--transfer-timeout', 'transfer-timeout', [CompletionResultType]::ParameterName, 'Transfer timeout (0 to disable)')
[CompletionResult]::new('-A', 'A', [CompletionResultType]::ParameterName, 'Server API version to use, ''-'' to lookup')
[CompletionResult]::new('--api', 'api', [CompletionResultType]::ParameterName, 'Server API version to use, ''-'' to lookup')
[CompletionResult]::new('--basic-auth', 'basic-auth', [CompletionResultType]::ParameterName, 'Protected proxy HTTP basic authentication credentials (not FxA)')
[CompletionResult]::new('-H', 'H', [CompletionResultType]::ParameterName, 'Use the specified history file')
[CompletionResult]::new('--history', 'history', [CompletionResultType]::ParameterName, 'Use the specified history file')
[CompletionResult]::new('-h', 'h', [CompletionResultType]::ParameterName, 'Prints help information')
[CompletionResult]::new('--help', 'help', [CompletionResultType]::ParameterName, 'Prints help information')
[CompletionResult]::new('-V', 'V', [CompletionResultType]::ParameterName, 'Prints version information')
[CompletionResult]::new('--version', 'version', [CompletionResultType]::ParameterName, 'Prints version information')
[CompletionResult]::new('-f', 'f', [CompletionResultType]::ParameterName, 'Force the action, ignore warnings')
[CompletionResult]::new('--force', 'force', [CompletionResultType]::ParameterName, 'Force the action, ignore warnings')
[CompletionResult]::new('-I', 'I', [CompletionResultType]::ParameterName, 'Not interactive, do not prompt')
[CompletionResult]::new('--no-interact', 'no-interact', [CompletionResultType]::ParameterName, 'Not interactive, do not prompt')
[CompletionResult]::new('-y', 'y', [CompletionResultType]::ParameterName, 'Assume yes for prompts')
[CompletionResult]::new('--yes', 'yes', [CompletionResultType]::ParameterName, 'Assume yes for prompts')
[CompletionResult]::new('-q', 'q', [CompletionResultType]::ParameterName, 'Produce output suitable for logging and automation')
[CompletionResult]::new('--quiet', 'quiet', [CompletionResultType]::ParameterName, 'Produce output suitable for logging and automation')
[CompletionResult]::new('-v', 'v', [CompletionResultType]::ParameterName, 'Enable verbose information and logging')
[CompletionResult]::new('--verbose', 'verbose', [CompletionResultType]::ParameterName, 'Enable verbose information and logging')
[CompletionResult]::new('-i', 'i', [CompletionResultType]::ParameterName, 'Don''t update local history for actions')
[CompletionResult]::new('--incognito', 'incognito', [CompletionResultType]::ParameterName, 'Don''t update local history for actions')
break
}
})
$completions.Where{ $_.CompletionText -like "$wordToComplete*" } |
Sort-Object -Property ListItemText
}

File diff suppressed because it is too large Load diff

View file

@ -1,493 +0,0 @@
edit:completion:arg-completer[ffsend] = [@words]{
fn spaces [n]{
repeat $n ' ' | joins ''
}
fn cand [text desc]{
edit:complex-candidate $text &display-suffix=' '(spaces (- 14 (wcswidth $text)))$desc
}
command = 'ffsend'
for word $words[1:-1] {
if (has-prefix $word '-') {
break
}
command = $command';'$word
}
completions = [
&'ffsend'= {
cand -t 'Request timeout (0 to disable)'
cand --timeout 'Request timeout (0 to disable)'
cand -T 'Transfer timeout (0 to disable)'
cand --transfer-timeout 'Transfer timeout (0 to disable)'
cand -A 'Server API version to use, ''-'' to lookup'
cand --api 'Server API version to use, ''-'' to lookup'
cand --basic-auth 'Protected proxy HTTP basic authentication credentials (not FxA)'
cand -H 'Use the specified history file'
cand --history 'Use the specified history file'
cand -f 'Force the action, ignore warnings'
cand --force 'Force the action, ignore warnings'
cand -I 'Not interactive, do not prompt'
cand --no-interact 'Not interactive, do not prompt'
cand -y 'Assume yes for prompts'
cand --yes 'Assume yes for prompts'
cand -q 'Produce output suitable for logging and automation'
cand --quiet 'Produce output suitable for logging and automation'
cand -v 'Enable verbose information and logging'
cand --verbose 'Enable verbose information and logging'
cand -i 'Don''t update local history for actions'
cand --incognito 'Don''t update local history for actions'
cand -h 'Prints help information'
cand --help 'Prints help information'
cand -V 'Prints version information'
cand --version 'Prints version information'
cand debug 'View debug information'
cand delete 'Delete a shared file'
cand download 'Download files'
cand exists 'Check whether a remote file exists'
cand generate 'Generate assets'
cand info 'Fetch info about a shared file'
cand parameters 'Change parameters of a shared file'
cand password 'Change the password of a shared file'
cand upload 'Upload files'
cand version 'Determine the Send server version'
cand history 'View file history'
cand help 'Prints this message or the help of the given subcommand(s)'
}
&'ffsend;debug'= {
cand -h 'The remote host to upload to'
cand --host 'The remote host to upload to'
cand -t 'Request timeout (0 to disable)'
cand --timeout 'Request timeout (0 to disable)'
cand -T 'Transfer timeout (0 to disable)'
cand --transfer-timeout 'Transfer timeout (0 to disable)'
cand -A 'Server API version to use, ''-'' to lookup'
cand --api 'Server API version to use, ''-'' to lookup'
cand --basic-auth 'Protected proxy HTTP basic authentication credentials (not FxA)'
cand -H 'Use the specified history file'
cand --history 'Use the specified history file'
cand --help 'Prints help information'
cand -V 'Prints version information'
cand --version 'Prints version information'
cand -f 'Force the action, ignore warnings'
cand --force 'Force the action, ignore warnings'
cand -I 'Not interactive, do not prompt'
cand --no-interact 'Not interactive, do not prompt'
cand -y 'Assume yes for prompts'
cand --yes 'Assume yes for prompts'
cand -q 'Produce output suitable for logging and automation'
cand --quiet 'Produce output suitable for logging and automation'
cand -v 'Enable verbose information and logging'
cand --verbose 'Enable verbose information and logging'
cand -i 'Don''t update local history for actions'
cand --incognito 'Don''t update local history for actions'
}
&'ffsend;delete'= {
cand -o 'Specify the file owner token'
cand --owner 'Specify the file owner token'
cand -t 'Request timeout (0 to disable)'
cand --timeout 'Request timeout (0 to disable)'
cand -T 'Transfer timeout (0 to disable)'
cand --transfer-timeout 'Transfer timeout (0 to disable)'
cand -A 'Server API version to use, ''-'' to lookup'
cand --api 'Server API version to use, ''-'' to lookup'
cand --basic-auth 'Protected proxy HTTP basic authentication credentials (not FxA)'
cand -H 'Use the specified history file'
cand --history 'Use the specified history file'
cand -h 'Prints help information'
cand --help 'Prints help information'
cand -V 'Prints version information'
cand --version 'Prints version information'
cand -f 'Force the action, ignore warnings'
cand --force 'Force the action, ignore warnings'
cand -I 'Not interactive, do not prompt'
cand --no-interact 'Not interactive, do not prompt'
cand -y 'Assume yes for prompts'
cand --yes 'Assume yes for prompts'
cand -q 'Produce output suitable for logging and automation'
cand --quiet 'Produce output suitable for logging and automation'
cand -v 'Enable verbose information and logging'
cand --verbose 'Enable verbose information and logging'
cand -i 'Don''t update local history for actions'
cand --incognito 'Don''t update local history for actions'
}
&'ffsend;download'= {
cand -p 'Unlock a password protected file'
cand --password 'Unlock a password protected file'
cand -o 'Output file or directory'
cand --output 'Output file or directory'
cand -t 'Request timeout (0 to disable)'
cand --timeout 'Request timeout (0 to disable)'
cand -T 'Transfer timeout (0 to disable)'
cand --transfer-timeout 'Transfer timeout (0 to disable)'
cand -A 'Server API version to use, ''-'' to lookup'
cand --api 'Server API version to use, ''-'' to lookup'
cand --basic-auth 'Protected proxy HTTP basic authentication credentials (not FxA)'
cand -H 'Use the specified history file'
cand --history 'Use the specified history file'
cand -e 'Extract an archived file'
cand --extract 'Extract an archived file'
cand -h 'Prints help information'
cand --help 'Prints help information'
cand -V 'Prints version information'
cand --version 'Prints version information'
cand -f 'Force the action, ignore warnings'
cand --force 'Force the action, ignore warnings'
cand -I 'Not interactive, do not prompt'
cand --no-interact 'Not interactive, do not prompt'
cand -y 'Assume yes for prompts'
cand --yes 'Assume yes for prompts'
cand -q 'Produce output suitable for logging and automation'
cand --quiet 'Produce output suitable for logging and automation'
cand -v 'Enable verbose information and logging'
cand --verbose 'Enable verbose information and logging'
cand -i 'Don''t update local history for actions'
cand --incognito 'Don''t update local history for actions'
}
&'ffsend;exists'= {
cand -t 'Request timeout (0 to disable)'
cand --timeout 'Request timeout (0 to disable)'
cand -T 'Transfer timeout (0 to disable)'
cand --transfer-timeout 'Transfer timeout (0 to disable)'
cand -A 'Server API version to use, ''-'' to lookup'
cand --api 'Server API version to use, ''-'' to lookup'
cand --basic-auth 'Protected proxy HTTP basic authentication credentials (not FxA)'
cand -H 'Use the specified history file'
cand --history 'Use the specified history file'
cand -h 'Prints help information'
cand --help 'Prints help information'
cand -V 'Prints version information'
cand --version 'Prints version information'
cand -f 'Force the action, ignore warnings'
cand --force 'Force the action, ignore warnings'
cand -I 'Not interactive, do not prompt'
cand --no-interact 'Not interactive, do not prompt'
cand -y 'Assume yes for prompts'
cand --yes 'Assume yes for prompts'
cand -q 'Produce output suitable for logging and automation'
cand --quiet 'Produce output suitable for logging and automation'
cand -v 'Enable verbose information and logging'
cand --verbose 'Enable verbose information and logging'
cand -i 'Don''t update local history for actions'
cand --incognito 'Don''t update local history for actions'
}
&'ffsend;generate'= {
cand -t 'Request timeout (0 to disable)'
cand --timeout 'Request timeout (0 to disable)'
cand -T 'Transfer timeout (0 to disable)'
cand --transfer-timeout 'Transfer timeout (0 to disable)'
cand -A 'Server API version to use, ''-'' to lookup'
cand --api 'Server API version to use, ''-'' to lookup'
cand --basic-auth 'Protected proxy HTTP basic authentication credentials (not FxA)'
cand -H 'Use the specified history file'
cand --history 'Use the specified history file'
cand -h 'Prints help information'
cand --help 'Prints help information'
cand -V 'Prints version information'
cand --version 'Prints version information'
cand -f 'Force the action, ignore warnings'
cand --force 'Force the action, ignore warnings'
cand -I 'Not interactive, do not prompt'
cand --no-interact 'Not interactive, do not prompt'
cand -y 'Assume yes for prompts'
cand --yes 'Assume yes for prompts'
cand -q 'Produce output suitable for logging and automation'
cand --quiet 'Produce output suitable for logging and automation'
cand -v 'Enable verbose information and logging'
cand --verbose 'Enable verbose information and logging'
cand -i 'Don''t update local history for actions'
cand --incognito 'Don''t update local history for actions'
cand completions 'Shell completions'
cand help 'Prints this message or the help of the given subcommand(s)'
}
&'ffsend;generate;completions'= {
cand -o 'Shell completion files output directory'
cand --output 'Shell completion files output directory'
cand -t 'Request timeout (0 to disable)'
cand --timeout 'Request timeout (0 to disable)'
cand -T 'Transfer timeout (0 to disable)'
cand --transfer-timeout 'Transfer timeout (0 to disable)'
cand -A 'Server API version to use, ''-'' to lookup'
cand --api 'Server API version to use, ''-'' to lookup'
cand --basic-auth 'Protected proxy HTTP basic authentication credentials (not FxA)'
cand -H 'Use the specified history file'
cand --history 'Use the specified history file'
cand -h 'Prints help information'
cand --help 'Prints help information'
cand -V 'Prints version information'
cand --version 'Prints version information'
cand -f 'Force the action, ignore warnings'
cand --force 'Force the action, ignore warnings'
cand -I 'Not interactive, do not prompt'
cand --no-interact 'Not interactive, do not prompt'
cand -y 'Assume yes for prompts'
cand --yes 'Assume yes for prompts'
cand -q 'Produce output suitable for logging and automation'
cand --quiet 'Produce output suitable for logging and automation'
cand -v 'Enable verbose information and logging'
cand --verbose 'Enable verbose information and logging'
cand -i 'Don''t update local history for actions'
cand --incognito 'Don''t update local history for actions'
}
&'ffsend;generate;help'= {
cand -t 'Request timeout (0 to disable)'
cand --timeout 'Request timeout (0 to disable)'
cand -T 'Transfer timeout (0 to disable)'
cand --transfer-timeout 'Transfer timeout (0 to disable)'
cand -A 'Server API version to use, ''-'' to lookup'
cand --api 'Server API version to use, ''-'' to lookup'
cand --basic-auth 'Protected proxy HTTP basic authentication credentials (not FxA)'
cand -H 'Use the specified history file'
cand --history 'Use the specified history file'
cand -h 'Prints help information'
cand --help 'Prints help information'
cand -V 'Prints version information'
cand --version 'Prints version information'
cand -f 'Force the action, ignore warnings'
cand --force 'Force the action, ignore warnings'
cand -I 'Not interactive, do not prompt'
cand --no-interact 'Not interactive, do not prompt'
cand -y 'Assume yes for prompts'
cand --yes 'Assume yes for prompts'
cand -q 'Produce output suitable for logging and automation'
cand --quiet 'Produce output suitable for logging and automation'
cand -v 'Enable verbose information and logging'
cand --verbose 'Enable verbose information and logging'
cand -i 'Don''t update local history for actions'
cand --incognito 'Don''t update local history for actions'
}
&'ffsend;info'= {
cand -o 'Specify the file owner token'
cand --owner 'Specify the file owner token'
cand -p 'Unlock a password protected file'
cand --password 'Unlock a password protected file'
cand -t 'Request timeout (0 to disable)'
cand --timeout 'Request timeout (0 to disable)'
cand -T 'Transfer timeout (0 to disable)'
cand --transfer-timeout 'Transfer timeout (0 to disable)'
cand -A 'Server API version to use, ''-'' to lookup'
cand --api 'Server API version to use, ''-'' to lookup'
cand --basic-auth 'Protected proxy HTTP basic authentication credentials (not FxA)'
cand -H 'Use the specified history file'
cand --history 'Use the specified history file'
cand -h 'Prints help information'
cand --help 'Prints help information'
cand -V 'Prints version information'
cand --version 'Prints version information'
cand -f 'Force the action, ignore warnings'
cand --force 'Force the action, ignore warnings'
cand -I 'Not interactive, do not prompt'
cand --no-interact 'Not interactive, do not prompt'
cand -y 'Assume yes for prompts'
cand --yes 'Assume yes for prompts'
cand -q 'Produce output suitable for logging and automation'
cand --quiet 'Produce output suitable for logging and automation'
cand -v 'Enable verbose information and logging'
cand --verbose 'Enable verbose information and logging'
cand -i 'Don''t update local history for actions'
cand --incognito 'Don''t update local history for actions'
}
&'ffsend;parameters'= {
cand -o 'Specify the file owner token'
cand --owner 'Specify the file owner token'
cand -d 'The file download limit'
cand --download-limit 'The file download limit'
cand -t 'Request timeout (0 to disable)'
cand --timeout 'Request timeout (0 to disable)'
cand -T 'Transfer timeout (0 to disable)'
cand --transfer-timeout 'Transfer timeout (0 to disable)'
cand -A 'Server API version to use, ''-'' to lookup'
cand --api 'Server API version to use, ''-'' to lookup'
cand --basic-auth 'Protected proxy HTTP basic authentication credentials (not FxA)'
cand -H 'Use the specified history file'
cand --history 'Use the specified history file'
cand -h 'Prints help information'
cand --help 'Prints help information'
cand -V 'Prints version information'
cand --version 'Prints version information'
cand -f 'Force the action, ignore warnings'
cand --force 'Force the action, ignore warnings'
cand -I 'Not interactive, do not prompt'
cand --no-interact 'Not interactive, do not prompt'
cand -y 'Assume yes for prompts'
cand --yes 'Assume yes for prompts'
cand -q 'Produce output suitable for logging and automation'
cand --quiet 'Produce output suitable for logging and automation'
cand -v 'Enable verbose information and logging'
cand --verbose 'Enable verbose information and logging'
cand -i 'Don''t update local history for actions'
cand --incognito 'Don''t update local history for actions'
}
&'ffsend;password'= {
cand -p 'Specify a password, do not prompt'
cand --password 'Specify a password, do not prompt'
cand -o 'Specify the file owner token'
cand --owner 'Specify the file owner token'
cand -t 'Request timeout (0 to disable)'
cand --timeout 'Request timeout (0 to disable)'
cand -T 'Transfer timeout (0 to disable)'
cand --transfer-timeout 'Transfer timeout (0 to disable)'
cand -A 'Server API version to use, ''-'' to lookup'
cand --api 'Server API version to use, ''-'' to lookup'
cand --basic-auth 'Protected proxy HTTP basic authentication credentials (not FxA)'
cand -H 'Use the specified history file'
cand --history 'Use the specified history file'
cand -P 'Protect the file with a generated passphrase'
cand --gen-passphrase 'Protect the file with a generated passphrase'
cand -h 'Prints help information'
cand --help 'Prints help information'
cand -V 'Prints version information'
cand --version 'Prints version information'
cand -f 'Force the action, ignore warnings'
cand --force 'Force the action, ignore warnings'
cand -I 'Not interactive, do not prompt'
cand --no-interact 'Not interactive, do not prompt'
cand -y 'Assume yes for prompts'
cand --yes 'Assume yes for prompts'
cand -q 'Produce output suitable for logging and automation'
cand --quiet 'Produce output suitable for logging and automation'
cand -v 'Enable verbose information and logging'
cand --verbose 'Enable verbose information and logging'
cand -i 'Don''t update local history for actions'
cand --incognito 'Don''t update local history for actions'
}
&'ffsend;upload'= {
cand -p 'Protect the file with a password'
cand --password 'Protect the file with a password'
cand -d 'The file download limit'
cand --download-limit 'The file download limit'
cand -e 'The file expiry time'
cand --expiry-time 'The file expiry time'
cand -h 'The remote host to upload to'
cand --host 'The remote host to upload to'
cand -n 'Rename the file being uploaded'
cand --name 'Rename the file being uploaded'
cand -t 'Request timeout (0 to disable)'
cand --timeout 'Request timeout (0 to disable)'
cand -T 'Transfer timeout (0 to disable)'
cand --transfer-timeout 'Transfer timeout (0 to disable)'
cand -A 'Server API version to use, ''-'' to lookup'
cand --api 'Server API version to use, ''-'' to lookup'
cand --basic-auth 'Protected proxy HTTP basic authentication credentials (not FxA)'
cand -H 'Use the specified history file'
cand --history 'Use the specified history file'
cand -P 'Protect the file with a generated passphrase'
cand --gen-passphrase 'Protect the file with a generated passphrase'
cand -o 'Open the share link in your browser'
cand --open 'Open the share link in your browser'
cand -D 'Delete local file after upload'
cand --delete 'Delete local file after upload'
cand -a 'Archive the upload in a single file'
cand --archive 'Archive the upload in a single file'
cand -c 'Copy the share link to your clipboard'
cand --copy 'Copy the share link to your clipboard'
cand -C 'Copy the ffsend download command to your clipboard'
cand --copy-cmd 'Copy the ffsend download command to your clipboard'
cand -S 'Shorten share URLs with a public service'
cand --shorten 'Shorten share URLs with a public service'
cand -Q 'Print a QR code for the share URL'
cand --qrcode 'Print a QR code for the share URL'
cand --help 'Prints help information'
cand -V 'Prints version information'
cand --version 'Prints version information'
cand -f 'Force the action, ignore warnings'
cand --force 'Force the action, ignore warnings'
cand -I 'Not interactive, do not prompt'
cand --no-interact 'Not interactive, do not prompt'
cand -y 'Assume yes for prompts'
cand --yes 'Assume yes for prompts'
cand -q 'Produce output suitable for logging and automation'
cand --quiet 'Produce output suitable for logging and automation'
cand -v 'Enable verbose information and logging'
cand --verbose 'Enable verbose information and logging'
cand -i 'Don''t update local history for actions'
cand --incognito 'Don''t update local history for actions'
}
&'ffsend;version'= {
cand -h 'The remote host to upload to'
cand --host 'The remote host to upload to'
cand -t 'Request timeout (0 to disable)'
cand --timeout 'Request timeout (0 to disable)'
cand -T 'Transfer timeout (0 to disable)'
cand --transfer-timeout 'Transfer timeout (0 to disable)'
cand -A 'Server API version to use, ''-'' to lookup'
cand --api 'Server API version to use, ''-'' to lookup'
cand --basic-auth 'Protected proxy HTTP basic authentication credentials (not FxA)'
cand -H 'Use the specified history file'
cand --history 'Use the specified history file'
cand --help 'Prints help information'
cand -V 'Prints version information'
cand --version 'Prints version information'
cand -f 'Force the action, ignore warnings'
cand --force 'Force the action, ignore warnings'
cand -I 'Not interactive, do not prompt'
cand --no-interact 'Not interactive, do not prompt'
cand -y 'Assume yes for prompts'
cand --yes 'Assume yes for prompts'
cand -q 'Produce output suitable for logging and automation'
cand --quiet 'Produce output suitable for logging and automation'
cand -v 'Enable verbose information and logging'
cand --verbose 'Enable verbose information and logging'
cand -i 'Don''t update local history for actions'
cand --incognito 'Don''t update local history for actions'
}
&'ffsend;history'= {
cand -R 'Remove history entry'
cand --rm 'Remove history entry'
cand -t 'Request timeout (0 to disable)'
cand --timeout 'Request timeout (0 to disable)'
cand -T 'Transfer timeout (0 to disable)'
cand --transfer-timeout 'Transfer timeout (0 to disable)'
cand -A 'Server API version to use, ''-'' to lookup'
cand --api 'Server API version to use, ''-'' to lookup'
cand --basic-auth 'Protected proxy HTTP basic authentication credentials (not FxA)'
cand -H 'Use the specified history file'
cand --history 'Use the specified history file'
cand -C 'Clear all history'
cand --clear 'Clear all history'
cand -h 'Prints help information'
cand --help 'Prints help information'
cand -V 'Prints version information'
cand --version 'Prints version information'
cand -f 'Force the action, ignore warnings'
cand --force 'Force the action, ignore warnings'
cand -I 'Not interactive, do not prompt'
cand --no-interact 'Not interactive, do not prompt'
cand -y 'Assume yes for prompts'
cand --yes 'Assume yes for prompts'
cand -q 'Produce output suitable for logging and automation'
cand --quiet 'Produce output suitable for logging and automation'
cand -v 'Enable verbose information and logging'
cand --verbose 'Enable verbose information and logging'
cand -i 'Don''t update local history for actions'
cand --incognito 'Don''t update local history for actions'
}
&'ffsend;help'= {
cand -t 'Request timeout (0 to disable)'
cand --timeout 'Request timeout (0 to disable)'
cand -T 'Transfer timeout (0 to disable)'
cand --transfer-timeout 'Transfer timeout (0 to disable)'
cand -A 'Server API version to use, ''-'' to lookup'
cand --api 'Server API version to use, ''-'' to lookup'
cand --basic-auth 'Protected proxy HTTP basic authentication credentials (not FxA)'
cand -H 'Use the specified history file'
cand --history 'Use the specified history file'
cand -h 'Prints help information'
cand --help 'Prints help information'
cand -V 'Prints version information'
cand --version 'Prints version information'
cand -f 'Force the action, ignore warnings'
cand --force 'Force the action, ignore warnings'
cand -I 'Not interactive, do not prompt'
cand --no-interact 'Not interactive, do not prompt'
cand -y 'Assume yes for prompts'
cand --yes 'Assume yes for prompts'
cand -q 'Produce output suitable for logging and automation'
cand --quiet 'Produce output suitable for logging and automation'
cand -v 'Enable verbose information and logging'
cand --verbose 'Enable verbose information and logging'
cand -i 'Don''t update local history for actions'
cand --incognito 'Don''t update local history for actions'
}
]
$completions[$command]
}

View file

@ -1,238 +0,0 @@
complete -c ffsend -n "__fish_use_subcommand" -s t -l timeout -d 'Request timeout (0 to disable)'
complete -c ffsend -n "__fish_use_subcommand" -s T -l transfer-timeout -d 'Transfer timeout (0 to disable)'
complete -c ffsend -n "__fish_use_subcommand" -s A -l api -d 'Server API version to use, \'-\' to lookup'
complete -c ffsend -n "__fish_use_subcommand" -l basic-auth -d 'Protected proxy HTTP basic authentication credentials (not FxA)'
complete -c ffsend -n "__fish_use_subcommand" -s H -l history -d 'Use the specified history file'
complete -c ffsend -n "__fish_use_subcommand" -s f -l force -d 'Force the action, ignore warnings'
complete -c ffsend -n "__fish_use_subcommand" -s I -l no-interact -d 'Not interactive, do not prompt'
complete -c ffsend -n "__fish_use_subcommand" -s y -l yes -d 'Assume yes for prompts'
complete -c ffsend -n "__fish_use_subcommand" -s q -l quiet -d 'Produce output suitable for logging and automation'
complete -c ffsend -n "__fish_use_subcommand" -s v -l verbose -d 'Enable verbose information and logging'
complete -c ffsend -n "__fish_use_subcommand" -s i -l incognito -d 'Don\'t update local history for actions'
complete -c ffsend -n "__fish_use_subcommand" -s h -l help -d 'Prints help information'
complete -c ffsend -n "__fish_use_subcommand" -s V -l version -d 'Prints version information'
complete -c ffsend -n "__fish_use_subcommand" -f -a "debug" -d 'View debug information'
complete -c ffsend -n "__fish_use_subcommand" -f -a "delete" -d 'Delete a shared file'
complete -c ffsend -n "__fish_use_subcommand" -f -a "download" -d 'Download files'
complete -c ffsend -n "__fish_use_subcommand" -f -a "exists" -d 'Check whether a remote file exists'
complete -c ffsend -n "__fish_use_subcommand" -f -a "generate" -d 'Generate assets'
complete -c ffsend -n "__fish_use_subcommand" -f -a "info" -d 'Fetch info about a shared file'
complete -c ffsend -n "__fish_use_subcommand" -f -a "parameters" -d 'Change parameters of a shared file'
complete -c ffsend -n "__fish_use_subcommand" -f -a "password" -d 'Change the password of a shared file'
complete -c ffsend -n "__fish_use_subcommand" -f -a "upload" -d 'Upload files'
complete -c ffsend -n "__fish_use_subcommand" -f -a "version" -d 'Determine the Send server version'
complete -c ffsend -n "__fish_use_subcommand" -f -a "history" -d 'View file history'
complete -c ffsend -n "__fish_use_subcommand" -f -a "help" -d 'Prints this message or the help of the given subcommand(s)'
complete -c ffsend -n "__fish_seen_subcommand_from debug" -s h -l host -d 'The remote host to upload to'
complete -c ffsend -n "__fish_seen_subcommand_from debug" -s t -l timeout -d 'Request timeout (0 to disable)'
complete -c ffsend -n "__fish_seen_subcommand_from debug" -s T -l transfer-timeout -d 'Transfer timeout (0 to disable)'
complete -c ffsend -n "__fish_seen_subcommand_from debug" -s A -l api -d 'Server API version to use, \'-\' to lookup'
complete -c ffsend -n "__fish_seen_subcommand_from debug" -l basic-auth -d 'Protected proxy HTTP basic authentication credentials (not FxA)'
complete -c ffsend -n "__fish_seen_subcommand_from debug" -s H -l history -d 'Use the specified history file'
complete -c ffsend -n "__fish_seen_subcommand_from debug" -l help -d 'Prints help information'
complete -c ffsend -n "__fish_seen_subcommand_from debug" -s V -l version -d 'Prints version information'
complete -c ffsend -n "__fish_seen_subcommand_from debug" -s f -l force -d 'Force the action, ignore warnings'
complete -c ffsend -n "__fish_seen_subcommand_from debug" -s I -l no-interact -d 'Not interactive, do not prompt'
complete -c ffsend -n "__fish_seen_subcommand_from debug" -s y -l yes -d 'Assume yes for prompts'
complete -c ffsend -n "__fish_seen_subcommand_from debug" -s q -l quiet -d 'Produce output suitable for logging and automation'
complete -c ffsend -n "__fish_seen_subcommand_from debug" -s v -l verbose -d 'Enable verbose information and logging'
complete -c ffsend -n "__fish_seen_subcommand_from debug" -s i -l incognito -d 'Don\'t update local history for actions'
complete -c ffsend -n "__fish_seen_subcommand_from delete" -s o -l owner -d 'Specify the file owner token'
complete -c ffsend -n "__fish_seen_subcommand_from delete" -s t -l timeout -d 'Request timeout (0 to disable)'
complete -c ffsend -n "__fish_seen_subcommand_from delete" -s T -l transfer-timeout -d 'Transfer timeout (0 to disable)'
complete -c ffsend -n "__fish_seen_subcommand_from delete" -s A -l api -d 'Server API version to use, \'-\' to lookup'
complete -c ffsend -n "__fish_seen_subcommand_from delete" -l basic-auth -d 'Protected proxy HTTP basic authentication credentials (not FxA)'
complete -c ffsend -n "__fish_seen_subcommand_from delete" -s H -l history -d 'Use the specified history file'
complete -c ffsend -n "__fish_seen_subcommand_from delete" -s h -l help -d 'Prints help information'
complete -c ffsend -n "__fish_seen_subcommand_from delete" -s V -l version -d 'Prints version information'
complete -c ffsend -n "__fish_seen_subcommand_from delete" -s f -l force -d 'Force the action, ignore warnings'
complete -c ffsend -n "__fish_seen_subcommand_from delete" -s I -l no-interact -d 'Not interactive, do not prompt'
complete -c ffsend -n "__fish_seen_subcommand_from delete" -s y -l yes -d 'Assume yes for prompts'
complete -c ffsend -n "__fish_seen_subcommand_from delete" -s q -l quiet -d 'Produce output suitable for logging and automation'
complete -c ffsend -n "__fish_seen_subcommand_from delete" -s v -l verbose -d 'Enable verbose information and logging'
complete -c ffsend -n "__fish_seen_subcommand_from delete" -s i -l incognito -d 'Don\'t update local history for actions'
complete -c ffsend -n "__fish_seen_subcommand_from download" -s p -l password -d 'Unlock a password protected file'
complete -c ffsend -n "__fish_seen_subcommand_from download" -s o -l output -d 'Output file or directory'
complete -c ffsend -n "__fish_seen_subcommand_from download" -s t -l timeout -d 'Request timeout (0 to disable)'
complete -c ffsend -n "__fish_seen_subcommand_from download" -s T -l transfer-timeout -d 'Transfer timeout (0 to disable)'
complete -c ffsend -n "__fish_seen_subcommand_from download" -s A -l api -d 'Server API version to use, \'-\' to lookup'
complete -c ffsend -n "__fish_seen_subcommand_from download" -l basic-auth -d 'Protected proxy HTTP basic authentication credentials (not FxA)'
complete -c ffsend -n "__fish_seen_subcommand_from download" -s H -l history -d 'Use the specified history file'
complete -c ffsend -n "__fish_seen_subcommand_from download" -s e -l extract -d 'Extract an archived file'
complete -c ffsend -n "__fish_seen_subcommand_from download" -s h -l help -d 'Prints help information'
complete -c ffsend -n "__fish_seen_subcommand_from download" -s V -l version -d 'Prints version information'
complete -c ffsend -n "__fish_seen_subcommand_from download" -s f -l force -d 'Force the action, ignore warnings'
complete -c ffsend -n "__fish_seen_subcommand_from download" -s I -l no-interact -d 'Not interactive, do not prompt'
complete -c ffsend -n "__fish_seen_subcommand_from download" -s y -l yes -d 'Assume yes for prompts'
complete -c ffsend -n "__fish_seen_subcommand_from download" -s q -l quiet -d 'Produce output suitable for logging and automation'
complete -c ffsend -n "__fish_seen_subcommand_from download" -s v -l verbose -d 'Enable verbose information and logging'
complete -c ffsend -n "__fish_seen_subcommand_from download" -s i -l incognito -d 'Don\'t update local history for actions'
complete -c ffsend -n "__fish_seen_subcommand_from exists" -s t -l timeout -d 'Request timeout (0 to disable)'
complete -c ffsend -n "__fish_seen_subcommand_from exists" -s T -l transfer-timeout -d 'Transfer timeout (0 to disable)'
complete -c ffsend -n "__fish_seen_subcommand_from exists" -s A -l api -d 'Server API version to use, \'-\' to lookup'
complete -c ffsend -n "__fish_seen_subcommand_from exists" -l basic-auth -d 'Protected proxy HTTP basic authentication credentials (not FxA)'
complete -c ffsend -n "__fish_seen_subcommand_from exists" -s H -l history -d 'Use the specified history file'
complete -c ffsend -n "__fish_seen_subcommand_from exists" -s h -l help -d 'Prints help information'
complete -c ffsend -n "__fish_seen_subcommand_from exists" -s V -l version -d 'Prints version information'
complete -c ffsend -n "__fish_seen_subcommand_from exists" -s f -l force -d 'Force the action, ignore warnings'
complete -c ffsend -n "__fish_seen_subcommand_from exists" -s I -l no-interact -d 'Not interactive, do not prompt'
complete -c ffsend -n "__fish_seen_subcommand_from exists" -s y -l yes -d 'Assume yes for prompts'
complete -c ffsend -n "__fish_seen_subcommand_from exists" -s q -l quiet -d 'Produce output suitable for logging and automation'
complete -c ffsend -n "__fish_seen_subcommand_from exists" -s v -l verbose -d 'Enable verbose information and logging'
complete -c ffsend -n "__fish_seen_subcommand_from exists" -s i -l incognito -d 'Don\'t update local history for actions'
complete -c ffsend -n "__fish_seen_subcommand_from generate" -s t -l timeout -d 'Request timeout (0 to disable)'
complete -c ffsend -n "__fish_seen_subcommand_from generate" -s T -l transfer-timeout -d 'Transfer timeout (0 to disable)'
complete -c ffsend -n "__fish_seen_subcommand_from generate" -s A -l api -d 'Server API version to use, \'-\' to lookup'
complete -c ffsend -n "__fish_seen_subcommand_from generate" -l basic-auth -d 'Protected proxy HTTP basic authentication credentials (not FxA)'
complete -c ffsend -n "__fish_seen_subcommand_from generate" -s H -l history -d 'Use the specified history file'
complete -c ffsend -n "__fish_seen_subcommand_from generate" -s h -l help -d 'Prints help information'
complete -c ffsend -n "__fish_seen_subcommand_from generate" -s V -l version -d 'Prints version information'
complete -c ffsend -n "__fish_seen_subcommand_from generate" -s f -l force -d 'Force the action, ignore warnings'
complete -c ffsend -n "__fish_seen_subcommand_from generate" -s I -l no-interact -d 'Not interactive, do not prompt'
complete -c ffsend -n "__fish_seen_subcommand_from generate" -s y -l yes -d 'Assume yes for prompts'
complete -c ffsend -n "__fish_seen_subcommand_from generate" -s q -l quiet -d 'Produce output suitable for logging and automation'
complete -c ffsend -n "__fish_seen_subcommand_from generate" -s v -l verbose -d 'Enable verbose information and logging'
complete -c ffsend -n "__fish_seen_subcommand_from generate" -s i -l incognito -d 'Don\'t update local history for actions'
complete -c ffsend -n "__fish_seen_subcommand_from generate" -f -a "completions" -d 'Shell completions'
complete -c ffsend -n "__fish_seen_subcommand_from generate" -f -a "help" -d 'Prints this message or the help of the given subcommand(s)'
complete -c ffsend -n "__fish_seen_subcommand_from completions" -s o -l output -d 'Shell completion files output directory'
complete -c ffsend -n "__fish_seen_subcommand_from completions" -s t -l timeout -d 'Request timeout (0 to disable)'
complete -c ffsend -n "__fish_seen_subcommand_from completions" -s T -l transfer-timeout -d 'Transfer timeout (0 to disable)'
complete -c ffsend -n "__fish_seen_subcommand_from completions" -s A -l api -d 'Server API version to use, \'-\' to lookup'
complete -c ffsend -n "__fish_seen_subcommand_from completions" -l basic-auth -d 'Protected proxy HTTP basic authentication credentials (not FxA)'
complete -c ffsend -n "__fish_seen_subcommand_from completions" -s H -l history -d 'Use the specified history file'
complete -c ffsend -n "__fish_seen_subcommand_from completions" -s h -l help -d 'Prints help information'
complete -c ffsend -n "__fish_seen_subcommand_from completions" -s V -l version -d 'Prints version information'
complete -c ffsend -n "__fish_seen_subcommand_from completions" -s f -l force -d 'Force the action, ignore warnings'
complete -c ffsend -n "__fish_seen_subcommand_from completions" -s I -l no-interact -d 'Not interactive, do not prompt'
complete -c ffsend -n "__fish_seen_subcommand_from completions" -s y -l yes -d 'Assume yes for prompts'
complete -c ffsend -n "__fish_seen_subcommand_from completions" -s q -l quiet -d 'Produce output suitable for logging and automation'
complete -c ffsend -n "__fish_seen_subcommand_from completions" -s v -l verbose -d 'Enable verbose information and logging'
complete -c ffsend -n "__fish_seen_subcommand_from completions" -s i -l incognito -d 'Don\'t update local history for actions'
complete -c ffsend -n "__fish_seen_subcommand_from help" -s t -l timeout -d 'Request timeout (0 to disable)'
complete -c ffsend -n "__fish_seen_subcommand_from help" -s T -l transfer-timeout -d 'Transfer timeout (0 to disable)'
complete -c ffsend -n "__fish_seen_subcommand_from help" -s A -l api -d 'Server API version to use, \'-\' to lookup'
complete -c ffsend -n "__fish_seen_subcommand_from help" -l basic-auth -d 'Protected proxy HTTP basic authentication credentials (not FxA)'
complete -c ffsend -n "__fish_seen_subcommand_from help" -s H -l history -d 'Use the specified history file'
complete -c ffsend -n "__fish_seen_subcommand_from help" -s h -l help -d 'Prints help information'
complete -c ffsend -n "__fish_seen_subcommand_from help" -s V -l version -d 'Prints version information'
complete -c ffsend -n "__fish_seen_subcommand_from help" -s f -l force -d 'Force the action, ignore warnings'
complete -c ffsend -n "__fish_seen_subcommand_from help" -s I -l no-interact -d 'Not interactive, do not prompt'
complete -c ffsend -n "__fish_seen_subcommand_from help" -s y -l yes -d 'Assume yes for prompts'
complete -c ffsend -n "__fish_seen_subcommand_from help" -s q -l quiet -d 'Produce output suitable for logging and automation'
complete -c ffsend -n "__fish_seen_subcommand_from help" -s v -l verbose -d 'Enable verbose information and logging'
complete -c ffsend -n "__fish_seen_subcommand_from help" -s i -l incognito -d 'Don\'t update local history for actions'
complete -c ffsend -n "__fish_seen_subcommand_from info" -s o -l owner -d 'Specify the file owner token'
complete -c ffsend -n "__fish_seen_subcommand_from info" -s p -l password -d 'Unlock a password protected file'
complete -c ffsend -n "__fish_seen_subcommand_from info" -s t -l timeout -d 'Request timeout (0 to disable)'
complete -c ffsend -n "__fish_seen_subcommand_from info" -s T -l transfer-timeout -d 'Transfer timeout (0 to disable)'
complete -c ffsend -n "__fish_seen_subcommand_from info" -s A -l api -d 'Server API version to use, \'-\' to lookup'
complete -c ffsend -n "__fish_seen_subcommand_from info" -l basic-auth -d 'Protected proxy HTTP basic authentication credentials (not FxA)'
complete -c ffsend -n "__fish_seen_subcommand_from info" -s H -l history -d 'Use the specified history file'
complete -c ffsend -n "__fish_seen_subcommand_from info" -s h -l help -d 'Prints help information'
complete -c ffsend -n "__fish_seen_subcommand_from info" -s V -l version -d 'Prints version information'
complete -c ffsend -n "__fish_seen_subcommand_from info" -s f -l force -d 'Force the action, ignore warnings'
complete -c ffsend -n "__fish_seen_subcommand_from info" -s I -l no-interact -d 'Not interactive, do not prompt'
complete -c ffsend -n "__fish_seen_subcommand_from info" -s y -l yes -d 'Assume yes for prompts'
complete -c ffsend -n "__fish_seen_subcommand_from info" -s q -l quiet -d 'Produce output suitable for logging and automation'
complete -c ffsend -n "__fish_seen_subcommand_from info" -s v -l verbose -d 'Enable verbose information and logging'
complete -c ffsend -n "__fish_seen_subcommand_from info" -s i -l incognito -d 'Don\'t update local history for actions'
complete -c ffsend -n "__fish_seen_subcommand_from parameters" -s o -l owner -d 'Specify the file owner token'
complete -c ffsend -n "__fish_seen_subcommand_from parameters" -s d -l download-limit -d 'The file download limit'
complete -c ffsend -n "__fish_seen_subcommand_from parameters" -s t -l timeout -d 'Request timeout (0 to disable)'
complete -c ffsend -n "__fish_seen_subcommand_from parameters" -s T -l transfer-timeout -d 'Transfer timeout (0 to disable)'
complete -c ffsend -n "__fish_seen_subcommand_from parameters" -s A -l api -d 'Server API version to use, \'-\' to lookup'
complete -c ffsend -n "__fish_seen_subcommand_from parameters" -l basic-auth -d 'Protected proxy HTTP basic authentication credentials (not FxA)'
complete -c ffsend -n "__fish_seen_subcommand_from parameters" -s H -l history -d 'Use the specified history file'
complete -c ffsend -n "__fish_seen_subcommand_from parameters" -s h -l help -d 'Prints help information'
complete -c ffsend -n "__fish_seen_subcommand_from parameters" -s V -l version -d 'Prints version information'
complete -c ffsend -n "__fish_seen_subcommand_from parameters" -s f -l force -d 'Force the action, ignore warnings'
complete -c ffsend -n "__fish_seen_subcommand_from parameters" -s I -l no-interact -d 'Not interactive, do not prompt'
complete -c ffsend -n "__fish_seen_subcommand_from parameters" -s y -l yes -d 'Assume yes for prompts'
complete -c ffsend -n "__fish_seen_subcommand_from parameters" -s q -l quiet -d 'Produce output suitable for logging and automation'
complete -c ffsend -n "__fish_seen_subcommand_from parameters" -s v -l verbose -d 'Enable verbose information and logging'
complete -c ffsend -n "__fish_seen_subcommand_from parameters" -s i -l incognito -d 'Don\'t update local history for actions'
complete -c ffsend -n "__fish_seen_subcommand_from password" -s p -l password -d 'Specify a password, do not prompt'
complete -c ffsend -n "__fish_seen_subcommand_from password" -s o -l owner -d 'Specify the file owner token'
complete -c ffsend -n "__fish_seen_subcommand_from password" -s t -l timeout -d 'Request timeout (0 to disable)'
complete -c ffsend -n "__fish_seen_subcommand_from password" -s T -l transfer-timeout -d 'Transfer timeout (0 to disable)'
complete -c ffsend -n "__fish_seen_subcommand_from password" -s A -l api -d 'Server API version to use, \'-\' to lookup'
complete -c ffsend -n "__fish_seen_subcommand_from password" -l basic-auth -d 'Protected proxy HTTP basic authentication credentials (not FxA)'
complete -c ffsend -n "__fish_seen_subcommand_from password" -s H -l history -d 'Use the specified history file'
complete -c ffsend -n "__fish_seen_subcommand_from password" -s P -l gen-passphrase -d 'Protect the file with a generated passphrase'
complete -c ffsend -n "__fish_seen_subcommand_from password" -s h -l help -d 'Prints help information'
complete -c ffsend -n "__fish_seen_subcommand_from password" -s V -l version -d 'Prints version information'
complete -c ffsend -n "__fish_seen_subcommand_from password" -s f -l force -d 'Force the action, ignore warnings'
complete -c ffsend -n "__fish_seen_subcommand_from password" -s I -l no-interact -d 'Not interactive, do not prompt'
complete -c ffsend -n "__fish_seen_subcommand_from password" -s y -l yes -d 'Assume yes for prompts'
complete -c ffsend -n "__fish_seen_subcommand_from password" -s q -l quiet -d 'Produce output suitable for logging and automation'
complete -c ffsend -n "__fish_seen_subcommand_from password" -s v -l verbose -d 'Enable verbose information and logging'
complete -c ffsend -n "__fish_seen_subcommand_from password" -s i -l incognito -d 'Don\'t update local history for actions'
complete -c ffsend -n "__fish_seen_subcommand_from upload" -s p -l password -d 'Protect the file with a password'
complete -c ffsend -n "__fish_seen_subcommand_from upload" -s d -l download-limit -d 'The file download limit'
complete -c ffsend -n "__fish_seen_subcommand_from upload" -s e -l expiry-time -d 'The file expiry time'
complete -c ffsend -n "__fish_seen_subcommand_from upload" -s h -l host -d 'The remote host to upload to'
complete -c ffsend -n "__fish_seen_subcommand_from upload" -s n -l name -d 'Rename the file being uploaded'
complete -c ffsend -n "__fish_seen_subcommand_from upload" -s t -l timeout -d 'Request timeout (0 to disable)'
complete -c ffsend -n "__fish_seen_subcommand_from upload" -s T -l transfer-timeout -d 'Transfer timeout (0 to disable)'
complete -c ffsend -n "__fish_seen_subcommand_from upload" -s A -l api -d 'Server API version to use, \'-\' to lookup'
complete -c ffsend -n "__fish_seen_subcommand_from upload" -l basic-auth -d 'Protected proxy HTTP basic authentication credentials (not FxA)'
complete -c ffsend -n "__fish_seen_subcommand_from upload" -s H -l history -d 'Use the specified history file'
complete -c ffsend -n "__fish_seen_subcommand_from upload" -s P -l gen-passphrase -d 'Protect the file with a generated passphrase'
complete -c ffsend -n "__fish_seen_subcommand_from upload" -s o -l open -d 'Open the share link in your browser'
complete -c ffsend -n "__fish_seen_subcommand_from upload" -s D -l delete -d 'Delete local file after upload'
complete -c ffsend -n "__fish_seen_subcommand_from upload" -s a -l archive -d 'Archive the upload in a single file'
complete -c ffsend -n "__fish_seen_subcommand_from upload" -s c -l copy -d 'Copy the share link to your clipboard'
complete -c ffsend -n "__fish_seen_subcommand_from upload" -s C -l copy-cmd -d 'Copy the ffsend download command to your clipboard'
complete -c ffsend -n "__fish_seen_subcommand_from upload" -s S -l shorten -d 'Shorten share URLs with a public service'
complete -c ffsend -n "__fish_seen_subcommand_from upload" -s Q -l qrcode -d 'Print a QR code for the share URL'
complete -c ffsend -n "__fish_seen_subcommand_from upload" -l help -d 'Prints help information'
complete -c ffsend -n "__fish_seen_subcommand_from upload" -s V -l version -d 'Prints version information'
complete -c ffsend -n "__fish_seen_subcommand_from upload" -s f -l force -d 'Force the action, ignore warnings'
complete -c ffsend -n "__fish_seen_subcommand_from upload" -s I -l no-interact -d 'Not interactive, do not prompt'
complete -c ffsend -n "__fish_seen_subcommand_from upload" -s y -l yes -d 'Assume yes for prompts'
complete -c ffsend -n "__fish_seen_subcommand_from upload" -s q -l quiet -d 'Produce output suitable for logging and automation'
complete -c ffsend -n "__fish_seen_subcommand_from upload" -s v -l verbose -d 'Enable verbose information and logging'
complete -c ffsend -n "__fish_seen_subcommand_from upload" -s i -l incognito -d 'Don\'t update local history for actions'
complete -c ffsend -n "__fish_seen_subcommand_from version" -s h -l host -d 'The remote host to upload to'
complete -c ffsend -n "__fish_seen_subcommand_from version" -s t -l timeout -d 'Request timeout (0 to disable)'
complete -c ffsend -n "__fish_seen_subcommand_from version" -s T -l transfer-timeout -d 'Transfer timeout (0 to disable)'
complete -c ffsend -n "__fish_seen_subcommand_from version" -s A -l api -d 'Server API version to use, \'-\' to lookup'
complete -c ffsend -n "__fish_seen_subcommand_from version" -l basic-auth -d 'Protected proxy HTTP basic authentication credentials (not FxA)'
complete -c ffsend -n "__fish_seen_subcommand_from version" -s H -l history -d 'Use the specified history file'
complete -c ffsend -n "__fish_seen_subcommand_from version" -l help -d 'Prints help information'
complete -c ffsend -n "__fish_seen_subcommand_from version" -s V -l version -d 'Prints version information'
complete -c ffsend -n "__fish_seen_subcommand_from version" -s f -l force -d 'Force the action, ignore warnings'
complete -c ffsend -n "__fish_seen_subcommand_from version" -s I -l no-interact -d 'Not interactive, do not prompt'
complete -c ffsend -n "__fish_seen_subcommand_from version" -s y -l yes -d 'Assume yes for prompts'
complete -c ffsend -n "__fish_seen_subcommand_from version" -s q -l quiet -d 'Produce output suitable for logging and automation'
complete -c ffsend -n "__fish_seen_subcommand_from version" -s v -l verbose -d 'Enable verbose information and logging'
complete -c ffsend -n "__fish_seen_subcommand_from version" -s i -l incognito -d 'Don\'t update local history for actions'
complete -c ffsend -n "__fish_seen_subcommand_from history" -s R -l rm -d 'Remove history entry'
complete -c ffsend -n "__fish_seen_subcommand_from history" -s t -l timeout -d 'Request timeout (0 to disable)'
complete -c ffsend -n "__fish_seen_subcommand_from history" -s T -l transfer-timeout -d 'Transfer timeout (0 to disable)'
complete -c ffsend -n "__fish_seen_subcommand_from history" -s A -l api -d 'Server API version to use, \'-\' to lookup'
complete -c ffsend -n "__fish_seen_subcommand_from history" -l basic-auth -d 'Protected proxy HTTP basic authentication credentials (not FxA)'
complete -c ffsend -n "__fish_seen_subcommand_from history" -s H -l history -d 'Use the specified history file'
complete -c ffsend -n "__fish_seen_subcommand_from history" -s C -l clear -d 'Clear all history'
complete -c ffsend -n "__fish_seen_subcommand_from history" -s h -l help -d 'Prints help information'
complete -c ffsend -n "__fish_seen_subcommand_from history" -s V -l version -d 'Prints version information'
complete -c ffsend -n "__fish_seen_subcommand_from history" -s f -l force -d 'Force the action, ignore warnings'
complete -c ffsend -n "__fish_seen_subcommand_from history" -s I -l no-interact -d 'Not interactive, do not prompt'
complete -c ffsend -n "__fish_seen_subcommand_from history" -s y -l yes -d 'Assume yes for prompts'
complete -c ffsend -n "__fish_seen_subcommand_from history" -s q -l quiet -d 'Produce output suitable for logging and automation'
complete -c ffsend -n "__fish_seen_subcommand_from history" -s v -l verbose -d 'Enable verbose information and logging'
complete -c ffsend -n "__fish_seen_subcommand_from history" -s i -l incognito -d 'Don\'t update local history for actions'
complete -c ffsend -n "__fish_seen_subcommand_from help" -s t -l timeout -d 'Request timeout (0 to disable)'
complete -c ffsend -n "__fish_seen_subcommand_from help" -s T -l transfer-timeout -d 'Transfer timeout (0 to disable)'
complete -c ffsend -n "__fish_seen_subcommand_from help" -s A -l api -d 'Server API version to use, \'-\' to lookup'
complete -c ffsend -n "__fish_seen_subcommand_from help" -l basic-auth -d 'Protected proxy HTTP basic authentication credentials (not FxA)'
complete -c ffsend -n "__fish_seen_subcommand_from help" -s H -l history -d 'Use the specified history file'
complete -c ffsend -n "__fish_seen_subcommand_from help" -s h -l help -d 'Prints help information'
complete -c ffsend -n "__fish_seen_subcommand_from help" -s V -l version -d 'Prints version information'
complete -c ffsend -n "__fish_seen_subcommand_from help" -s f -l force -d 'Force the action, ignore warnings'
complete -c ffsend -n "__fish_seen_subcommand_from help" -s I -l no-interact -d 'Not interactive, do not prompt'
complete -c ffsend -n "__fish_seen_subcommand_from help" -s y -l yes -d 'Assume yes for prompts'
complete -c ffsend -n "__fish_seen_subcommand_from help" -s q -l quiet -d 'Produce output suitable for logging and automation'
complete -c ffsend -n "__fish_seen_subcommand_from help" -s v -l verbose -d 'Enable verbose information and logging'
complete -c ffsend -n "__fish_seen_subcommand_from help" -s i -l incognito -d 'Don\'t update local history for actions'

View file

@ -1,8 +0,0 @@
#!/usr/bin/env bash
# Stop on error
set -e
echo "Generating all completions using cargo debug binary..."
cargo run -q -- generate completions all --output "$PWD"
echo "Done."

View file

@ -1,6 +0,0 @@
`firefox-send` is a script for Nautilus/Nemo/Caja (maybe it needs some adaptation for Caja) to send files directly from the file browser, using the contextual menu.
* Copy the `firefox-send` file to ~/.local/share/nautilus/scripts/firefox-send
* Modify the default options to your use case: host server, download number, retention time.
* Make the file executable (`chmod +x firefox-send`).
* Restart Nautilus/Nemo/Caja.

View file

@ -1,46 +0,0 @@
#!/bin/bash
#CONSTANTS
#FILEPATH=`echo $NAUTILUS_SCRIPT_SELECTED_URIS | sed 's@file://@@g'`
# Quote the paths
IFS=$'\n' read -d '' -r -a FILEPATH <<< "$NAUTILUS_SCRIPT_SELECTED_FILE_PATHS"
FFSEND_BIN='/usr/bin/ffsend'
FFSEND_BIN_OPTS="upload --open --copy"
ZENITY='/usr/bin/zenity '
ZENITY_PROGRESS_OPTIONS='--auto-close --auto-kill' #you can remove this if you like
#sanity checks
for sanity_check in $FFSEND_BIN "${FILEPATH[@]}"
do
ZENITY_ERROR_SANITY="There is an error, it involved $sanity_check.\n Probably binary or file missing"
if [ ! -e $sanity_check ]
then
#zenity --error --text="$(eval "echo \"$ZENITY_ERROR_SANITY\"")"
zenity --error --text="$ZENITY_ERROR_SANITY"
exit
fi
done
# Use the following flags automatically from now on
# -I: no interaction
# -f: force
# -y: yes
# -q: quiet
export FFSEND_NO_INTERACT=1 FFSEND_FORCE=1 FFSEND_YES=1 FFSEND_QUIET=1
export FFSEND_HOST=https://send.boblorange.net
export FFSEND_EXPIRY_TIME=604800
export FFSEND_DOWNLOAD_LIMIT=5
#check whether copying file or directory
if [ ! -f "${FILEPATH[@]}" ]; then
FFSEND_BIN_OPTS="$FFSEND_BIN_OPTS --archive"
fi
# Upload a file
#zenity --info --text="Ready to send: $FFSEND_BIN $FFSEND_BIN_OPTS ${FILEPATH[@]}"
$FFSEND_BIN $FFSEND_BIN_OPTS "${FILEPATH[@]}" | $($ZENITY --progress --text="sending $(basename $FILEPATH)" --pulsate $ZENITY_PROGRESS_OPTIONS)
#echo -e "$FILEPATH" | xargs -i $FFSEND_BIN $FFSEND_BIN_OPTS {} | $($ZENITY --progress --text="sending $(basename $FILEPATH)" --pulsate $ZENITY_PROGRESS_OPTIONS)
# Upload a file
#echo -e "$NAUTILUS_SCRIPT_SELECTED_FILE_PATHS" | xargs -i ffsend upload --open --copy {}

View file

@ -1,65 +1 @@
# Contributor: Rasmus Thomsen <oss@cogitri.dev>
# Maintainer: Rasmus Thomsen <oss@cogitri.dev>
pkgname=ffsend
pkgver=0.2.62
pkgrel=0
pkgdesc=" A fully featured Send client"
url="https://gitlab.com/timvisee/ffsend"
arch="x86_64 x86 armhf armv7 aarch64 ppc64le" # limited by cargo
license="GPL-3.0-only"
makedepends="cargo openssl-dev"
subpackages="
$pkgname-zsh-completion:zshcomp:noarch
$pkgname-fish-completion:fishcomp:noarch
$pkgname-bash-completion:bashcomp:noarch
"
source="https://gitlab.com/timvisee/ffsend/-/archive/v$pkgver/ffsend-v$pkgver.tar.gz"
builddir="$srcdir/$pkgname-v$pkgver"
case "$CARCH" in
x86)
export CFLAGS="$CFLAGS -fno-stack-protector"
;;
esac
build() {
cargo build --release
}
check() {
cargo test --release
}
package() {
cargo install --path . --root="$pkgdir/usr"
rm "$pkgdir"/usr/.crates.toml
}
bashcomp() {
depends=""
pkgdesc="Bash completions for $pkgname"
install_if="$pkgname=$pkgver-r$pkgrel bash-completion"
install -Dm644 "$builddir"/contrib/completions/ffsend.bash \
"$subpkgdir"/usr/share/bash-completion/completions/ffsend
}
zshcomp() {
depends=""
pkgdesc="Zsh compltions for $pkgname"
install_if="$pkgname=$pkgver-r$pkgrel zsh"
install -Dm644 "$builddir"/contrib/completions/_ffsend \
"$subpkgdir"/usr/share/zsh/site-functions/_ffsend
}
fishcomp() {
depends=""
pkgdesc="Fish completions for $pkgname"
install_if="$pkgname=$pkgver-r$pkgrel fish"
install -Dm644 "$builddir"/contrib/completions/ffsend.fish \
"$subpkgdir"/usr/share/fish/completions/ffsend.fish
}
sha512sums="97477f59a498f4805340647c50c139d70ddbabaebb49b4acbae595ed9987fa2941b8d4dfd2edf76264b2a500fa10ac10f8583938fb29013936d9c092fe7fe7f1 ffsend-v0.2.51.tar.gz"
# TODO: implement this

18
pkg/aur/bin/PKGBUILD Normal file
View file

@ -0,0 +1,18 @@
# Maintainer: Tim Visee <tim@visee.me>
pkgname=ffsend
pkgver=0.0.0
pkgrel=1
pkgdesc="Easily and securely share files from the command line. A Firefox Send client."
url="https://gitlab.com/timvisee/ffsend"
license=('GPL3')
conflicts=('ffsend-git')
source=("ffsend::https://github.com/timvisee/ffsend/releases/download/v$pkgver/ffsend-v$pkgver-linux-x64-static")
sha256sums=('SKIP')
arch=('x86_64')
depends=('ca-certificates')
optdepends=('xclip: clipboard support')
package() {
install -D "$srcdir/ffsend" "$pkgdir/usr/bin/ffsend"
}

View file

@ -1,37 +0,0 @@
# Maintainer: Tim Visee <tim@visee.me>
# Contributor: Ariel AxionL <i at axionl dot me>
#
# This PKGBUILD is managed externally, and is automatically updated here:
# - https://gitlab.com/timvisee/ffsend/blob/master/pkg/aur/ffsend-bin/PKGBUILD
# - Mirror: https://github.com/timvisee/ffsend/blob/master/pkg/aur/ffsend-bin/PKGBUILD
pkgname=ffsend-bin
pkgver=0.0.0 # automatically set in CI, see: /.gitlab-ci.yml
pkgrel=1
pkgdesc="Easily and securely share files from the command line. A Send client."
url="https://gitlab.com/timvisee/ffsend"
license=('GPL3')
source=("ffsend-v$pkgver::https://github.com/timvisee/ffsend/releases/download/v$pkgver/ffsend-v$pkgver-linux-x64-static"
"ffsend-v$pkgver.bash::https://raw.githubusercontent.com/timvisee/ffsend/v$pkgver/contrib/completions/ffsend.bash"
"ffsend-v$pkgver.zsh::https://raw.githubusercontent.com/timvisee/ffsend/v$pkgver/contrib/completions/_ffsend"
"ffsend-v$pkgver.fish::https://raw.githubusercontent.com/timvisee/ffsend/v$pkgver/contrib/completions/ffsend.fish"
"LICENSE-v$pkgver::https://raw.githubusercontent.com/timvisee/ffsend/v$pkgver/LICENSE") # automatically set in CI, see: /.gitlab-ci.yml
sha256sums=('SKIP')
arch=('x86_64')
provides=('ffsend')
conflicts=('ffsend')
depends=('ca-certificates')
optdepends=('xclip: clipboard support'
'bash-completion: support auto completion for bash')
package() {
cd "$srcdir"
install -Dm755 "ffsend-v$pkgver" "$pkgdir/usr/bin/ffsend"
# Shell completions and LICENSE file
install -Dm644 "ffsend-v$pkgver.bash" "$pkgdir/usr/share/bash-completion/completions/ffsend"
install -Dm644 "ffsend-v$pkgver.zsh" "$pkgdir/usr/share/zsh/site-functions/_ffsend"
install -Dm644 "ffsend-v$pkgver.fish" "$pkgdir/usr/share/fish/vendor_completions.d/ffsend.fish"
install -Dm644 "LICENSE-v$pkgver" "$pkgdir/usr/share/licenses/ffsend/LICENSE"
}

View file

@ -1,57 +0,0 @@
# Maintainer: Tim Visee <tim@visee.me>
#
# This PKGBUILD is managed externally, and is automatically updated here:
# - https://gitlab.com/timvisee/ffsend/blob/master/pkg/aur/ffsend-git/PKGBUILD
# - Mirror: https://github.com/timvisee/ffsend/blob/master/pkg/aur/ffsend-git/PKGBUILD
pkgname=ffsend-git
pkgver=0.0.0 # automatically set in CI, see: /.gitlab-ci.yml
pkgrel=1
pkgdesc="Easily and securely share files from the command line. A Send client."
url="https://gitlab.com/timvisee/ffsend"
license=('GPL3')
source=("git+${url}")
sha256sums=('SKIP')
arch=('x86_64' 'i686')
provides=('ffsend')
conflicts=('ffsend')
depends=('ca-certificates')
makedepends=('cargo' 'cmake' 'openssl>=1.0')
optdepends=('xclip: clipboard support')
prepare() {
cd "${pkgname%-git}"
cargo fetch --locked --target "$CARCH-unknown-linux-gnu"
}
build() {
cd "${pkgname%-git}"
export RUSTUP_TOOLCHAIN=stable
export CARGO_TARGET_DIR=target
cargo build --frozen --release
}
check() {
cd "${pkgname%-git}"
export RUSTUP_TOOLCHAIN=stable
cargo test --frozen
}
package() {
cd "${pkgname%-git}"
install -Dm0755 -t "$pkgdir/usr/bin/" "target/release/ffsend"
# Shell completions and LICENSE file
install -Dm644 "contrib/completions/ffsend.bash" \
"$pkgdir/etc/bash_completion.d/ffsend"
install -Dm644 "contrib/completions/_ffsend" \
"$pkgdir/usr/share/zsh/site-functions/_ffsend"
install -Dm644 "contrib/completions/ffsend.fish" \
"$pkgdir/usr/share/fish/vendor_completions.d/ffsend.fish"
install -Dm644 "LICENSE" \
"$pkgdir/usr/share/licenses/ffsend/LICENSE"
}

View file

@ -1,55 +0,0 @@
# Maintainer: Tim Visee <tim@visee.me>
#
# This PKGBUILD is managed externally, and is automatically updated here:
# - https://gitlab.com/timvisee/ffsend/blob/master/pkg/aur/ffsend/PKGBUILD
# - Mirror: https://github.com/timvisee/ffsend/blob/master/pkg/aur/ffsend/PKGBUILD
pkgname=ffsend
pkgver=0.0.0 # automatically set in CI, see: /.gitlab-ci.yml
pkgrel=1
pkgdesc="Easily and securely share files from the command line. A Send client."
url="https://gitlab.com/timvisee/ffsend"
license=('GPL3')
source=("$url/-/archive/v$pkgver/ffsend-v$pkgver.tar.gz") # automatically set in CI, see: /.gitlab-ci.yml
sha256sums=('SKIP') # automatically set in CI, see: /.gitlab-ci.yml
arch=('x86_64' 'i686')
depends=('ca-certificates')
makedepends=('cargo' 'cmake' 'openssl>=1.0')
optdepends=('xclip: clipboard support')
prepare() {
cd "$pkgname-v$pkgver"
cargo fetch --locked --target "$CARCH-unknown-linux-gnu"
}
build() {
cd "$pkgname-v$pkgver"
export RUSTUP_TOOLCHAIN=stable
export CARGO_TARGET_DIR=target
cargo build --frozen --release
}
check() {
cd "$pkgname-v$pkgver"
export RUSTUP_TOOLCHAIN=stable
cargo test --frozen
}
package() {
cd "$pkgname-v$pkgver"
install -Dm0755 -t "$pkgdir/usr/bin/" "target/release/$pkgname"
# Shell completions and LICENSE file
install -Dm644 "contrib/completions/ffsend.bash" \
"$pkgdir/etc/bash_completion.d/ffsend"
install -Dm644 "contrib/completions/_ffsend" \
"$pkgdir/usr/share/zsh/site-functions/_ffsend"
install -Dm644 "contrib/completions/ffsend.fish" \
"$pkgdir/usr/share/fish/vendor_completions.d/ffsend.fish"
install -Dm644 "LICENSE" \
"$pkgdir/usr/share/licenses/ffsend/LICENSE"
}

25
pkg/aur/git/PKGBUILD Normal file
View file

@ -0,0 +1,25 @@
# Maintainer: Tim Visee <tim@visee.me>
pkgname=ffsend-git
pkgver=0.0.0
pkgrel=1
pkgdesc="Easily and securely share files from the command line. A Firefox Send client."
url="https://gitlab.com/timvisee/ffsend"
license=('GPL3')
provides=('ffsend')
conflicts=('ffsend')
source=("ffsend-v$pkgver.tar.gz::$url/-/archive/v$pkgver/ffsend-v$pkgver.tar.gz")
sha256sums=('SKIP')
arch=('x86_64' 'i686')
depends=('ca-certificates')
makedepends=('openssl>=1.0' 'rust>=1.32' 'cargo' 'cmake')
optdepends=('xclip: clipboard support')
build() {
cd "ffsend-v$pkgver"
env CARGO_INCREMENTAL=0 cargo build --release
}
package() {
install -Dm755 "$srcdir/ffsend-v$pkgver/target/release/ffsend" "$pkgdir/usr/bin/ffsend"
}

View file

@ -45,12 +45,14 @@ This is a nuspec. It mostly adheres to https://docs.nuget.org/create/Nuspec-Refe
<docsUrl>https://github.com/timvisee/ffsend</docsUrl>
<bugTrackerUrl>https://gitlab.com/timvisee/ffsend/issues</bugTrackerUrl>
<tags>ffsend firefox-send cli file-sharing file-upload encryption rust</tags>
<summary>Easily and securely share files from the command line. A fully featured Send client.</summary>
<description>Easily and securely share files from the command line. A fully featured Send client.</description>
<summary>Easily and securely share files from the command line. A fully featured Firefox Send client.</summary>
<description>Easily and securely share files from the command line. A fully featured Firefox Send client.</description>
<!-- =============================== -->
<!-- Specifying dependencies and version ranges? https://docs.nuget.org/create/versioning#specifying-version-ranges-in-.nuspec-files -->
<dependencies></dependencies>
<dependencies>
<dependency id="openssl.light" version="" />
</dependencies>
</metadata>
<files>
<!-- this section controls what actually gets packaged into the Chocolatey package -->

View file

@ -9,13 +9,13 @@ if [[ ! $TRAVIS_TAG =~ ^v([0-9]+\.)*[0-9]+$ ]]; then
fi
# Ensure the debian architecture is set
if [[ -z $DEB_ARCH ]]; then
if [[ -z "$DEB_ARCH" ]]; then
echo "Error: debian architecture not configured in \$DEB_ARCH"
exit 1
fi
# Define some useful variables
DIR=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
VERSION=${TRAVIS_TAG:1}
# Ensure the binary file exists
@ -25,23 +25,23 @@ if [[ ! -f "$DIR/../ffsend" ]]; then
fi
# Create an application directory, copy the binary into it
mkdir -p "$DIR/ffsend-$VERSION"
cp -- "$DIR/../ffsend" "$DIR/ffsend-$VERSION/ffsend"
mkdir -p $DIR/ffsend-$VERSION
cp $DIR/../ffsend $DIR/ffsend-$VERSION/ffsend
# Create an application tarball
cd -- "$DIR/.."
git archive --format tar.gz -o "$DIR/ffsend-$VERSION/ffsend-$VERSION.tar.gz" "$TRAVIS_TAG"
# Create an application tarbal
cd $DIR/..
git archive --format tar.gz -o $DIR/ffsend-$VERSION/ffsend-$VERSION.tar.gz $TRAVIS_TAG
# Change into the app directory
cd -- "$DIR/ffsend-$VERSION"
cd $DIR/ffsend-$VERSION
# Build the debian package
# TODO: define GPG?
dh_make -e "timvisee@gmail.com" -c gpl3 -f "ffsend-$VERSION.tar.gz" -s -y
rm -- *.ex README.Debian README.source
dh_make -e "timvisee@gmail.com" -c gpl3 -f ffsend-$VERSION.tar.gz -s -y
rm *.ex README.Debian README.source
# Remove the project tar ball, we're not using it anymore
rm -- "$DIR/ffsend-$VERSION/ffsend-$VERSION.tar.gz"
rm $DIR/ffsend-$VERSION/ffsend-$VERSION.tar.gz
# TODO: configure the debian/control file
# TODO: configure copyright file

View file

@ -2,7 +2,7 @@
# Unlink the ffs alias if it links to ffsend
if [[ -L /usr/bin/ffs ]] \
&& [[ $(realpath /usr/bin/ffs) == "/usr/bin/ffsend" ]]; \
&& [[ $(ls -l /usr/bin/ffs | sed -e 's/.* -> //') == "/usr/bin/ffsend" ]]; \
then
echo "Removing ffs alias for ffsend..."
unlink /usr/bin/ffs

View file

@ -1,7 +0,0 @@
FROM alpine:latest
LABEL maintainer="Tim Visée <3a4fb3964f@sinenomine.email>"
COPY ./ffsend /
WORKDIR /data/
ENTRYPOINT ["/ffsend"]

View file

@ -1,8 +0,0 @@
# scoop manifest for ffsend
This directory contains a [`scoop`][scoop] manifest template for `ffsend`.
The currently published manifest can be found in the `scoop` repository,
and is automatically updated:
https://github.com/ScoopInstaller/Main/blob/master/bucket/ffsend.json
[scoop]: https://scoop.rs/

View file

@ -1,21 +0,0 @@
{
"homepage": "https://github.com/timvisee/ffsend",
"description": "Easily and securely share files from the command line. A fully featured Send client.",
"license": "GPL-3.0-only",
"version": "0.0.0",
"architecture": {
"64bit": {
"url": "https://github.com/timvisee/ffsend/releases/download/v0.2.30/ffsend-v0.2.30-windows-x64-static.exe#/ffsend.exe",
"hash": "297f1405bacdc34948cd94ba785336c48e1ac862984268d66a8928abd3e322e1"
}
},
"bin": "ffsend.exe",
"checkver": "github",
"autoupdate": {
"architecture": {
"64bit": {
"url": "https://github.com/timvisee/ffsend/releases/download/v$version/ffsend-v$version-windows-x64-static.exe#/ffsend.exe"
}
}
}
}

View file

@ -13,7 +13,6 @@ description: |
An optional password may be specified, and a default file lifetime of 1
(up to 20) download or 24 hours is enforced to ensure your stuff does not
remain online forever. This provides a secure platform to share your files.
# license: GPL-3.0
architectures:
- amd64
@ -24,7 +23,7 @@ confinement: strict
apps:
ffsend:
command: ffsend
plugs: [desktop, home, network, removable-media, x11]
plugs: [home, network, removable-media, x11]
parts:
ffsend:

View file

@ -1,4 +1,4 @@
#!/usr/bin/env bash
#!/bin/bash
# Ensure svg-term is installed
if ! [ -x "$(command -v svg-term)" ]; then

View file

@ -1 +0,0 @@
eyJyIjogIk1EQXlPV3h2WTJGMGFXOXVJRzE1WVhCd2N5NWtaWFpsYkc5d1pYSXVkV0oxYm5SMUxtTnZiUW93TURFMmFXUmxiblJwWm1sbGNpQk5lVUZ3Y0hNS01EQTBZbU5wWkNCdGVXRndjSE11WkdWMlpXeHZjR1Z5TG5WaWRXNTBkUzVqYjIxOGRtRnNhV1JmYzJsdVkyVjhNakF5TWkwd05pMHlNRlF3T0Rvd016b3hNaTQwTlRreU9EUUtNREUzWkdOcFpDQjdJblpsY25OcGIyNGlPaUF4TENBaWMyVmpjbVYwSWpvZ0ltOWFVRzlpVG1vdlJYTmhkazlYUVZoRmNIQlRNM1JNY0RGak1tdFZaVU5NYzFaWlNUQmFaa1JFZERkcVozaHhTVlI2VEhselFtOVZjV3N5VURad1JXdFFRblpNUkhOMFkwTTNSRlU1VUVFMGFXVlpPV0U0WWxGU2VtNHJNbTQxVEVsRWMwY3ZTVEphVEM4eU1uSTBjakZDYmlzM1VqRnhUVWt2WjNoR1kzVnVVV05pUlZkeVVWQXdZV3BrUTBrd1pWRmxOVEZsVmpWSVJrbzNjMlZGU0c5WVpFRlZTVVZ1Vm1GNU5sRnFVMDQzTW1SSFpWSlVabVZvYVVacmFWbFFaVVJJZVN0d1VqbHVjMXBNZW5aMFNYbHNPRnBYYVhkSVdIcFpRVTl2T0Rob1VXdFhZakZ6UzNGSGFYWjZWRFZEWlZoV1IyaElaalJSUVdRNVIwOWxLMjUxUmpWM2JHMWtaRlIwY1U1YWNWQnNjVkZHYjBoelprUk9jRFpvVFd4dWFHbDVMMGxvY2tOclZscENUVzFMYld0cFIwVk1VWFZMTUVSUVFtdFFNSE5RVWpaVVdsWnFjVGQzZVRaaFdIa3dVM1ZyZDBaclp6MDlJbjBLTURBMU1YWnBaQ0ExYU5mbjFKU3NBa1VoMFM1NXB4bnp2VUxESlRtcldMLTZSa3B0c3kzbFdVSG02T0hVQnJHeGtfYmlnbnEtbkxIMm9hYVdwaENKRkM1YzNod1BqWjBXUl83bUZ5QWN2QlFLTURBeE9HTnNJR3h2WjJsdUxuVmlkVzUwZFM1amIyMEtNREJoT1dOcFpDQnRlV0Z3Y0hNdVpHVjJaV3h2Y0dWeUxuVmlkVzUwZFM1amIyMThZV05zZkZzaWNHRmphMkZuWlY5aFkyTmxjM01pTENBaWNHRmphMkZuWlY5dFlXNWhaMlVpTENBaWNHRmphMkZuWlY5dFpYUnlhV056SWl3Z0luQmhZMnRoWjJWZmNIVnphQ0lzSUNKd1lXTnJZV2RsWDNKbFoybHpkR1Z5SWl3Z0luQmhZMnRoWjJWZmNtVnNaV0Z6WlNJc0lDSndZV05yWVdkbFgzVndaR0YwWlNKZENqQXdORGRqYVdRZ2JYbGhjSEJ6TG1SbGRtVnNiM0JsY2k1MVluVnVkSFV1WTI5dGZHVjRjR2x5WlhOOE1qQXlNeTB3TmkweU1GUXdPRG93TXpveE1pNHdNREF3TVRjS01EQXlabk5wWjI1aGRIVnlaU0JOYm05MFAyTkpYV3RON0I1aktWbkVBQUZqM0c3dzItLWNhOGloR2lJWUhRbyIsICJkIjogIk1EQXhaV3h2WTJGMGFXOXVJR3h2WjJsdUxuVmlkVzUwZFM1amIyMEtNREU0Tkdsa1pXNTBhV1pwWlhJZ2V5SjJaWEp6YVc5dUlqb2dNU3dnSW5ObFkzSmxkQ0k2SUNKdldsQnZZazVxTDBWellYWlBWMEZZUlhCd1V6TjBUSEF4WXpKclZXVkRUSE5XV1Vrd1dtWkVSSFEzYW1kNGNVbFVla3g1YzBKdlZYRnJNbEEyY0VWclVFSjJURVJ6ZEdORE4wUlZPVkJCTkdsbFdUbGhPR0pSVW5wdUt6SnVOVXhKUkhOSEwwa3lXa3d2TWpKeU5ISXhRbTRyTjFJeGNVMUpMMmQ0Um1OMWJsRmpZa1ZYY2xGUU1HRnFaRU5KTUdWUlpUVXhaVlkxU0VaS04zTmxSVWh2V0dSQlZVbEZibFpoZVRaUmFsTk9OekprUjJWU1ZHWmxhR2xHYTJsWlVHVkVTSGtyY0ZJNWJuTmFUSHAyZEVsNWJEaGFWMmwzU0ZoNldVRlBiemc0YUZGclYySXhjMHR4UjJsMmVsUTFRMlZZVmtkb1NHWTBVVUZrT1VkUFpTdHVkVVkxZDJ4dFpHUlVkSEZPV25GUWJIRlJSbTlJYzJaRVRuQTJhRTFzYm1ocGVTOUphSEpEYTFaYVFrMXRTMjFyYVVkRlRGRjFTekJFVUVKclVEQnpVRkkyVkZwV2FuRTNkM2syWVZoNU1GTjFhM2RHYTJjOVBTSjlDakF3WkRaamFXUWdiRzluYVc0dWRXSjFiblIxTG1OdmJYeGhZMk52ZFc1MGZHVjVTakZqTWxaNVltMUdkRnBUU1RaSlEwb3dZVmN4TW1GWVRteGFVMGx6U1VOS2RtTkhWblZoVjFGcFQybEJhVlZ1WkV4UlZXaDBVME5KYzBsRFNtdGhXRTUzWWtkR05XSnRSblJhVTBrMlNVTktWV0ZYTUdkV2JXeDZXRWhWZDAxSFZUVmFVMGx6U1VOS2JHSlhSbkJpUTBrMlNVTktNR0ZYTUhKa1Ywb3hZbTVTTVdJeU5XeFJTRnB3WXpKV2JFeHRNV3hKYVhkblNXMXNlbGd6V214amJXeHRZVmRXYTBscWIyZGtTRW94V2xnd1BRb3dNRFF3WTJsa0lHeHZaMmx1TG5WaWRXNTBkUzVqYjIxOGRtRnNhV1JmYzJsdVkyVjhNakF5TWkwd05pMHlNRlF3T0Rvd016b3hNeTR5T0RJd09UTUtNREF6WldOcFpDQnNiMmRwYmk1MVluVnVkSFV1WTI5dGZHeGhjM1JmWVhWMGFId3lNREl5TFRBMkxUSXdWREE0T2pBek9qRXpMakk0TWpBNU13b3dNREptYzJsbmJtRjBkWEpsSVBtdzV2aGZjWXhjRVFzX1RHV2VHWUFrQWhxdkJONVJjOFpHTEpnVFZ2eGxDZyJ9

View file

@ -6,8 +6,6 @@ use prettytable::{format::FormatBuilder, Cell, Row, Table};
use crate::client::to_duration;
use crate::cmd::matcher::{debug::DebugMatcher, main::MainMatcher, Matcher};
use crate::error::ActionError;
#[cfg(feature = "clipboard-bin")]
use crate::util::ClipboardType;
use crate::util::{api_version_list, features_list, format_bool, format_duration};
/// A file debug action.
@ -32,12 +30,6 @@ impl<'a> Debug<'a> {
let mut table = Table::new();
table.set_format(FormatBuilder::new().padding(0, 2).build());
// The crate version
table.add_row(Row::new(vec![
Cell::new("Version:"),
Cell::new(crate_version!()),
]));
// The default host
table.add_row(Row::new(vec![
Cell::new("Host:"),
@ -98,22 +90,6 @@ impl<'a> Debug<'a> {
Cell::new(&api_version_list().join(", ")),
]));
// Show used crypto backend
table.add_row(Row::new(vec![
Cell::new("Crypto backend:"),
#[cfg(feature = "crypto-ring")]
Cell::new("ring"),
#[cfg(feature = "crypto-openssl")]
Cell::new("OpenSSL"),
]));
// Clipboard information
#[cfg(feature = "clipboard-bin")]
table.add_row(Row::new(vec![
Cell::new("Clipboard:"),
Cell::new(&format!("{}", ClipboardType::select())),
]));
// Show whether quiet is used
table.add_row(Row::new(vec![
Cell::new("Quiet:"),

View file

@ -2,7 +2,7 @@ use clap::ArgMatches;
use ffsend_api::action::delete::{Delete as ApiDelete, Error as DeleteError};
use ffsend_api::file::remote_file::{FileParseError, RemoteFile};
use crate::client::create_config;
use crate::client::create_client;
use crate::cmd::matcher::{delete::DeleteMatcher, main::MainMatcher, Matcher};
use crate::error::ActionError;
#[cfg(feature = "history")]
@ -30,9 +30,9 @@ impl<'a> Delete<'a> {
// Get the share link
let url = matcher_delete.url();
// Create client
let client_config = create_config(&matcher_main);
let client = client_config.client(false);
// Create a reqwest client
// TODO: create transfer client when draining downloads
let client = create_client(&matcher_main);
// Parse the remote file based on the share link, derive the owner token from history
let mut file = RemoteFile::parse_url(url, matcher_delete.owner())?;

View file

@ -19,14 +19,13 @@ use tempfile::{Builder as TempBuilder, NamedTempFile};
use super::select_api_version;
#[cfg(feature = "archive")]
use crate::archive::archive::Archive;
use crate::client::create_config;
use crate::client::create_transfer_client;
use crate::cmd::matcher::{download::DownloadMatcher, main::MainMatcher, Matcher};
#[cfg(feature = "history")]
use crate::history_tool;
use crate::progress::ProgressBar;
use crate::util::{
ensure_enough_space, ensure_password, follow_url, print_error, prompt_yes, quit, quit_error,
quit_error_msg, ErrorHints,
ensure_enough_space, ensure_password, prompt_yes, quit, quit_error, quit_error_msg, ErrorHints,
};
/// A file download action.
@ -47,22 +46,12 @@ impl<'a> Download<'a> {
let matcher_main = MainMatcher::with(self.cmd_matches).unwrap();
let matcher_download = DownloadMatcher::with(self.cmd_matches).unwrap();
// Create a regular client
let client_config = create_config(&matcher_main);
let client = client_config.clone().client(false);
// Get the share URL, attempt to follow it
// Get the share URL, derive the host
let url = matcher_download.url();
let url = match follow_url(&client, &url) {
Ok(url) => url,
Err(err) => {
print_error(err.context("failed to follow share URL, ignoring").compat());
url
}
};
let host = matcher_download.guess_host();
// Guess the host
let host = matcher_download.guess_host(Some(url.clone()));
// Create a reqwest client capable for downloading files
let client = create_transfer_client(&matcher_main);
// Determine the API version to use
let mut desired_version = matcher_main.api();
@ -139,7 +128,7 @@ impl<'a> Download<'a> {
{
// Allocate an archive file, and update the download and target paths
if extract {
// TODO: select the extension dynamically
// TODO: select the extention dynamically
let archive_extention = ".tar";
// Allocate a temporary file to download the archive to
@ -163,10 +152,7 @@ impl<'a> Download<'a> {
// Create a progress bar reporter
let progress_bar = Arc::new(Mutex::new(ProgressBar::new_download()));
let progress_reader: Arc<Mutex<dyn ProgressReporter>> = progress_bar;
// Create a transfer client
let transfer_client = client_config.client(true);
let progress_reader: Arc<Mutex<ProgressReporter>> = progress_bar;
// Execute an download action
let progress = if !matcher_main.quiet() {
@ -175,7 +161,7 @@ impl<'a> Download<'a> {
None
};
ApiDownload::new(api_version, &file, target, password, false, Some(metadata))
.invoke(&transfer_client, progress)?;
.invoke(&client, progress)?;
// Extract the downloaded file if working with an archive
#[cfg(feature = "archive")]
@ -210,7 +196,7 @@ impl<'a> Download<'a> {
///
/// The full path including the name is returned.
///
/// This method will check whether a file is overwritten, and whether
/// This method will check whether a file is overwitten, and whether
/// parent directories must be created.
///
/// The program will quit with an error message if a problem occurs.
@ -313,7 +299,7 @@ impl<'a> Download<'a> {
// Get the path string
let path = target.to_str();
// If the path is empty, use the working directory with the name hint
// If the path is emtpy, use the working directory with the name hint
let use_workdir = path.map(|path| path.trim().is_empty()).unwrap_or(true);
if use_workdir {
match current_dir() {

View file

@ -2,7 +2,7 @@ use clap::ArgMatches;
use ffsend_api::action::exists::{Error as ExistsError, Exists as ApiExists};
use ffsend_api::file::remote_file::{FileParseError, RemoteFile};
use crate::client::create_config;
use crate::client::create_client;
use crate::cmd::matcher::main::MainMatcher;
use crate::cmd::matcher::{exists::ExistsMatcher, Matcher};
use crate::error::ActionError;
@ -31,8 +31,7 @@ impl<'a> Exists<'a> {
let url = matcher_exists.url();
// Create a reqwest client
let client_config = create_config(&matcher_main);
let client = client_config.client(false);
let client = create_client(&matcher_main);
// Parse the remote file based on the share URL
let file = RemoteFile::parse_url(url, None)?;

View file

@ -1,61 +0,0 @@
use std::fs;
use std::io;
use clap::ArgMatches;
use crate::cmd::matcher::{generate::completions::CompletionsMatcher, main::MainMatcher, Matcher};
use crate::error::ActionError;
/// A file completions action.
pub struct Completions<'a> {
cmd_matches: &'a ArgMatches<'a>,
}
impl<'a> Completions<'a> {
/// Construct a new completions action.
pub fn new(cmd_matches: &'a ArgMatches<'a>) -> Self {
Self { cmd_matches }
}
/// Invoke the completions action.
// TODO: create a trait for this method
pub fn invoke(&self) -> Result<(), ActionError> {
// Create the command matchers
let matcher_main = MainMatcher::with(self.cmd_matches).unwrap();
let matcher_completions = CompletionsMatcher::with(self.cmd_matches).unwrap();
// Obtain shells to generate completions for, build application definition
let shells = matcher_completions.shells();
let dir = matcher_completions.output();
let quiet = matcher_main.quiet();
let mut app = crate::cmd::handler::Handler::build();
// If the directory does not exist yet, attempt to create it
if !dir.is_dir() {
fs::create_dir_all(&dir).map_err(Error::CreateOutputDir)?;
}
// Generate completions
for shell in shells {
if !quiet {
eprint!(
"Generating completions for {}...",
format!("{}", shell).to_lowercase()
);
}
app.gen_completions(crate_name!(), shell, &dir);
if !quiet {
eprintln!(" done.");
}
}
Ok(())
}
}
#[derive(Debug, Fail)]
pub enum Error {
/// An error occurred while creating the output directory.
#[fail(display = "failed to create output directory, it doesn't exist")]
CreateOutputDir(#[cause] io::Error),
}

View file

@ -1,34 +0,0 @@
pub mod completions;
use clap::ArgMatches;
use crate::cmd::matcher::{generate::GenerateMatcher, Matcher};
use crate::error::ActionError;
use completions::Completions;
/// A file generate action.
pub struct Generate<'a> {
cmd_matches: &'a ArgMatches<'a>,
}
impl<'a> Generate<'a> {
/// Construct a new generate action.
pub fn new(cmd_matches: &'a ArgMatches<'a>) -> Self {
Self { cmd_matches }
}
/// Invoke the generate action.
// TODO: create a trait for this method
pub fn invoke(&self) -> Result<(), ActionError> {
// Create the command matcher
let matcher_generate = GenerateMatcher::with(self.cmd_matches).unwrap();
// Match shell completions
if matcher_generate.matcher_completions().is_some() {
return Completions::new(self.cmd_matches).invoke();
}
// Unreachable, clap will print help for missing sub command instead
unreachable!()
}
}

View file

@ -1,11 +1,10 @@
use clap::ArgMatches;
use failure::Fail;
use prettytable::{format::FormatBuilder, Cell, Row, Table};
use crate::cmd::matcher::{history::HistoryMatcher, main::MainMatcher, Matcher};
use crate::cmd::matcher::{main::MainMatcher, Matcher};
use crate::error::ActionError;
use crate::history::{History as HistoryManager, LoadError as HistoryLoadError};
use crate::util::{format_duration, quit_error, quit_error_msg, ErrorHintsBuilder};
use crate::util::format_duration;
/// A history action.
pub struct History<'a> {
@ -23,7 +22,6 @@ impl<'a> History<'a> {
pub fn invoke(&self) -> Result<(), ActionError> {
// Create the command matchers
let matcher_main = MainMatcher::with(self.cmd_matches).unwrap();
let matcher_history = HistoryMatcher::with(self.cmd_matches).unwrap();
// Get the history path, make sure it exists
let history_path = matcher_main.history();
@ -35,7 +33,7 @@ impl<'a> History<'a> {
}
// History
let mut history = HistoryManager::load(history_path)?;
let history = HistoryManager::load(history_path)?;
// Do not report any files if there aren't any
if history.files().is_empty() {
@ -45,49 +43,6 @@ impl<'a> History<'a> {
return Ok(());
}
// Clear all history
if matcher_history.clear() {
history.clear();
// Save history
if let Err(err) = history.save() {
quit_error(
err,
ErrorHintsBuilder::default().verbose(true).build().unwrap(),
);
}
eprintln!("History cleared");
return Ok(());
}
// Remove history item
if let Some(url) = matcher_history.rm() {
// Remove item, print error if no item with URL was found
match history.remove_url(url) {
Ok(removed) if !removed => quit_error_msg(
"could not remove item from history, no item matches given URL",
ErrorHintsBuilder::default().verbose(true).build().unwrap(),
),
Err(err) => quit_error(
err.context("could not remove item from history"),
ErrorHintsBuilder::default().verbose(true).build().unwrap(),
),
_ => {}
}
// Save history
if let Err(err) = history.save() {
quit_error(
err,
ErrorHintsBuilder::default().verbose(true).build().unwrap(),
);
}
eprintln!("Item removed from history");
return Ok(());
}
// Get the list of files, and sort the first expiring files to be last
let mut files = history.files().clone();
files.sort_by(|a, b| b.expire_at().cmp(&a.expire_at()));
@ -95,7 +50,7 @@ impl<'a> History<'a> {
// Log a history table, or just the URLs in quiet mode
if !matcher_main.quiet() {
// Build the list of column names
let mut columns = vec!["#", "LINK", "EXPIRE"];
let mut columns = vec!["#", "LINK", "EXPIRY"];
if matcher_main.verbose() {
columns.push("OWNER TOKEN");
}
@ -120,8 +75,11 @@ impl<'a> History<'a> {
};
// Define the cell values
let mut cells: Vec<String> =
vec![format!("{}", i + 1), file.download_url(true).into(), expiry];
let mut cells: Vec<String> = vec![
format!("{}", i + 1),
file.download_url(true).into_string(),
expiry,
];
if matcher_main.verbose() {
cells.push(owner_token);
}

View file

@ -7,7 +7,7 @@ use ffsend_api::action::metadata::Metadata as ApiMetadata;
use ffsend_api::file::remote_file::{FileParseError, RemoteFile};
use prettytable::{format::FormatBuilder, Cell, Row, Table};
use crate::client::create_config;
use crate::client::create_client;
use crate::cmd::matcher::{info::InfoMatcher, main::MainMatcher, Matcher};
#[cfg(feature = "history")]
use crate::history_tool;
@ -37,8 +37,7 @@ impl<'a> Info<'a> {
let url = matcher_info.url();
// Create a reqwest client
let client_config = create_config(&matcher_main);
let client = client_config.client(false);
let client = create_client(&matcher_main);
// Parse the remote file based on the share URL, derive the owner token from history
let mut file = RemoteFile::parse_url(url, matcher_info.owner())?;

View file

@ -2,7 +2,6 @@ pub mod debug;
pub mod delete;
pub mod download;
pub mod exists;
pub mod generate;
#[cfg(feature = "history")]
pub mod history;
pub mod info;
@ -13,9 +12,9 @@ pub mod version;
use ffsend_api::action::version::{Error as VersionError, Version as ApiVersion};
use ffsend_api::api::DesiredVersion;
use ffsend_api::client::Client;
use ffsend_api::url::Url;
use crate::client::Client;
use crate::config::API_VERSION_ASSUME;
use crate::util::print_warning;
@ -50,7 +49,7 @@ fn select_api_version(
));
}
// Propagate other errors
// Propegate other errors
Err(e) => return Err(e),
}

View file

@ -2,8 +2,7 @@ use clap::ArgMatches;
use ffsend_api::action::params::{Error as ParamsError, Params as ApiParams, ParamsDataBuilder};
use ffsend_api::file::remote_file::RemoteFile;
use super::select_api_version;
use crate::client::create_config;
use crate::client::create_client;
use crate::cmd::matcher::{main::MainMatcher, params::ParamsMatcher, Matcher};
use crate::error::ActionError;
#[cfg(feature = "history")]
@ -28,22 +27,11 @@ impl<'a> Params<'a> {
let matcher_main = MainMatcher::with(self.cmd_matches).unwrap();
let matcher_params = ParamsMatcher::with(self.cmd_matches).unwrap();
// Get the share URL and the host
// TODO: derive host through helper function
// Get the share URL
let url = matcher_params.url();
let mut host = url.clone();
host.set_path("");
host.set_query(None);
host.set_fragment(None);
// Create a reqwest client
let client_config = create_config(&matcher_main);
let client = client_config.client(false);
// Determine the API version to use
let mut desired_version = matcher_main.api();
select_api_version(&client, host, &mut desired_version)?;
let api_version = desired_version.version().unwrap();
let client = create_client(&matcher_main);
// Parse the remote file based on the share URL, derive the owner token from history
let mut file = RemoteFile::parse_url(url, matcher_params.owner())?;
@ -53,16 +41,9 @@ impl<'a> Params<'a> {
// Ensure the owner token is set
ensure_owner_token(file.owner_token_mut(), &matcher_main, false);
// We don't authenticate for now
let auth = false;
// Build the parameters data object
let data = ParamsDataBuilder::default()
.download_limit(
matcher_params
.download_limit(&matcher_main, api_version, auth)
.map(|d| d as u8),
)
.download_limit(matcher_params.download_limit())
.build()
.unwrap();

View file

@ -3,7 +3,7 @@ use ffsend_api::action::password::{Error as PasswordError, Password as ApiPasswo
use ffsend_api::file::remote_file::RemoteFile;
use prettytable::{format::FormatBuilder, Cell, Row, Table};
use crate::client::create_config;
use crate::client::create_client;
use crate::cmd::matcher::{main::MainMatcher, password::PasswordMatcher, Matcher};
use crate::error::ActionError;
#[cfg(feature = "history")]
@ -32,8 +32,7 @@ impl<'a> Password<'a> {
let url = matcher_password.url();
// Create a reqwest client
let client_config = create_config(&matcher_main);
let client = client_config.client(false);
let client = create_client(&matcher_main);
// Parse the remote file based on the share URL, derive the owner token from history
let mut file = RemoteFile::parse_url(url, matcher_password.owner())?;

View file

@ -1,11 +1,7 @@
use std::env::current_dir;
use std::fs;
use std::io::{Error as IoError, Write};
use std::fs::File;
#[cfg(feature = "archive")]
use std::io::Error as IoError;
use std::path::Path;
#[cfg(feature = "archive")]
use std::path::PathBuf;
#[cfg(feature = "archive")]
use std::process::exit;
use std::sync::{Arc, Mutex};
use clap::ArgMatches;
@ -15,27 +11,23 @@ use ffsend_api::action::upload::{Error as UploadError, Upload as ApiUpload};
use ffsend_api::action::version::Error as VersionError;
use ffsend_api::config::{upload_size_max, UPLOAD_SIZE_MAX_RECOMMENDED};
use ffsend_api::pipe::ProgressReporter;
use pathdiff::diff_paths;
use prettytable::{format::FormatBuilder, Cell, Row, Table};
#[cfg(feature = "qrcode")]
use qr2term::print_qr;
#[cfg(feature = "archive")]
use tempfile::{Builder as TempBuilder, NamedTempFile};
use super::select_api_version;
#[cfg(feature = "archive")]
use crate::archive::archiver::Archiver;
use crate::client::create_config;
use crate::client::create_transfer_client;
use crate::cmd::matcher::{MainMatcher, Matcher, UploadMatcher};
#[cfg(feature = "history")]
use crate::history_tool;
use crate::progress::ProgressBar;
#[cfg(feature = "urlshorten")]
use crate::urlshorten;
#[cfg(feature = "clipboard")]
use crate::util::set_clipboard;
use crate::util::{
format_bytes, open_url, print_error, print_error_msg, prompt_yes, quit, quit_error_msg,
rand_alphanum_string, stdin_read_file, ErrorHintsBuilder, StdinErr,
ErrorHintsBuilder,
};
/// A file upload action.
@ -56,60 +48,88 @@ impl<'a> Upload<'a> {
let matcher_main = MainMatcher::with(self.cmd_matches).unwrap();
let matcher_upload = UploadMatcher::with(self.cmd_matches).unwrap();
// Get API parameters
#[allow(unused_mut)]
let mut path = Path::new(matcher_upload.file()).to_path_buf();
let host = matcher_upload.host();
// Create a reqwest client capable for uploading files
let client = create_transfer_client(&matcher_main);
// Determine the API version to use
let mut desired_version = matcher_main.api();
select_api_version(&client, host.clone(), &mut desired_version)?;
let api_version = desired_version.version().unwrap();
// TODO: ensure the file exists and is accessible
// Determine the max file size
// TODO: set false parameter to authentication state
let max_size = upload_size_max(api_version, false);
// Get the file size to warn about large files
if let Ok(size) = File::open(&path)
.and_then(|f| f.metadata())
.map(|m| m.len())
{
if size > max_size && !matcher_main.force() {
// The file is too large, show an error and quit
quit_error_msg(
format!(
"the file size is {}, bigger than the maximum allowed of {}",
format_bytes(size),
format_bytes(max_size),
),
ErrorHintsBuilder::default()
.force(true)
.verbose(false)
.build()
.unwrap(),
);
} else if size > UPLOAD_SIZE_MAX_RECOMMENDED && !matcher_main.force() {
// The file is larger than the recommended maximum, warn
eprintln!(
"The file size is {}, bigger than the recommended maximum of {}",
format_bytes(size),
format_bytes(UPLOAD_SIZE_MAX_RECOMMENDED),
);
// Prompt the user to continue, quit if the user answered no
if !prompt_yes("Continue uploading?", Some(true), &matcher_main) {
println!("Upload cancelled");
quit();
}
}
} else {
print_error_msg("failed to check the file size, ignoring");
}
// Create a reqwest client capable for uploading files
let transfer_client = create_transfer_client(&matcher_main);
// Create a progress bar reporter
let progress_bar = Arc::new(Mutex::new(ProgressBar::new_upload()));
// Build a parameters object to set for the file
let params = {
// Build the parameters data object
let params = ParamsDataBuilder::default()
.download_limit(matcher_upload.download_limit())
.build()
.unwrap();
// Wrap the data in an option if not empty
if params.is_empty() {
None
} else {
Some(params)
}
};
// The file name to use
#[allow(unused_mut)]
let mut file_name = matcher_upload.name().map(|s| s.to_owned());
// The selected files
let mut files = matcher_upload.files();
// If file is `-`, upload from stdin
// TODO: write stdin directly to file, or directly to upload buffer
let mut tmp_stdin: Option<NamedTempFile> = None;
if files.len() == 1 && files[0] == "-" {
// Obtain data from stdin
let data = stdin_read_file(!matcher_main.quiet()).map_err(Error::Stdin)?;
// Create temporary stdin buffer file
tmp_stdin = Some(
TempBuilder::new()
.prefix(&format!(".{}-stdin-", crate_name!()))
.tempfile()
.map_err(Error::StdinTempFile)?,
);
let file = tmp_stdin.as_ref().unwrap();
// Fill temporary file with data, update list of files we upload, suggest name
file.as_file()
.write_all(&data)
.map_err(Error::StdinTempFile)?;
files = vec![file
.path()
.to_str()
.expect("failed to obtain file name for stdin buffer file")];
file_name = file_name.or_else(|| Some("stdin.txt".into()));
}
// Get API parameters
#[allow(unused_mut)]
let mut paths: Vec<_> = files
.into_iter()
.map(|p| Path::new(p).to_path_buf())
.collect();
let mut path = Path::new(paths.first().unwrap()).to_path_buf();
let host = matcher_upload.host();
// All paths must exist
// TODO: ensure the file exists and is accessible
for path in &paths {
if !path.exists() {
quit_error_msg(
format!("the path '{}' does not exist", path.to_str().unwrap_or("?")),
ErrorHintsBuilder::default().build().unwrap(),
);
}
}
// A temporary archive file, only used when archiving
// The temporary file is stored here, to ensure it's lifetime exceeds the upload process
#[allow(unused_mut)]
@ -118,31 +138,16 @@ impl<'a> Upload<'a> {
#[cfg(feature = "archive")]
{
// Determine whether to archive, we must archive for multiple files/directory
// Determine whether to archive, ask if a directory was selected
let mut archive = matcher_upload.archive();
if !archive {
if paths.len() > 1 {
if prompt_yes(
"You've selected multiple files, only a single file may be uploaded.\n\
Archive the files into a single file?",
Some(true),
&matcher_main,
) {
archive = true;
} else {
exit(1);
}
} else if path.is_dir() {
if prompt_yes(
"You've selected a directory, only a single file may be uploaded.\n\
Archive the directory into a single file?",
Some(true),
&matcher_main,
) {
archive = true;
} else {
exit(1);
}
if !archive && path.is_dir() {
if prompt_yes(
"You've selected a directory, only a single file may be uploaded.\n\
Archive the directory into a single file?",
Some(true),
&matcher_main,
) {
archive = true;
}
}
@ -169,194 +174,37 @@ impl<'a> Upload<'a> {
// Select the file name to use if not set
if file_name.is_none() {
// Derive name from given file
if paths.len() == 1 {
file_name = Some(
path.canonicalize()
.map_err(|err| ArchiveError::FileName(Some(err)))?
.file_name()
.ok_or(ArchiveError::FileName(None))?
.to_str()
.map(|s| s.to_owned())
.ok_or(ArchiveError::FileName(None))?,
);
} else {
// Unable to derive file name from paths, generate random
file_name = Some(format!("ffsend-archive-{}", rand_alphanum_string(8)));
}
file_name = Some(
path.canonicalize()
.map_err(|err| ArchiveError::FileName(Some(err)))?
.file_name()
.ok_or(ArchiveError::FileName(None))?
.to_str()
.map(|s| s.to_owned())
.ok_or(ArchiveError::FileName(None))?,
);
}
// Get the current working directory, including working directory as highest possible root, canonicalize it
let working_dir =
current_dir().expect("failed to get current working directory");
let shared_dir = {
let mut paths = paths.clone();
paths.push(working_dir.clone());
match shared_dir(paths) {
Some(p) => p,
None => quit_error_msg(
"when archiving, all files must be within a same directory",
ErrorHintsBuilder::default().verbose(false).build().unwrap(),
),
}
};
// Build an archiver, append each file
// Build an archiver and append the file
let mut archiver = Archiver::new(archive_file);
for path in &paths {
// Canonicalize the path
let mut path = Path::new(path).to_path_buf();
if let Ok(p) = path.canonicalize() {
path = p;
}
// Find relative name to share dir, used to derive name from
let name = diff_paths(&path, &shared_dir)
.expect("failed to determine relative path of file to archive");
let name = name.to_str().expect("failed to get file path");
// Add file to archiver
archiver
.append_path(name, &path)
.map_err(ArchiveError::AddFile)?;
}
archiver
.append_path(file_name.as_ref().unwrap(), &path)
.map_err(ArchiveError::AddFile)?;
// Finish the archival process, writes the archive file
archiver.finish().map_err(ArchiveError::Write)?;
// Append archive extension to name, set to upload archived file
// Append archive extention to name, set to upload archived file
if let Some(ref mut file_name) = file_name {
file_name.push_str(archive_extention);
}
path = archive_path;
paths.clear();
}
}
}
// Quit with error when uploading multiple files or directory, if we cannot archive
#[cfg(not(feature = "archive"))]
{
if paths.len() > 1 {
quit_error_msg(
"uploading multiple files is not supported, ffsend must be compiled with 'archive' feature for this",
ErrorHintsBuilder::default()
.verbose(false)
.build()
.unwrap(),
);
}
if path.is_dir() {
quit_error_msg(
"uploading a directory is not supported, ffsend must be compiled with 'archive' feature for this",
ErrorHintsBuilder::default()
.verbose(false)
.build()
.unwrap(),
);
}
}
// Create a reqwest client capable for uploading files
let client_config = create_config(&matcher_main);
let client = client_config.clone().client(false);
// Determine the API version to use
let mut desired_version = matcher_main.api();
select_api_version(&client, host.clone(), &mut desired_version)?;
let api_version = desired_version.version().unwrap();
// We do not authenticate for now
let auth = false;
// TODO: extract this into external function
{
// Determine the max file size
// TODO: set false parameter to authentication state
let max_size = upload_size_max(api_version, auth);
// Get the file size, fail on empty files, warn about large files
if let Ok(size) = path.metadata().map(|m| m.len()) {
// Enforce files not being 0 bytes
if size == 0 && !matcher_main.force() {
quit_error_msg(
"uploading a file with a size of 0 bytes is not supported",
ErrorHintsBuilder::default()
.force(true)
.verbose(false)
.build()
.unwrap(),
)
}
// Enforce maximum file size
if size > max_size && !matcher_main.force() {
// The file is too large, show an error and quit
quit_error_msg(
format!(
"the file size is {}, bigger than the maximum allowed of {}",
format_bytes(size),
format_bytes(max_size),
),
ErrorHintsBuilder::default()
.force(true)
.verbose(false)
.build()
.unwrap(),
);
}
// Enforce maximum recommended size
if size > UPLOAD_SIZE_MAX_RECOMMENDED && !matcher_main.force() {
// The file is larger than the recommended maximum, warn
eprintln!(
"The file size is {}, bigger than the recommended maximum of {}",
format_bytes(size),
format_bytes(UPLOAD_SIZE_MAX_RECOMMENDED),
);
// Prompt the user to continue, quit if the user answered no
if !prompt_yes("Continue uploading?", Some(true), &matcher_main) {
eprintln!("Upload cancelled");
quit();
}
}
} else {
print_error_msg("failed to check the file size, ignoring");
}
}
// TODO: assert max expiry time for file
// Create a reqwest client capable for uploading files
let transfer_client = client_config.client(true);
// Create a progress bar reporter
let progress_bar = Arc::new(Mutex::new(ProgressBar::new_upload()));
// Build a parameters object to set for the file
let params = {
// Build the parameters data object
let params = ParamsDataBuilder::default()
.download_limit(
matcher_upload
.download_limit(&matcher_main, api_version, auth)
.map(|d| d as u8),
)
.expiry_time(matcher_upload.expiry_time(&matcher_main, api_version, auth))
.build()
.unwrap();
// Wrap the data in an option if not empty
if params.is_empty() {
None
} else {
Some(params)
}
};
// Build the progress reporter
let progress_reporter: Arc<Mutex<dyn ProgressReporter>> = progress_bar;
let progress_reporter: Arc<Mutex<ProgressReporter>> = progress_bar;
// Get the password to use and whether it was generated
let password = matcher_upload.password();
@ -378,72 +226,29 @@ impl<'a> Upload<'a> {
params,
)
.invoke(&transfer_client, reporter)?;
#[allow(unused_mut)]
let mut url = file.download_url(true);
// Shorten the share URL if requested, prompt the user to confirm
#[cfg(feature = "urlshorten")]
{
if matcher_upload.shorten() {
if prompt_yes("URL shortening is a security risk. This shares the secret URL with a 3rd party.\nDo you want to shorten the share URL?", Some(false), &matcher_main) {
match urlshorten::shorten_url(&client, &url) {
Ok(short) => url = short,
Err(err) => print_error(
err.context("failed to shorten share URL, ignoring")
.compat(),
),
}
}
}
}
let url = file.download_url(true);
// Report the result
if !matcher_main.quiet() {
// Create a table
// Show a table
let mut table = Table::new();
table.set_format(FormatBuilder::new().padding(0, 2).build());
// Show the original URL when shortening, verbose and different
#[cfg(feature = "urlshorten")]
{
let full_url = file.download_url(true);
if matcher_main.verbose() && matcher_upload.shorten() && url != full_url {
table.add_row(Row::new(vec![
Cell::new("Full share link:"),
Cell::new(full_url.as_str()),
]));
}
}
if matcher_main.verbose() {
// Show the share URL
table.add_row(Row::new(vec![
Cell::new("Share link:"),
Cell::new(url.as_str()),
]));
if password_generated {
table.add_row(Row::new(vec![
Cell::new("Share link:"),
Cell::new(url.as_str()),
Cell::new("Passphrase:"),
Cell::new(&password.unwrap_or("?".into())),
]));
// Show a generate passphrase
if password_generated {
table.add_row(Row::new(vec![
Cell::new("Passphrase:"),
Cell::new(&password.unwrap_or("?".into())),
]));
}
// Show the owner token
}
if matcher_main.verbose() {
table.add_row(Row::new(vec![
Cell::new("Owner token:"),
Cell::new(file.owner_token().unwrap()),
]));
} else {
table.add_row(Row::new(vec![Cell::new(url.as_str())]));
// Show a generate passphrase
if password_generated {
table.add_row(Row::new(vec![Cell::new(&password.unwrap_or("?".into()))]));
}
}
table.printstd();
} else {
println!("{}", url);
@ -472,26 +277,6 @@ impl<'a> Upload<'a> {
}
}
// Print a QR code for the share URL
#[cfg(feature = "qrcode")]
{
if matcher_upload.qrcode() {
if let Err(err) = print_qr(url.as_str()) {
print_error(err.context("failed to print QR code, ignoring").compat());
}
}
}
// Close the temporary stdin buffer file, to ensure it's removed
if let Some(tmp_stdin) = tmp_stdin.take() {
if let Err(err) = tmp_stdin.close() {
print_error(
err.context("failed to clean up temporary stdin buffer file, ignoring")
.compat(),
);
}
}
#[cfg(feature = "archive")]
{
// Close the temporary zip file, to ensure it's removed
@ -505,101 +290,10 @@ impl<'a> Upload<'a> {
}
}
// Delete local files after uploading
if matcher_upload.delete() {
for path in &paths {
if path.is_file() {
if let Err(err) = fs::remove_file(path) {
print_error(
Error::Delete(err)
.context("failed to delete local file after upload, ignoring")
.compat(),
);
}
} else {
if let Err(err) = fs::remove_dir_all(path) {
print_error(
Error::Delete(err)
.context("failed to delete local directory after upload, ignoring")
.compat(),
);
}
}
}
}
Ok(())
}
}
/// Find the deepest directory all given paths share.
///
/// This function canonicalizes the paths, make sure the paths exist.
///
/// Returns `None` if paths are using a different root.
///
/// # Examples
///
/// If the following paths are given:
///
/// - `/home/user/git/ffsend/src`
/// - `/home/user/git/ffsend/src/main.rs`
/// - `/home/user/git/ffsend/Cargo.toml`
///
/// The following is returned:
///
/// `/home/user/git/ffsend`
#[cfg(feature = "archive")]
fn shared_dir(paths: Vec<PathBuf>) -> Option<PathBuf> {
// Any path must be given
if paths.is_empty() {
return None;
}
// Build vector
let c: Vec<Vec<PathBuf>> = paths
.into_iter()
.map(|p| p.canonicalize().expect("failed to canonicalize path"))
.map(|mut p| {
// Start with parent if current path is file
if p.is_file() {
p = match p.parent() {
Some(p) => p.to_path_buf(),
None => return vec![],
};
}
// Build list of path buffers for each path component
let mut items = vec![p];
#[allow(mutable_borrow_reservation_conflict)]
while let Some(item) = items.last().unwrap().parent() {
items.push(item.to_path_buf());
}
// Reverse as we built it in the wrong order
items.reverse();
items
})
.collect();
// Find the index at which the paths are last shared at by walking through indices
let i = (0..)
.take_while(|i| {
// Get path for first item, stop if none
let base = &c[0].get(*i);
if base.is_none() {
return false;
};
// All other paths must equal at this index
c.iter().skip(1).all(|p| &p.get(*i) == base)
})
.last();
// Find the shared path
i.map(|i| c[0][i].to_path_buf())
}
#[derive(Debug, Fail)]
pub enum Error {
/// Selecting the API version to use failed.
@ -615,18 +309,6 @@ pub enum Error {
/// An error occurred while uploading the file.
#[fail(display = "")]
Upload(#[cause] UploadError),
/// An error occurred while deleting a local file after upload.
#[fail(display = "failed to delete local file")]
Delete(#[cause] IoError),
/// An error occurred while reading data from stdin.
#[fail(display = "failed to read data from stdin")]
Stdin(#[cause] StdinErr),
/// An error occurred while creating the temporary stdin file.
#[fail(display = "failed to create temporary stdin buffer file")]
StdinTempFile(#[cause] IoError),
}
impl From<VersionError> for Error {

View file

@ -1,7 +1,7 @@
use clap::ArgMatches;
use ffsend_api::action::version::{Error as VersionError, Version as ApiVersion};
use crate::client::create_config;
use crate::client::create_client;
use crate::cmd::matcher::main::MainMatcher;
use crate::cmd::matcher::{version::VersionMatcher, Matcher};
use crate::error::ActionError;
@ -28,8 +28,7 @@ impl<'a> Version<'a> {
let host = matcher_version.host();
// Create a reqwest client
let client_config = create_config(&matcher_main);
let client = client_config.client(false);
let client = create_client(&matcher_main);
// Make sure the file version
let response = ApiVersion::new(host).invoke(&client);

View file

@ -1,7 +1,7 @@
use std::io::{Error as IoError, Read};
use std::path::Path;
use tar::Archive as TarArchive;
use super::tar::Archive as TarArchive;
pub type Result<T> = ::std::result::Result<T, IoError>;

View file

@ -2,7 +2,7 @@ use std::fs::File;
use std::io::{Error as IoError, Write};
use std::path::Path;
use tar::Builder as TarBuilder;
use super::tar::Builder as TarBuilder;
pub type Result<T> = ::std::result::Result<T, IoError>;

View file

@ -1,2 +1,4 @@
extern crate tar;
pub mod archive;
pub mod archiver;

View file

@ -1,22 +1,30 @@
use std::time::Duration;
use ffsend_api::client::{ClientConfig, ClientConfigBuilder};
pub use ffsend_api::reqwest::Client;
use ffsend_api::reqwest::ClientBuilder;
use crate::cmd::matcher::MainMatcher;
/// Create a client configuration for ffsend actions.
/// Create the default client, which is used for generic Send requests.
///
/// A client configuration allows you to build a client, which must be passed to ffsend API
/// actions.
// TODO: properly handle errors, do not unwrap
pub fn create_config(matcher_main: &MainMatcher) -> ClientConfig {
// TODO: configure HTTP authentication properties
ClientConfigBuilder::default()
.timeout(to_duration(matcher_main.timeout()))
.transfer_timeout(to_duration(matcher_main.transfer_timeout()))
.basic_auth(matcher_main.basic_auth())
/// Note: use `create_transfer_client()` instead for clients that upload/download.
pub fn create_client(matcher_main: &MainMatcher) -> Client {
create_custom_client(to_duration(matcher_main.timeout()))
}
/// Create the default client, which is used for generic Send requests.
///
/// Note: use `create_transfer_client()` instead for clients that upload/download.
pub fn create_transfer_client(matcher_main: &MainMatcher) -> Client {
create_custom_client(to_duration(matcher_main.transfer_timeout()))
}
/// Create the Send client with a custom timeout.
fn create_custom_client(timeout: Option<Duration>) -> Client {
ClientBuilder::new()
.timeout(timeout)
.build()
.expect("failed to create network client configuration")
.expect("failed to build custom reqwest client")
}
/// Convert the given number of seconds into an optional duration, used for clients.

View file

@ -24,7 +24,7 @@ impl CmdArg for ArgApi {
.help("Server API version to use, '-' to lookup")
.long_help(
"Server API version to use, one of:\n\
2, 3: Send API versions\n\
2, 3: Firefox Send API versions\n\
auto, -: probe server to determine\
",
)

View file

@ -1,44 +0,0 @@
use clap::{Arg, ArgMatches};
use super::{CmdArg, CmdArgOption};
/// The basicauth argument.
pub struct ArgBasicAuth {}
impl CmdArg for ArgBasicAuth {
fn name() -> &'static str {
"basic-auth"
}
fn build<'b, 'c>() -> Arg<'b, 'c> {
Arg::with_name("basic-auth")
.long("basic-auth")
.alias("basic-authentication")
.alias("http-basic-authentication")
.alias("http-basic-auth")
.value_name("USER:PASSWORD")
.env("FFSEND_BASIC_AUTH")
.hide_env_values(true)
.global(true)
.help("Protected proxy HTTP basic authentication credentials (not FxA)")
}
}
impl<'a> CmdArgOption<'a> for ArgBasicAuth {
type Value = Option<(String, Option<String>)>;
fn value<'b: 'a>(matches: &'a ArgMatches<'b>) -> Self::Value {
// Get the authentication credentials
let raw = match Self::value_raw(matches) {
Some(raw) => raw,
None => return None,
};
// Split the properties
let mut iter = raw.splitn(2, ':');
Some((
iter.next().unwrap_or("").to_owned(),
iter.next().map(|pass| pass.to_owned()),
))
}
}

View file

@ -1,61 +1,15 @@
use clap::{Arg, ArgMatches};
use ffsend_api::api::Version as ApiVersion;
use ffsend_api::config::downloads_max;
use ffsend_api::action::params::{
PARAMS_DOWNLOAD_MAX as DOWNLOAD_MAX, PARAMS_DOWNLOAD_MIN as DOWNLOAD_MIN,
};
use super::{CmdArg, CmdArgFlag, CmdArgOption};
use crate::cmd::matcher::MainMatcher;
use crate::util::{highlight, prompt_yes, quit};
use crate::util::{quit_error_msg, ErrorHintsBuilder};
/// The download limit argument.
pub struct ArgDownloadLimit {}
impl ArgDownloadLimit {
pub fn value_checked<'a>(
matches: &ArgMatches<'a>,
main_matcher: &MainMatcher,
api_version: ApiVersion,
auth: bool,
) -> Option<usize> {
// Get the download value
let mut downloads = Self::value(matches)?;
// Get number of allowed downloads, return if allowed or when forcing
let allowed = downloads_max(api_version, auth);
if allowed.contains(&downloads) || main_matcher.force() {
return Some(downloads);
}
// Prompt the user the specified downloads limit is invalid
let allowed_str = allowed
.iter()
.map(|value| format!("{}", value))
.collect::<Vec<_>>()
.join(", ");
eprintln!("The downloads limit must be one of: {}", allowed_str);
if auth {
eprintln!("Use '{}' to force", highlight("--force"));
} else {
eprintln!(
"Use '{}' to force, authenticate for higher limits",
highlight("--force")
);
}
// Ask to use closest limit, quit if user cancelled
let closest = closest(allowed, downloads);
if !prompt_yes(
&format!("Would you like to limit downloads to {} instead?", closest),
None,
main_matcher,
) {
quit();
}
downloads = closest;
Some(downloads)
}
}
impl CmdArg for ArgDownloadLimit {
fn name() -> &'static str {
"download-limit"
@ -68,7 +22,6 @@ impl CmdArg for ArgDownloadLimit {
.alias("downloads")
.alias("download")
.value_name("COUNT")
.env("FFSEND_DOWNLOAD_LIMIT")
.help("The file download limit")
}
}
@ -76,26 +29,30 @@ impl CmdArg for ArgDownloadLimit {
impl CmdArgFlag for ArgDownloadLimit {}
impl<'a> CmdArgOption<'a> for ArgDownloadLimit {
type Value = Option<usize>;
type Value = Option<u8>;
fn value<'b: 'a>(matches: &'a ArgMatches<'b>) -> Self::Value {
// TODO: do not unwrap, report an error
Self::value_raw(matches).map(|d| d.parse::<usize>().expect("invalid download limit"))
Self::value_raw(matches)
.map(|d| d.parse::<u8>().expect("invalid download limit"))
.and_then(|d| {
// Check the download limit bounds
// TODO: somehow allow to force a different number here
if d < DOWNLOAD_MIN || d > DOWNLOAD_MAX {
quit_error_msg(
format!(
"invalid download limit, must be between {} and {}",
DOWNLOAD_MIN, DOWNLOAD_MAX,
),
ErrorHintsBuilder::default()
.force(false)
.verbose(false)
.build()
.unwrap(),
);
}
Some(d)
})
}
}
/// Find the closest value to `current` in the given `values` range.
fn closest(values: &[usize], current: usize) -> usize {
// Own the values, sort and reverse, start with biggest first
let mut values = values.to_vec();
values.sort_unstable();
// Find the closest value, return it
*values
.iter()
.rev()
.map(|value| (value, (current as i64 - *value as i64).abs()))
.min_by_key(|value| value.1)
.expect("failed to find closest value, none given")
.0
}

View file

@ -1,97 +0,0 @@
use chrono::Duration;
use clap::{Arg, ArgMatches};
use failure::Fail;
use ffsend_api::api::Version as ApiVersion;
use ffsend_api::config::expiry_max;
use super::{CmdArg, CmdArgFlag, CmdArgOption};
use crate::cmd::matcher::MainMatcher;
use crate::util::{
format_duration, highlight, parse_duration, prompt_yes, quit, quit_error, ErrorHints,
};
/// The download limit argument.
pub struct ArgExpiryTime {}
impl ArgExpiryTime {
pub fn value_checked<'a>(
matches: &ArgMatches<'a>,
main_matcher: &MainMatcher,
api_version: ApiVersion,
auth: bool,
) -> Option<usize> {
// Get the expiry time value
let mut expiry = Self::value(matches)?;
// Get expiry time, return if allowed or when forcing
let max = *expiry_max(api_version, auth).into_iter().max().unwrap();
if expiry <= max || main_matcher.force() {
return Some(expiry);
}
// Define function to format seconds
let format_secs = |secs: usize| format_duration(Duration::seconds(secs as i64));
// Prompt the user the specified expiry time is invalid
eprintln!(
"The expiry time must equal to or less than: {}",
format_secs(max),
);
if auth {
eprintln!("Use '{}' to force", highlight("--force"));
} else {
eprintln!(
"Use '{}' to force, authenticate for higher limits",
highlight("--force")
);
}
// Ask to use maximum expiry time instead, quit if user cancelled
if !prompt_yes(
&format!(
"Would you like to set expiry time to {} instead?",
format_secs(max),
),
None,
main_matcher,
) {
quit();
}
expiry = max;
Some(expiry)
}
}
impl CmdArg for ArgExpiryTime {
fn name() -> &'static str {
"expiry-time"
}
fn build<'b, 'c>() -> Arg<'b, 'c> {
Arg::with_name("expiry-time")
.long("expiry-time")
.short("e")
.alias("expire")
.alias("expiry")
.value_name("TIME")
.env("FFSEND_EXPIRY_TIME")
.help("The file expiry time")
}
}
impl CmdArgFlag for ArgExpiryTime {}
impl<'a> CmdArgOption<'a> for ArgExpiryTime {
type Value = Option<usize>;
fn value<'b: 'a>(matches: &'a ArgMatches<'b>) -> Self::Value {
Self::value_raw(matches).map(|t| match parse_duration(t) {
Ok(seconds) => seconds,
Err(err) => quit_error(
err.context("specified invalid file expiry time"),
ErrorHints::default(),
),
})
}
}

View file

@ -1,16 +1,26 @@
use chbs;
use chbs::{
config::BasicConfig,
prelude::*,
word::WordList,
};
use clap::Arg;
use super::{CmdArg, CmdArgFlag};
/// How many words the passphrase should consist of.
const PASSPHRASE_WORDS: usize = 5;
/// The passphrase generation argument.
pub struct ArgGenPassphrase {}
impl ArgGenPassphrase {
/// Generate a cryptographically secure passphrase that is easily
/// remembered using diceware.
/// rememberable using diceware.
pub fn gen_passphrase() -> String {
chbs::passphrase()
let mut config = BasicConfig::default();
config.words = PASSPHRASE_WORDS;
config.word_provider = WordList::builtin_eff_general_short().sampler();
config.to_scheme().generate()
}
}
@ -26,7 +36,6 @@ impl CmdArg for ArgGenPassphrase {
.alias("generate-passphrase")
.alias("generate-password")
.short("P")
.conflicts_with("password")
.help("Protect the file with a generated passphrase")
}
}

View file

@ -1,5 +1,6 @@
use clap::{Arg, ArgMatches};
use failure::Fail;
use ffsend_api::config::SEND_DEFAULT_HOST;
use ffsend_api::url::Url;
use super::{CmdArg, CmdArgOption};
@ -19,7 +20,7 @@ impl CmdArg for ArgHost {
.long("host")
.short("h")
.value_name("URL")
.default_value("https://send.vis.ee/")
.default_value(SEND_DEFAULT_HOST)
.env("FFSEND_HOST")
.hide_env_values(true)
.help("The remote host to upload to")

View file

@ -1,18 +1,14 @@
pub mod api;
pub mod basic_auth;
pub mod download_limit;
pub mod expiry_time;
pub mod gen_passphrase;
pub mod host;
pub mod owner;
pub mod password;
pub mod url;
// Re-export to arg module
// Re-eexport to arg module
pub use self::api::ArgApi;
pub use self::basic_auth::ArgBasicAuth;
pub use self::download_limit::ArgDownloadLimit;
pub use self::expiry_time::ArgExpiryTime;
pub use self::gen_passphrase::ArgGenPassphrase;
pub use self::host::ArgHost;
pub use self::owner::ArgOwner;

View file

@ -1,29 +1,23 @@
#[cfg(feature = "infer-command")]
use std::ffi::OsString;
extern crate directories;
use clap::{App, AppSettings, Arg, ArgMatches};
use super::arg::{ArgApi, ArgBasicAuth, CmdArg};
use super::arg::{ArgApi, CmdArg};
#[cfg(feature = "history")]
use super::matcher::HistoryMatcher;
use super::matcher::{
DebugMatcher, DeleteMatcher, DownloadMatcher, ExistsMatcher, GenerateMatcher, InfoMatcher,
Matcher, ParamsMatcher, PasswordMatcher, UploadMatcher, VersionMatcher,
DebugMatcher, DeleteMatcher, DownloadMatcher, ExistsMatcher, InfoMatcher, Matcher,
ParamsMatcher, PasswordMatcher, UploadMatcher, VersionMatcher,
};
#[cfg(feature = "history")]
use super::subcmd::CmdHistory;
use super::subcmd::{
CmdDebug, CmdDelete, CmdDownload, CmdExists, CmdGenerate, CmdInfo, CmdParams, CmdPassword,
CmdUpload, CmdVersion,
CmdDebug, CmdDelete, CmdDownload, CmdExists, CmdInfo, CmdParams, CmdPassword, CmdUpload,
CmdVersion,
};
#[cfg(feature = "infer-command")]
use crate::config::INFER_COMMANDS;
use crate::config::{CLIENT_TIMEOUT, CLIENT_TRANSFER_TIMEOUT};
#[cfg(feature = "history")]
use crate::util::app_history_file_path_string;
#[cfg(feature = "infer-command")]
use crate::util::bin_name;
use crate::util::parse_duration;
#[cfg(feature = "history")]
lazy_static! {
@ -37,15 +31,6 @@ lazy_static! {
/// The default client transfer timeout in seconds as a string
static ref DEFAULT_TRANSFER_TIMEOUT: String = format!("{}", CLIENT_TRANSFER_TIMEOUT);
/// The about notice in command output.
static ref APP_ABOUT: String = format!(
"{}\n\n\
The default public Send host is provided by Tim Visee, @timvisee.\n\
Please consider to donate and help keep it running: https://vis.ee/donate\
",
crate_description!(),
);
}
/// CLI argument handler.
@ -61,8 +46,13 @@ impl<'a: 'b, 'b> Handler<'a> {
let app = App::new(crate_name!())
.version(crate_version!())
.author(crate_authors!())
.about(APP_ABOUT.as_ref())
.after_help("This application is not affiliated with Firefox or Mozilla.")
.about(crate_description!())
.after_help(
"\
The public Send service that is used as default host is provided by Mozilla.\n\
This application is not affiliated with Mozilla, Firefox or Firefox Send.\
",
)
.global_setting(AppSettings::GlobalVersion)
.global_setting(AppSettings::VersionlessSubcommands)
// TODO: enable below command when it doesn't break `p` anymore.
@ -103,13 +93,13 @@ impl<'a: 'b, 'b> Handler<'a> {
.hide_default_value(true)
.env("FFSEND_TIMEOUT")
.hide_env_values(true)
.validator(|arg| {
parse_duration(&arg).map(drop).map_err(|_| {
String::from(
"Timeout time must be a positive number of seconds, or 0 to disable."
)
})
}),
.validator(|arg| arg
.parse::<u64>()
.map(|_| ())
.map_err(|_| String::from(
"Timeout time must be a positive number of seconds, or 0 to disable."
))
),
)
.arg(
Arg::with_name("transfer-timeout")
@ -128,13 +118,13 @@ impl<'a: 'b, 'b> Handler<'a> {
.hide_default_value(true)
.env("FFSEND_TRANSFER_TIMEOUT")
.hide_env_values(true)
.validator(|arg| {
parse_duration(&arg).map(drop).map_err(|_| {
String::from(
"Timeout time must be a positive number of seconds, or 0 to disable."
)
})
}),
.validator(|arg| arg
.parse::<u64>()
.map(|_| ())
.map_err(|_| String::from(
"Timeout time must be a positive number of seconds, or 0 to disable."
))
),
)
.arg(
Arg::with_name("quiet")
@ -152,12 +142,10 @@ impl<'a: 'b, 'b> Handler<'a> {
.help("Enable verbose information and logging"),
)
.arg(ArgApi::build())
.arg(ArgBasicAuth::build())
.subcommand(CmdDebug::build())
.subcommand(CmdDelete::build())
.subcommand(CmdDownload::build().display_order(2))
.subcommand(CmdExists::build())
.subcommand(CmdGenerate::build())
.subcommand(CmdInfo::build())
.subcommand(CmdParams::build())
.subcommand(CmdPassword::build())
@ -200,40 +188,9 @@ impl<'a: 'b, 'b> Handler<'a> {
/// Parse CLI arguments.
pub fn parse() -> Handler<'a> {
// Obtain the program arguments
#[allow(unused_mut)]
let mut args: Vec<_> = ::std::env::args_os().collect();
// Infer subcommand based on binary name
#[cfg(feature = "infer-command")]
Self::infer_subcommand(&mut args);
// Build the application CLI definition, get the matches
Handler {
matches: Handler::build().get_matches_from(args),
}
}
/// Infer subcommand when the binary has a predefined name,
/// modifying the given program arguments.
///
/// If no subcommand could be inferred, the `args` list leaves unchanged.
/// See `crate::config::INFER_COMMANDS` for a list of commands.
///
/// When the `ffsend` binary is called with such a name, the corresponding subcommand is
/// automatically inserted as argument. This also works when calling binaries through symbolic
/// or hard links.
#[cfg(feature = "infer-command")]
fn infer_subcommand(args: &mut Vec<OsString>) {
// Get the name of the called binary
let name = bin_name();
// Infer subcommands
for (bin, subcmd) in INFER_COMMANDS.iter() {
if &name == bin {
args.insert(1, subcmd.into());
break;
}
matches: Handler::build().get_matches(),
}
}
@ -262,11 +219,6 @@ impl<'a: 'b, 'b> Handler<'a> {
ExistsMatcher::with(&self.matches)
}
/// Get the generate sub command, if matched.
pub fn generate(&'a self) -> Option<GenerateMatcher> {
GenerateMatcher::with(&self.matches)
}
/// Get the history sub command, if matched.
#[cfg(feature = "history")]
pub fn history(&'a self) -> Option<HistoryMatcher> {

View file

@ -26,8 +26,8 @@ impl<'a: 'b, 'b> DownloadMatcher<'a> {
/// Guess the file share host, based on the file share URL.
///
/// See `Self::url`.
pub fn guess_host(&'a self, url: Option<Url>) -> Url {
let mut url = url.unwrap_or(self.url());
pub fn guess_host(&'a self) -> Url {
let mut url = self.url();
url.set_path("");
url.set_query(None);
url.set_fragment(None);

View file

@ -1,64 +0,0 @@
use std::path::PathBuf;
use std::str::FromStr;
use clap::{ArgMatches, Shell};
use super::Matcher;
/// The completions completions command matcher.
pub struct CompletionsMatcher<'a> {
matches: &'a ArgMatches<'a>,
}
impl<'a: 'b, 'b> CompletionsMatcher<'a> {
/// Get the shells to generate completions for.
pub fn shells(&'a self) -> Vec<Shell> {
// Get the raw list of shells
let raw = self
.matches
.values_of("SHELL")
.expect("no shells were given");
// Parse the list of shell names, deduplicate
let mut shells: Vec<_> = raw
.into_iter()
.map(|name| name.trim().to_lowercase())
.map(|name| {
if name == "all" {
Shell::variants()
.iter()
.map(|name| name.to_string())
.collect()
} else {
vec![name]
}
})
.flatten()
.collect();
shells.sort_unstable();
shells.dedup();
// Parse the shell names
shells
.into_iter()
.map(|name| Shell::from_str(&name).expect("failed to parse shell name"))
.collect()
}
/// The target directory to output the shell completion files to.
pub fn output(&'a self) -> PathBuf {
self.matches
.value_of("output")
.map(PathBuf::from)
.unwrap_or_else(|| PathBuf::from("./"))
}
}
impl<'a> Matcher<'a> for CompletionsMatcher<'a> {
fn with(matches: &'a ArgMatches) -> Option<Self> {
matches
.subcommand_matches("generate")?
.subcommand_matches("completions")
.map(|matches| CompletionsMatcher { matches })
}
}

View file

@ -1,29 +0,0 @@
pub mod completions;
use clap::ArgMatches;
use super::Matcher;
use completions::CompletionsMatcher;
/// The generate command matcher.
pub struct GenerateMatcher<'a> {
root: &'a ArgMatches<'a>,
_matches: &'a ArgMatches<'a>,
}
impl<'a: 'b, 'b> GenerateMatcher<'a> {
/// Get the generate completions sub command, if matched.
pub fn matcher_completions(&'a self) -> Option<CompletionsMatcher> {
CompletionsMatcher::with(&self.root)
}
}
impl<'a> Matcher<'a> for GenerateMatcher<'a> {
fn with(root: &'a ArgMatches) -> Option<Self> {
root.subcommand_matches("generate")
.map(|matches| GenerateMatcher {
root,
_matches: matches,
})
}
}

View file

@ -1,10 +1,6 @@
use clap::ArgMatches;
use failure::Fail;
use ffsend_api::url::Url;
use super::Matcher;
use crate::host::parse_host;
use crate::util::{quit_error, ErrorHints};
/// The history command matcher.
pub struct HistoryMatcher<'a> {
@ -12,32 +8,6 @@ pub struct HistoryMatcher<'a> {
matches: &'a ArgMatches<'a>,
}
impl<'a> HistoryMatcher<'a> {
/// Check whether to clear all history.
pub fn clear(&self) -> bool {
self.matches.is_present("clear")
}
/// Check whether to remove a given entry from the history.
///
/// This method parses the URL into an `Url`.
/// If the given URL is invalid,
/// the program will quit with an error message.
pub fn rm(&'a self) -> Option<Url> {
// Get the URL
let url = self.matches.value_of("rm")?;
// Parse the URL
match parse_host(&url) {
Ok(url) => Some(url),
Err(err) => quit_error(
err.context("failed to parse the given share URL"),
ErrorHints::default(),
),
}
}
}
impl<'a> Matcher<'a> for HistoryMatcher<'a> {
fn with(matches: &'a ArgMatches) -> Option<Self> {
matches

View file

@ -5,8 +5,8 @@ use clap::ArgMatches;
use ffsend_api::api::DesiredVersion;
use super::Matcher;
use crate::cmd::arg::{ArgApi, ArgBasicAuth, CmdArgOption};
use crate::util::{env_var_present, parse_duration};
use crate::cmd::arg::{ArgApi, CmdArgOption};
use crate::util::env_var_present;
#[cfg(feature = "history")]
use crate::util::{quit_error_msg, ErrorHintsBuilder};
@ -36,11 +36,6 @@ impl<'a: 'b, 'b> MainMatcher<'a> {
ArgApi::value(self.matches)
}
/// Get basic HTTP authentication credentials to use.
pub fn basic_auth(&'a self) -> Option<(String, Option<String>)> {
ArgBasicAuth::value(self.matches)
}
/// Get the history file to use.
#[cfg(feature = "history")]
pub fn history(&self) -> PathBuf {
@ -65,16 +60,16 @@ impl<'a: 'b, 'b> MainMatcher<'a> {
pub fn timeout(&self) -> u64 {
self.matches
.value_of("timeout")
.and_then(|arg| parse_duration(arg).ok())
.expect("invalid timeout value") as u64
.and_then(|arg| arg.parse().ok())
.expect("invalid timeout value")
}
/// Get the transfer timeout in seconds
pub fn transfer_timeout(&self) -> u64 {
self.matches
.value_of("transfer-timeout")
.and_then(|arg| parse_duration(arg).ok())
.expect("invalid transfer-timeout value") as u64
.and_then(|arg| arg.parse().ok())
.expect("invalid transfer-timeout value")
}
/// Check whether we are incognito from the file history.

View file

@ -2,7 +2,6 @@ pub mod debug;
pub mod delete;
pub mod download;
pub mod exists;
pub mod generate;
#[cfg(feature = "history")]
pub mod history;
pub mod info;
@ -17,7 +16,6 @@ pub use self::debug::DebugMatcher;
pub use self::delete::DeleteMatcher;
pub use self::download::DownloadMatcher;
pub use self::exists::ExistsMatcher;
pub use self::generate::GenerateMatcher;
#[cfg(feature = "history")]
pub use self::history::HistoryMatcher;
pub use self::info::InfoMatcher;

View file

@ -1,12 +1,8 @@
use clap::ArgMatches;
use ffsend_api::api::Version as ApiVersion;
use ffsend_api::url::Url;
use super::Matcher;
use crate::cmd::{
arg::{ArgDownloadLimit, ArgOwner, ArgUrl, CmdArgOption},
matcher::MainMatcher,
};
use crate::cmd::arg::{ArgDownloadLimit, ArgOwner, ArgUrl, CmdArgOption};
/// The params command matcher.
pub struct ParamsMatcher<'a> {
@ -30,16 +26,8 @@ impl<'a: 'b, 'b> ParamsMatcher<'a> {
}
/// Get the download limit.
///
/// If the download limit was the default, `None` is returned to not
/// explicitly set it.
pub fn download_limit(
&'a self,
main_matcher: &MainMatcher,
api_version: ApiVersion,
auth: bool,
) -> Option<usize> {
ArgDownloadLimit::value_checked(self.matches, main_matcher, api_version, auth)
pub fn download_limit(&'a self) -> Option<u8> {
ArgDownloadLimit::value(self.matches)
}
}

View file

@ -1,13 +1,10 @@
use clap::ArgMatches;
use ffsend_api::{api::Version as ApiVersion, config, url::Url};
use ffsend_api::action::params::PARAMS_DEFAULT_DOWNLOAD as DOWNLOAD_DEFAULT;
use ffsend_api::url::Url;
use super::Matcher;
use crate::cmd::{
arg::{
ArgDownloadLimit, ArgExpiryTime, ArgGenPassphrase, ArgHost, ArgPassword, CmdArgFlag,
CmdArgOption,
},
matcher::MainMatcher,
use crate::cmd::arg::{
ArgDownloadLimit, ArgGenPassphrase, ArgHost, ArgPassword, CmdArgFlag, CmdArgOption,
};
use crate::util::{bin_name, env_var_present, quit_error_msg, ErrorHintsBuilder};
@ -19,11 +16,10 @@ pub struct UploadMatcher<'a> {
impl<'a: 'b, 'b> UploadMatcher<'a> {
/// Get the selected file to upload.
// TODO: maybe return a file or path instance here
pub fn files(&'a self) -> Vec<&'a str> {
pub fn file(&'a self) -> &'a str {
self.matches
.values_of("FILE")
.value_of("FILE")
.expect("no file specified to upload")
.collect()
}
/// The the name to use for the uploaded file.
@ -35,7 +31,7 @@ impl<'a: 'b, 'b> UploadMatcher<'a> {
let name = self.matches.value_of("name")?;
// The file name must not be empty
// TODO: allow to force an empty name here, and process empty names on downloading
// TODO: allow to force an empty name here, and process emtpy names on downloading
if name.trim().is_empty() {
quit_error_msg(
"the file name must not be empty",
@ -77,33 +73,13 @@ impl<'a: 'b, 'b> UploadMatcher<'a> {
}
/// Get the download limit.
///
/// If the download limit was the default, `None` is returned to not
/// explicitly set it.
pub fn download_limit(
&'a self,
main_matcher: &MainMatcher,
api_version: ApiVersion,
auth: bool,
) -> Option<usize> {
ArgDownloadLimit::value_checked(self.matches, main_matcher, api_version, auth).and_then(
|d| match d {
d if d == config::downloads_default(api_version, auth) => None,
d => Some(d),
},
)
}
/// Get the expiry time in seconds.
///
/// If the expiry time was not set, `None` is returned.
pub fn expiry_time(
&'a self,
main_matcher: &MainMatcher,
api_version: ApiVersion,
auth: bool,
) -> Option<usize> {
ArgExpiryTime::value_checked(self.matches, main_matcher, api_version, auth)
pub fn download_limit(&'a self) -> Option<u8> {
ArgDownloadLimit::value(self.matches).and_then(|d| match d {
DOWNLOAD_DEFAULT => None,
d => Some(d),
})
}
/// Check whether to archive the file to upload.
@ -117,11 +93,6 @@ impl<'a: 'b, 'b> UploadMatcher<'a> {
self.matches.is_present("open") || env_var_present("FFSEND_OPEN")
}
/// Check whether to to delete local files after uploading.
pub fn delete(&self) -> bool {
self.matches.is_present("delete")
}
/// Check whether to copy the file URL in the user's clipboard, get the copy mode.
#[cfg(feature = "clipboard")]
pub fn copy(&self) -> Option<CopyMode> {
@ -138,18 +109,6 @@ impl<'a: 'b, 'b> UploadMatcher<'a> {
None
}
}
/// Check whether to shorten a share URL
#[cfg(feature = "urlshorten")]
pub fn shorten(&self) -> bool {
self.matches.is_present("shorten")
}
/// Check whether to print a QR code for the share URL.
#[cfg(feature = "qrcode")]
pub fn qrcode(&self) -> bool {
self.matches.is_present("qrcode")
}
}
impl<'a> Matcher<'a> for UploadMatcher<'a> {
@ -171,7 +130,7 @@ pub enum CopyMode {
}
impl CopyMode {
/// Build the string to copy, based on the given `url` and current mode.
/// Build the string to copy, based on the given `url` and currend mode.
pub fn build(&self, url: &str) -> String {
match self {
CopyMode::Url => url.into(),

View file

@ -10,7 +10,6 @@ impl CmdDelete {
SubCommand::with_name("delete")
.about("Delete a shared file")
.visible_alias("del")
.visible_alias("rm")
.arg(ArgUrl::build())
.arg(ArgOwner::build())
}

View file

@ -23,7 +23,7 @@ impl CmdDownload {
.alias("out")
.alias("file")
.value_name("PATH")
.help("Output file or directory"),
.help("The output file or directory"),
);
// Optional archive support

View file

@ -1,33 +0,0 @@
use clap::{App, Arg, Shell, SubCommand};
/// The generate completions command definition.
pub struct CmdCompletions;
impl CmdCompletions {
pub fn build<'a, 'b>() -> App<'a, 'b> {
SubCommand::with_name("completions")
.about("Shell completions")
.alias("completion")
.alias("complete")
.arg(
Arg::with_name("SHELL")
.help("Shell type to generate completions for")
.required(true)
.multiple(true)
.takes_value(true)
.possible_value("all")
.possible_values(&Shell::variants())
.case_insensitive(true),
)
.arg(
Arg::with_name("output")
.long("output")
.short("o")
.alias("output-dir")
.alias("out")
.alias("dir")
.value_name("DIR")
.help("Shell completion files output directory"),
)
}
}

View file

@ -1,18 +0,0 @@
pub mod completions;
use clap::{App, AppSettings, SubCommand};
use completions::CmdCompletions;
/// The generate command definition.
pub struct CmdGenerate;
impl CmdGenerate {
pub fn build<'a, 'b>() -> App<'a, 'b> {
SubCommand::with_name("generate")
.about("Generate assets")
.visible_alias("gen")
.setting(AppSettings::SubcommandRequiredElseHelp)
.subcommand(CmdCompletions::build())
}
}

View file

@ -1,4 +1,4 @@
use clap::{App, Arg, SubCommand};
use clap::{App, SubCommand};
/// The history command definition.
pub struct CmdHistory;
@ -9,20 +9,5 @@ impl CmdHistory {
.about("View file history")
.visible_alias("h")
.alias("ls")
.arg(
Arg::with_name("rm")
.long("rm")
.short("R")
.alias("remove")
.value_name("URL")
.help("Remove history entry"),
)
.arg(
Arg::with_name("clear")
.long("clear")
.short("C")
.alias("flush")
.help("Clear all history"),
)
}
}

View file

@ -2,7 +2,6 @@ pub mod debug;
pub mod delete;
pub mod download;
pub mod exists;
pub mod generate;
#[cfg(feature = "history")]
pub mod history;
pub mod info;
@ -16,7 +15,6 @@ pub use self::debug::CmdDebug;
pub use self::delete::CmdDelete;
pub use self::download::CmdDownload;
pub use self::exists::CmdExists;
pub use self::generate::CmdGenerate;
#[cfg(feature = "history")]
pub use self::history::CmdHistory;
pub use self::info::CmdInfo;

View file

@ -1,8 +1,7 @@
use clap::{App, Arg, SubCommand};
use ffsend_api::action::params::PARAMS_DEFAULT_DOWNLOAD_STR as DOWNLOAD_DEFAULT;
use crate::cmd::arg::{
ArgDownloadLimit, ArgExpiryTime, ArgGenPassphrase, ArgHost, ArgPassword, CmdArg,
};
use crate::cmd::arg::{ArgDownloadLimit, ArgGenPassphrase, ArgHost, ArgPassword, CmdArg};
/// The upload command definition.
pub struct CmdUpload;
@ -17,14 +16,13 @@ impl CmdUpload {
.visible_alias("up")
.arg(
Arg::with_name("FILE")
.help("The file(s) to upload")
.help("The file to upload")
.required(true)
.multiple(true),
.multiple(false),
)
.arg(ArgPassword::build().help("Protect the file with a password"))
.arg(ArgGenPassphrase::build())
.arg(ArgDownloadLimit::build())
.arg(ArgExpiryTime::build())
.arg(ArgDownloadLimit::build().default_value(DOWNLOAD_DEFAULT))
.arg(ArgHost::build())
.arg(
Arg::with_name("name")
@ -40,13 +38,6 @@ impl CmdUpload {
.long("open")
.short("o")
.help("Open the share link in your browser"),
)
.arg(
Arg::with_name("delete")
.long("delete")
.alias("rm")
.short("D")
.help("Delete local file after upload"),
);
// Optional archive support
@ -82,31 +73,6 @@ impl CmdUpload {
);
}
// Optional url shortening support
#[cfg(feature = "urlshorten")]
{
cmd = cmd.arg(
Arg::with_name("shorten")
.long("shorten")
.alias("short")
.alias("url-shorten")
.short("S")
.help("Shorten share URLs with a public service"),
)
}
// Optional qrcode support
#[cfg(feature = "qrcode")]
{
cmd = cmd.arg(
Arg::with_name("qrcode")
.long("qrcode")
.alias("qr")
.short("Q")
.help("Print a QR code for the share URL"),
)
}
cmd
}
}

View file

@ -1,6 +1,3 @@
#[cfg(feature = "infer-command")]
use std::collections::HashMap;
use ffsend_api::api::{DesiredVersion, Version};
/// The timeout for the Send client for generic requests, `0` to disable.
@ -18,19 +15,3 @@ pub const API_VERSION_DESIRED_DEFAULT: DesiredVersion = DesiredVersion::Assume(A
pub const API_VERSION_ASSUME: Version = Version::V3;
#[cfg(not(feature = "send3"))]
pub const API_VERSION_ASSUME: Version = Version::V2;
#[cfg(feature = "infer-command")]
lazy_static! {
/// Hashmap holding binary names to infer subcommands for.
///
/// When the `ffsend` binary is called with such a name, the corresponding subcommand is
/// automatically inserted as argument. This also works when calling binaries through symbolic
/// or hard links.
pub static ref INFER_COMMANDS: HashMap<&'static str, &'static str> = {
let mut m = HashMap::new();
m.insert("ffput", "upload");
m.insert("ffget", "download");
m.insert("ffdel", "delete");
m
};
}

View file

@ -6,7 +6,6 @@ use ffsend_api::action::version::Error as VersionError;
use ffsend_api::file::remote_file::FileParseError;
use crate::action::download::Error as CliDownloadError;
use crate::action::generate::completions::Error as CliGenerateCompletionsError;
#[cfg(feature = "history")]
use crate::action::history::Error as CliHistoryError;
use crate::action::info::Error as CliInfoError;
@ -57,10 +56,6 @@ pub enum ActionError {
#[fail(display = "failed to check whether the file exists")]
Exists(#[cause] ExistsError),
/// An error occurred while generating completions.
#[fail(display = "failed to generate shell completions")]
GenerateCompletions(#[cause] CliGenerateCompletionsError),
/// An error occurred while processing the file history.
#[cfg(feature = "history")]
#[fail(display = "failed to process the history")]
@ -104,12 +99,6 @@ impl From<ExistsError> for ActionError {
}
}
impl From<CliGenerateCompletionsError> for ActionError {
fn from(err: CliGenerateCompletionsError) -> ActionError {
ActionError::GenerateCompletions(err)
}
}
#[cfg(feature = "history")]
impl From<CliHistoryError> for ActionError {
fn from(err: CliHistoryError) -> ActionError {

View file

@ -1,14 +1,15 @@
extern crate toml;
extern crate version_compare;
use std::fs;
use std::io::Error as IoError;
use std::path::PathBuf;
use self::toml::de::Error as DeError;
use self::toml::ser::Error as SerError;
use self::version_compare::{CompOp, VersionCompare};
use failure::Fail;
use ffsend_api::{
file::remote_file::{FileParseError, RemoteFile},
url::Url,
};
use toml::{de::Error as DeError, ser::Error as SerError};
use version_compare::Cmp;
use ffsend_api::file::remote_file::RemoteFile;
use crate::util::{print_error, print_warning};
@ -21,7 +22,7 @@ const VERSION_MAX: &str = crate_version!();
#[derive(Serialize, Deserialize)]
pub struct History {
/// The application version the history file was built with.
/// Used for compatibility checking.
/// Used for compatability checking.
version: Option<String>,
/// The file history.
@ -62,9 +63,9 @@ impl History {
// Get the version number from the file
let version = history.version.as_ref().unwrap();
if let Ok(true) = version_compare::compare_to(version, VERSION_MIN, Cmp::Lt) {
if let Ok(true) = VersionCompare::compare_to(version, VERSION_MIN, &CompOp::Lt) {
print_warning("history file version is too old, ignoring");
} else if let Ok(true) = version_compare::compare_to(version, VERSION_MAX, Cmp::Gt) {
} else if let Ok(true) = VersionCompare::compare_to(version, VERSION_MAX, &CompOp::Gt) {
print_warning("history file has an unknown version, ignoring");
}
}
@ -103,26 +104,11 @@ impl History {
return Ok(());
}
// Ensure the file parent directories are available
// Ensure the file parnet directories are available
if let Some(parent) = path.parent() {
fs::create_dir_all(parent)?;
}
// Set file permissions on unix based systems
#[cfg(unix)]
{
use std::fs::Permissions;
use std::os::unix::fs::PermissionsExt;
if !path.exists() {
let file = fs::File::create(path).map_err(SaveError::Write)?;
// Set Read/Write permissions for the user
file.set_permissions(Permissions::from_mode(0o600))
.map_err(SaveError::SetPermissions)?;
}
}
// Build the data and write to a file
let data = toml::to_string(self)?;
fs::write(&path, data)?;
@ -166,16 +152,16 @@ impl History {
self.changed = true;
}
/// Remove a file, matched by it's file ID.
/// Remove the given remote file, matched by it's file ID.
///
/// If any file was removed, true is returned.
pub fn remove(&mut self, id: &str) -> bool {
pub fn remove(&mut self, file: &RemoteFile) -> bool {
// Get the indices of files that have expired
let expired_indices: Vec<usize> = self
.files
.iter()
.enumerate()
.filter(|&(_, f)| f.id() == id)
.filter(|&(_, f)| f.id() == file.id())
.map(|(i, _)| i)
.collect();
@ -191,13 +177,6 @@ impl History {
!expired_indices.is_empty()
}
/// Remove a file by the given URL.
///
/// If any file was removed, true is returned.
pub fn remove_url(&mut self, url: Url) -> Result<bool, FileParseError> {
Ok(self.remove(RemoteFile::parse_url(url, None)?.id()))
}
/// Get all files.
pub fn files(&self) -> &Vec<RemoteFile> {
&self.files
@ -213,18 +192,12 @@ impl History {
.find(|f| f.id() == file.id() && f.host() == file.host())
}
/// Clear all history.
pub fn clear(&mut self) {
self.changed = !self.files.is_empty();
self.files.clear();
}
/// Garbage collect (remove) all files that have been expired,
/// as defined by their `expire_at` property.
///
/// If the expiry property is None (thus unknown), the file will be kept.
///
/// The number of expired files is returned.
/// The number of exired files is returned.
pub fn gc(&mut self) -> usize {
// Get a list of expired files
let expired: Vec<RemoteFile> = self
@ -236,7 +209,7 @@ impl History {
// Remove the files
for f in &expired {
self.remove(f.id());
self.remove(f);
}
// Set the changed flag
@ -332,10 +305,6 @@ pub enum SaveError {
#[fail(display = "failed to write to the history file")]
Write(#[cause] IoError),
/// Failed to set file permissions to the history file.
#[fail(display = "failed to set permissions to the history file")]
SetPermissions(#[cause] IoError),
/// Failed to delete the history file, which was tried because there
/// are no history items to save.
#[fail(display = "failed to delete history file, because history is empty")]

View file

@ -55,7 +55,7 @@ fn remove_error(matcher_main: &MainMatcher, file: &RemoteFile) -> Result<bool, H
// Load the history, remove the file, and save
let mut history = History::load_or_new(matcher_main.history())?;
let removed = history.remove(file.id());
let removed = history.remove(file);
history.save()?;
Ok(removed)
}

View file

@ -23,17 +23,12 @@ mod history;
mod history_tool;
mod host;
mod progress;
#[cfg(feature = "urlshorten")]
mod urlshorten;
mod util;
use std::process;
use crate::action::debug::Debug;
use crate::action::delete::Delete;
use crate::action::download::Download;
use crate::action::exists::Exists;
use crate::action::generate::Generate;
#[cfg(feature = "history")]
use crate::action::history::History;
use crate::action::info::Info;
@ -95,13 +90,6 @@ fn invoke_action(handler: &Handler) -> Result<(), Error> {
.map_err(|err| err.into());
}
// Match the generate command
if handler.generate().is_some() {
return Generate::new(handler.matches())
.invoke()
.map_err(|err| err.into());
}
// Match the history command
#[cfg(feature = "history")]
{
@ -158,7 +146,7 @@ fn invoke_action(handler: &Handler) -> Result<(), Error> {
}
/// Print the main info, shown when no subcommands were supplied.
pub fn print_main_info() -> ! {
pub fn print_main_info() {
// Get the name of the used executable
let bin = bin_name();
@ -174,9 +162,4 @@ pub fn print_main_info() -> ! {
println!();
println!("To show all subcommands, features and other help:");
println!(" {}", highlight(&format!("{} help [SUBCOMMAND]", bin)));
println!();
println!("The default public Send host is provided by Tim Visee.");
println!("Please consider to donate and help keep it running: https://vis.ee/donate");
process::exit(1)
}

View file

@ -1,8 +1,10 @@
extern crate pbr;
use std::io::{stderr, Stderr};
use std::time::Duration;
use self::pbr::{ProgressBar as Pbr, Units};
use ffsend_api::pipe::ProgressReporter;
use pbr::{ProgressBar as Pbr, Units};
/// The refresh rate of the progress bar, in milliseconds.
const PROGRESS_BAR_FPS_MILLIS: u64 = 200;
@ -57,17 +59,9 @@ impl<'a> ProgressReporter for ProgressBar<'a> {
/// Finish the progress.
fn finish(&mut self) {
let progress_bar = self
.progress_bar
self.progress_bar
.as_mut()
.expect("progress bar not yet instantiated");
#[cfg(not(target_os = "windows"))]
progress_bar.finish_print(self.msg_finish);
#[cfg(target_os = "windows")]
{
progress_bar.finish_println(self.msg_finish);
eprintln!();
}
.expect("progress bar not yet instantiated")
.finish_print(self.msg_finish);
}
}

View file

@ -1,98 +0,0 @@
//! URL shortening mechanics.
use ffsend_api::{
api::request::{ensure_success, ResponseError},
client::Client,
reqwest,
url::{self, Url},
};
use urlshortener::{
providers::{self, Provider},
request::{Method, Request},
};
/// An URL shortening result.
type Result<T> = ::std::result::Result<T, Error>;
/// Shorten the given URL.
pub fn shorten(client: &Client, url: &str) -> Result<String> {
// TODO: allow selecting other shorteners
request(client, providers::request(url, &Provider::IsGd))
}
/// Shorten the given URL.
pub fn shorten_url(client: &Client, url: &Url) -> Result<Url> {
Url::parse(&shorten(client, url.as_str())?).map_err(|err| err.into())
}
/// Do the request as given, return the response.
fn request(client: &Client, req: Request) -> Result<String> {
// Start the request builder
let mut builder = match req.method {
Method::Get => client.get(&req.url),
Method::Post => client.post(&req.url),
};
// Define the custom user agent
if let Some(_agent) = req.user_agent.clone() {
// TODO: implement this
// builder.header(header::UserAgent::new(agent.0));
panic!("Custom UserAgent for URL shortener not yet implemented");
}
// Define the custom content type
if let Some(_content_type) = req.content_type {
// TODO: implement this
// match content_type {
// ContentType::Json => builder.header(header::ContentType::json()),
// ContentType::FormUrlEncoded => {
// builder.header(header::ContentType::form_url_encoded())
// }
// };
panic!("Custom UserAgent for URL shortener not yet implemented");
}
// Define the custom body
if let Some(body) = req.body.clone() {
builder = builder.body(body);
}
// Send the request, ensure success
let response = builder.send().map_err(Error::Request)?;
ensure_success(&response)?;
// Respond with the body text
response.text().map_err(Error::Malformed)
}
/// An URL shortening error.
#[derive(Debug, Fail)]
pub enum Error {
/// Failed to send the shortening request.
#[fail(display = "failed to send URL shorten request")]
Request(#[cause] reqwest::Error),
/// The server responded with a bad response.
#[fail(display = "failed to shorten URL, got bad response")]
Response(#[cause] ResponseError),
/// The server responded with a malformed response.
#[fail(display = "failed to shorten URL, got malformed response")]
Malformed(#[cause] reqwest::Error),
/// An error occurred while parsing the shortened URL.
#[fail(display = "failed to shorten URL, could not parse URL")]
Url(#[cause] url::ParseError),
}
impl From<url::ParseError> for Error {
fn from(err: url::ParseError) -> Self {
Error::Url(err)
}
}
impl From<ResponseError> for Error {
fn from(err: ResponseError) -> Self {
Error::Response(err)
}
}

View file

@ -1,47 +1,36 @@
#[cfg(feature = "clipboard-crate")]
extern crate clip;
#[cfg(feature = "clipboard-bin")]
extern crate which;
#[cfg(all(feature = "clipboard", not(target_os = "linux")))]
extern crate clipboard;
extern crate colored;
extern crate directories;
extern crate fs2;
extern crate open;
use std::borrow::Borrow;
use std::env::{self, current_exe, var_os};
use std::env::{current_exe, var_os};
use std::ffi::OsStr;
#[cfg(feature = "clipboard")]
use std::fmt;
use std::fmt::{Debug, Display};
#[cfg(feature = "clipboard-bin")]
#[cfg(feature = "clipboard")]
use std::io::ErrorKind as IoErrorKind;
use std::io::{self, Read};
use std::io::{stderr, stdin, Error as IoError, Write};
use std::iter;
use std::path::Path;
#[cfg(feature = "history")]
use std::path::PathBuf;
use std::process::exit;
#[cfg(feature = "clipboard-bin")]
use std::process::{exit, ExitStatus};
#[cfg(all(feature = "clipboard", target_os = "linux"))]
use std::process::{Command, Stdio};
#[cfg(feature = "clipboard-crate")]
use self::clip::{ClipboardContext, ClipboardProvider};
use chrono::Duration;
use colored::*;
#[cfg(all(feature = "clipboard", not(target_os = "linux")))]
use self::clipboard::{ClipboardContext, ClipboardProvider};
use self::colored::*;
#[cfg(feature = "history")]
use directories::ProjectDirs;
use self::directories::ProjectDirs;
use self::fs2::available_space;
use chrono::Duration;
use failure::{err_msg, Fail};
#[cfg(feature = "clipboard-crate")]
#[cfg(all(feature = "clipboard", not(target_os = "linux")))]
use failure::{Compat, Error};
use ffsend_api::{
api::request::{ensure_success, ResponseError},
client::Client,
reqwest,
url::Url,
};
use fs2::available_space;
use rand::distributions::Alphanumeric;
use rand::{thread_rng, Rng};
use regex::Regex;
use ffsend_api::url::Url;
use rpassword::prompt_password_stderr;
#[cfg(feature = "clipboard-bin")]
use which::which;
use crate::cmd::matcher::MainMatcher;
@ -133,9 +122,6 @@ pub struct ErrorHints {
/// A list of info messages to print along with the error.
info: Vec<String>,
/// Show about the name option.
name: bool,
/// Show about the password option.
password: bool,
@ -161,8 +147,7 @@ impl ErrorHints {
pub fn any(&self) -> bool {
// Determine the result
#[allow(unused_mut)]
let mut result =
self.name || self.password || self.owner || self.force || self.verbose || self.help;
let mut result = self.password || self.owner || self.force || self.verbose || self.help;
// Factor in the history hint when enabled
#[cfg(feature = "history")]
@ -194,12 +179,6 @@ impl ErrorHints {
highlight("--api <VERSION>")
);
}
if self.name {
eprintln!(
"Use '{}' to specify a file name",
highlight("--name <NAME>")
);
}
if self.password {
eprintln!(
"Use '{}' to specify a password",
@ -241,7 +220,6 @@ impl Default for ErrorHints {
ErrorHints {
api: false,
info: Vec::new(),
name: false,
password: false,
owner: false,
#[cfg(feature = "history")]
@ -291,137 +269,42 @@ pub fn highlight_info(msg: &str) -> ColoredString {
}
/// Open the given URL in the users default browser.
/// The browsers exit status is returned.
pub fn open_url(url: impl Borrow<Url>) -> Result<(), IoError> {
/// The browsers exit statis is returned.
pub fn open_url(url: impl Borrow<Url>) -> Result<ExitStatus, IoError> {
open_path(url.borrow().as_str())
}
/// Open the given path or URL using the program configured on the system.
/// The program exit status is returned.
pub fn open_path(path: &str) -> Result<(), IoError> {
/// The program exit statis is returned.
pub fn open_path(path: &str) -> Result<ExitStatus, IoError> {
open::that(path)
}
/// Set the clipboard of the user to the given `content` string.
#[cfg(feature = "clipboard")]
pub fn set_clipboard(content: String) -> Result<(), ClipboardError> {
ClipboardType::select().set(content)
}
/// Clipboard management enum.
///
/// Defines which method of setting the clipboard is used.
/// Invoke `ClipboardType::select()` to select the best variant to use determined at runtime.
///
/// Usually, the `Native` variant is used. However, on Linux system a different variant will be
/// selected which will call a system binary to set the clipboard. This must be done because the
/// native clipboard interface only has a lifetime of the application. This means that the
/// clipboard is instantly cleared as soon as this application quits, which is always immediately.
/// This limitation is due to security reasons as defined by X11. The alternative binaries we set
/// the clipboard with spawn a daemon in the background to keep the clipboard alive until it's
/// flushed.
#[cfg(feature = "clipboard")]
#[derive(Clone, Eq, PartialEq)]
pub enum ClipboardType {
/// Native operating system clipboard.
#[cfg(feature = "clipboard-crate")]
Native,
/// Manage clipboard through `xclip` on Linux.
///
/// May contain a binary path if specified at compile time through the `XCLIP_PATH` variable.
#[cfg(feature = "clipboard-bin")]
Xclip(Option<String>),
/// Manage clipboard through `xsel` on Linux.
///
/// May contain a binary path if specified at compile time through the `XSEL_PATH` variable.
#[cfg(feature = "clipboard-bin")]
Xsel(Option<String>),
}
#[cfg(feature = "clipboard")]
impl ClipboardType {
/// Select the clipboard type to use, depending on the runtime system.
pub fn select() -> Self {
#[cfg(feature = "clipboard-crate")]
{
ClipboardType::Native
}
#[cfg(feature = "clipboard-bin")]
{
if let Some(path) = option_env!("XCLIP_PATH") {
ClipboardType::Xclip(Some(path.to_owned()))
} else if let Some(path) = option_env!("XSEL_PATH") {
ClipboardType::Xsel(Some(path.to_owned()))
} else if which("xclip").is_ok() {
ClipboardType::Xclip(None)
} else if which("xsel").is_ok() {
ClipboardType::Xsel(None)
} else {
// TODO: should we error here instead, as no clipboard binary was found?
ClipboardType::Xclip(None)
}
}
}
/// Set clipboard contents through the selected clipboard type.
pub fn set(&self, content: String) -> Result<(), ClipboardError> {
match self {
#[cfg(feature = "clipboard-crate")]
ClipboardType::Native => Self::native_set(content),
#[cfg(feature = "clipboard-bin")]
ClipboardType::Xclip(path) => Self::xclip_set(path.clone(), &content),
#[cfg(feature = "clipboard-bin")]
ClipboardType::Xsel(path) => Self::xsel_set(path.clone(), &content),
}
}
/// Set the clipboard through a native interface.
///
/// This is used on non-Linux systems.
#[cfg(feature = "clipboard-crate")]
fn native_set(content: String) -> Result<(), ClipboardError> {
#[cfg(not(target_os = "linux"))]
{
ClipboardProvider::new()
.and_then(|mut context: ClipboardContext| context.set_contents(content))
.map_err(|err| format_err!("{}", err).compat())
.map_err(ClipboardError::Native)
.map_err(ClipboardError::Generic)
}
#[cfg(feature = "clipboard-bin")]
fn xclip_set(path: Option<String>, content: &str) -> Result<(), ClipboardError> {
Self::sys_cmd_set(
"xclip",
Command::new(path.unwrap_or_else(|| "xclip".into()))
.arg("-sel")
.arg("clip"),
content,
)
}
#[cfg(feature = "clipboard-bin")]
fn xsel_set(path: Option<String>, content: &str) -> Result<(), ClipboardError> {
Self::sys_cmd_set(
"xsel",
Command::new(path.unwrap_or_else(|| "xsel".into())).arg("--clipboard"),
content,
)
}
#[cfg(feature = "clipboard-bin")]
fn sys_cmd_set(
bin: &'static str,
command: &mut Command,
content: &str,
) -> Result<(), ClipboardError> {
// Spawn the command process for setting the clipboard
let mut process = match command.stdin(Stdio::piped()).stdout(Stdio::null()).spawn() {
#[cfg(target_os = "linux")]
{
// Open an xclip process
let mut process = match Command::new("xclip")
.arg("-sel")
.arg("clip")
.stdin(Stdio::piped())
.spawn()
{
Ok(process) => process,
Err(err) => {
return Err(match err.kind() {
IoErrorKind::NotFound => ClipboardError::NoBinary,
_ => ClipboardError::BinaryIo(bin, err),
IoErrorKind::NotFound => ClipboardError::NoXclip,
_ => ClipboardError::Xclip(err),
});
}
};
@ -432,76 +315,49 @@ impl ClipboardType {
.as_mut()
.unwrap()
.write_all(content.as_bytes())
.map_err(|err| ClipboardError::BinaryIo(bin, err))?;
.map_err(ClipboardError::Xclip)?;
// Wait for xclip to exit
let status = process
.wait()
.map_err(|err| ClipboardError::BinaryIo(bin, err))?;
let status = process.wait().map_err(ClipboardError::Xclip)?;
if !status.success() {
return Err(ClipboardError::BinaryStatus(
bin,
status.code().unwrap_or(0),
));
return Err(ClipboardError::XclipStatus(status.code().unwrap_or(0)));
}
Ok(())
}
}
#[cfg(feature = "clipboard")]
impl fmt::Display for ClipboardType {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
#[cfg(feature = "clipboard-crate")]
ClipboardType::Native => write!(f, "native"),
#[cfg(feature = "clipboard-bin")]
ClipboardType::Xclip(path) => match path {
None => write!(f, "xclip"),
Some(path) => write!(f, "xclip ({})", path),
},
#[cfg(feature = "clipboard-bin")]
ClipboardType::Xsel(path) => match path {
None => write!(f, "xsel"),
Some(path) => write!(f, "xsel ({})", path),
},
}
}
}
#[cfg(feature = "clipboard")]
#[derive(Debug, Fail)]
pub enum ClipboardError {
/// A generic error occurred while setting the clipboard contents.
///
/// This is for non-Linux systems, using a native clipboard interface.
#[cfg(feature = "clipboard-crate")]
/// A generic error occurred while setting the clipboard contents
#[cfg(not(target_os = "linux"))]
#[fail(display = "failed to access clipboard")]
Native(#[cause] Compat<Error>),
Generic(#[cause] Compat<Error>),
/// The `xclip` or `xsel` binary could not be found on the system, required for clipboard support.
#[cfg(feature = "clipboard-bin")]
#[fail(display = "failed to access clipboard, xclip or xsel is not installed")]
NoBinary,
/// Xclip is not installed on the system, which is required for clipboard support.
#[cfg(target_os = "linux")]
#[fail(display = "failed to access clipboard, xclip is not installed")]
NoXclip,
/// An error occurred while using `xclip` or `xsel` to set the clipboard contents.
/// This problem probably occurred when starting, or while piping the clipboard contents to
/// the process.
#[cfg(feature = "clipboard-bin")]
#[fail(display = "failed to access clipboard using {}", _0)]
BinaryIo(&'static str, #[cause] IoError),
/// An error occurred while using xclip to set the clipboard contents.
/// This problem probably occurred when stargin the xclip process, or while piping the
/// clipboard contents to the process.
#[cfg(target_os = "linux")]
#[fail(display = "failed to access clipboard using xclip")]
Xclip(#[cause] IoError),
/// `xclip` or `xsel` unexpectedly exited with a non-successful status code.
#[cfg(feature = "clipboard-bin")]
/// Xclip unexpectetly exited with a non-successful status code.
#[cfg(target_os = "linux")]
#[fail(
display = "failed to use clipboard, {} exited with status code {}",
_0, _1
display = "failed to use clipboard, xclip exited with status code {}",
_0
)]
BinaryStatus(&'static str, i32),
XclipStatus(i32),
}
/// Check for an empty password in the given `password`.
/// If the password is empty the program will quit with an error unless
/// Check for an emtpy password in the given `password`.
/// If the password is emtpy the program will quit with an error unless
/// forced.
// TODO: move this to a better module
pub fn check_empty_password(password: &str, matcher_main: &MainMatcher) {
@ -519,7 +375,7 @@ pub fn check_empty_password(password: &str, matcher_main: &MainMatcher) {
/// Prompt the user to enter a password.
///
/// If `empty` is `false`, empty passwords aren't allowed unless forced.
/// If `empty` is `false`, emtpy passwords aren't allowed unless forced.
pub fn prompt_password(main_matcher: &MainMatcher, optional: bool) -> Option<String> {
// Quit with an error if we may not interact
if !optional && main_matcher.no_interact() {
@ -549,7 +405,7 @@ pub fn prompt_password(main_matcher: &MainMatcher, optional: bool) -> Option<Str
}
}
// On input error, propagate the error or don't use a password if optional
// On input error, propegate the error or don't use a password if optional
Err(err) => {
if !optional {
quit_error(
@ -592,7 +448,7 @@ pub fn ensure_password(
return false;
}
// Check whether we allow interaction
// Check whehter we allow interaction
let interact = !main_matcher.no_interact();
loop {
@ -761,7 +617,7 @@ pub fn ensure_owner_token(
main_matcher: &MainMatcher,
optional: bool,
) -> bool {
// Check whether we allow interaction
// Check whehter we allow interaction
let interact = !main_matcher.no_interact();
// Notify that an owner token is required
@ -825,69 +681,6 @@ pub fn format_bytes(bytes: u64) -> String {
}
}
/// Parse the given duration string from human readable format into seconds.
/// This method parses a string of time components to represent the given duration.
///
/// The following time units are used:
/// - `w`: weeks
/// - `d`: days
/// - `h`: hours
/// - `m`: minutes
/// - `s`: seconds
/// The following time strings can be parsed:
/// - `8w6d`
/// - `23h14m`
/// - `9m55s`
/// - `1s1s1s1s1s`
pub fn parse_duration(duration: &str) -> Result<usize, ParseDurationError> {
// Build a regex to grab time parts
let re = Regex::new(r"(?i)([0-9]+)(([a-z]|\s*$))")
.expect("failed to compile duration parsing regex");
// We must find any match
if re.find(duration).is_none() {
return Err(ParseDurationError::Empty);
}
// Parse each time part, sum it's seconds
let mut seconds = 0;
for capture in re.captures_iter(duration) {
// Parse time value and modifier
let number = capture[1]
.parse::<usize>()
.map_err(ParseDurationError::InvalidValue)?;
let modifier = capture[2].trim().to_lowercase();
// Multiply and sum seconds by modifier
seconds += match modifier.as_str() {
"" | "s" => number,
"m" => number * 60,
"h" => number * 60 * 60,
"d" => number * 60 * 60 * 24,
"w" => number * 60 * 60 * 24 * 7,
m => return Err(ParseDurationError::UnknownIdentifier(m.into())),
};
}
Ok(seconds)
}
/// Represents a duration parsing error.
#[derive(Debug, Fail)]
pub enum ParseDurationError {
/// The given duration string did not contain any duration part.
#[fail(display = "given string did not contain any duration part")]
Empty,
/// A numeric value was invalid.
#[fail(display = "duration part has invalid numeric value")]
InvalidValue(std::num::ParseIntError),
/// The given duration string contained an invalid duration modifier.
#[fail(display = "duration part has unknown time identifier '{}'", _0)]
UnknownIdentifier(String),
}
/// Format the given duration in a human readable format.
/// This method builds a string of time components to represent
/// the given duration.
@ -948,19 +741,9 @@ pub fn format_bool(b: bool) -> &'static str {
}
/// Get the name of the executable that was invoked.
///
/// When a symbolic or hard link is used, the name of the link is returned.
///
/// This attempts to obtain the binary name in the following order:
/// - name in first item of program arguments via `std::env::args`
/// - current executable name via `std::env::current_exe`
/// - crate name
pub fn bin_name() -> String {
env::args_os()
.next()
.filter(|path| !path.is_empty())
.map(PathBuf::from)
.or_else(|| current_exe().ok())
current_exe()
.ok()
.and_then(|p| p.file_name().map(|n| n.to_owned()))
.and_then(|n| n.into_string().ok())
.unwrap_or_else(|| crate_name!().into())
@ -984,7 +767,7 @@ pub fn ensure_enough_space<P: AsRef<Path>>(path: P, size: u64) {
}
};
// Return if enough disk space is available
// Return if enough disk space is avaiable
if space >= size {
return;
}
@ -1048,28 +831,14 @@ pub fn features_list() -> Vec<&'static str> {
features.push("archive");
#[cfg(feature = "clipboard")]
features.push("clipboard");
#[cfg(feature = "clipboard-bin")]
features.push("clipboard-bin");
#[cfg(feature = "clipboard-crate")]
features.push("clipboard-crate");
#[cfg(feature = "history")]
features.push("history");
#[cfg(feature = "qrcode")]
features.push("qrcode");
#[cfg(feature = "urlshorten")]
features.push("urlshorten");
#[cfg(feature = "infer-command")]
features.push("infer-command");
#[cfg(feature = "no-qcolor")]
#[cfg(feature = "no-color")]
features.push("no-color");
#[cfg(feature = "send2")]
features.push("send2");
#[cfg(feature = "send3")]
features.push("send3");
#[cfg(feature = "crypto-ring")]
features.push("crypto-ring");
#[cfg(feature = "crypto-openssl")]
features.push("crypto-openssl");
features
}
@ -1088,71 +857,3 @@ pub fn api_version_list() -> Vec<&'static str> {
versions
}
/// Follow redirects on the given URL, and return the final full URL.
///
/// This is used to obtain share URLs from shortened links.
///
// TODO: extract this into module
pub fn follow_url(client: &Client, url: &Url) -> Result<Url, FollowError> {
// Send the request, follow the URL, ensure success
let response = client
.get(url.as_str())
.send()
.map_err(FollowError::Request)?;
ensure_success(&response)?;
// Obtain the final URL
Ok(response.url().clone())
}
/// URL following error.
#[derive(Debug, Fail)]
pub enum FollowError {
/// Failed to send the shortening request.
#[fail(display = "failed to send URL follow request")]
Request(#[cause] reqwest::Error),
/// The server responded with a bad response.
#[fail(display = "failed to shorten URL, got bad response")]
Response(#[cause] ResponseError),
}
impl From<ResponseError> for FollowError {
fn from(err: ResponseError) -> Self {
FollowError::Response(err)
}
}
/// Generate a random alphanumeric string with the given length.
pub fn rand_alphanum_string(len: usize) -> String {
let mut rng = thread_rng();
iter::repeat(())
.map(|()| rng.sample(Alphanumeric) as char)
.take(len)
.collect()
}
/// Read file from stdin.
pub fn stdin_read_file(prompt: bool) -> Result<Vec<u8>, StdinErr> {
if prompt {
#[cfg(not(windows))]
eprintln!("Enter input. Use [CTRL+D] to stop:");
#[cfg(windows)]
eprintln!("Enter input. Use [CTRL+Z] to stop:");
}
let mut data = vec![];
io::stdin()
.lock()
.read_to_end(&mut data)
.map_err(StdinErr::Stdin)?;
Ok(data)
}
/// URL following error.
#[derive(Debug, Fail)]
pub enum StdinErr {
#[fail(display = "failed to read from stdin")]
Stdin(#[cause] io::Error),
}