Compare commits
10 commits
14edc3974b
...
488fcabd69
Author | SHA1 | Date | |
---|---|---|---|
|
488fcabd69 | ||
|
f60e2a4537 | ||
|
26fe53dfd8 | ||
|
0874fd8ace | ||
|
4081827049 | ||
|
4948e52110 | ||
|
1c2b071fd9 | ||
|
398ef79341 | ||
|
9114ef63c1 | ||
|
c2755dd288 |
7 changed files with 382 additions and 277 deletions
28
assets/config.json
Normal file
28
assets/config.json
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
{
|
||||||
|
"paddingVertical": "45px",
|
||||||
|
"paddingHorizontal": "45px",
|
||||||
|
"backgroundImage": null,
|
||||||
|
"backgroundImageSelection": null,
|
||||||
|
"backgroundMode": "color",
|
||||||
|
"backgroundColor": "rgba(31,129,109,1)",
|
||||||
|
"dropShadow": true,
|
||||||
|
"dropShadowOffsetY": "12px",
|
||||||
|
"dropShadowBlurRadius": "22px",
|
||||||
|
"theme": "nord",
|
||||||
|
"windowTheme": "none",
|
||||||
|
"language": "text",
|
||||||
|
"fontFamily": "Hack",
|
||||||
|
"fontSize": "14px",
|
||||||
|
"lineHeight": "150%",
|
||||||
|
"windowControls": true,
|
||||||
|
"widthAdjustment": true,
|
||||||
|
"lineNumbers": false,
|
||||||
|
"firstLineNumber": 1,
|
||||||
|
"exportSize": "4x",
|
||||||
|
"watermark": false,
|
||||||
|
"squaredImage": false,
|
||||||
|
"hiddenCharacters": false,
|
||||||
|
"name": "",
|
||||||
|
"width": 680,
|
||||||
|
"highlights": null
|
||||||
|
}
|
BIN
assets/demo.png
Normal file
BIN
assets/demo.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 226 KiB |
16
flake.lock
16
flake.lock
|
@ -16,6 +16,21 @@
|
||||||
"url": "https://gist.github.com/f0fd86b6c73063283afe550bc5d77594.git"
|
"url": "https://gist.github.com/f0fd86b6c73063283afe550bc5d77594.git"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"flake-utils": {
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1659877975,
|
||||||
|
"narHash": "sha256-zllb8aq3YO3h8B/U0/J1WBgAL8EX5yWf5pMj3G0NAmc=",
|
||||||
|
"owner": "numtide",
|
||||||
|
"repo": "flake-utils",
|
||||||
|
"rev": "c0e246b9b83f637f4681389ecabcb2681b4f3af0",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "numtide",
|
||||||
|
"repo": "flake-utils",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
"gourou-src": {
|
"gourou-src": {
|
||||||
"flake": false,
|
"flake": false,
|
||||||
"locked": {
|
"locked": {
|
||||||
|
@ -68,6 +83,7 @@
|
||||||
"root": {
|
"root": {
|
||||||
"inputs": {
|
"inputs": {
|
||||||
"base64-src": "base64-src",
|
"base64-src": "base64-src",
|
||||||
|
"flake-utils": "flake-utils",
|
||||||
"gourou-src": "gourou-src",
|
"gourou-src": "gourou-src",
|
||||||
"nixpkgs": "nixpkgs",
|
"nixpkgs": "nixpkgs",
|
||||||
"pugixml-src": "pugixml-src",
|
"pugixml-src": "pugixml-src",
|
||||||
|
|
322
flake.nix
322
flake.nix
|
@ -1,6 +1,7 @@
|
||||||
{
|
{
|
||||||
inputs = {
|
inputs = {
|
||||||
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
|
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
|
||||||
|
flake-utils.url = "github:numtide/flake-utils";
|
||||||
gourou-src = {
|
gourou-src = {
|
||||||
url = "git://soutade.fr/libgourou.git";
|
url = "git://soutade.fr/libgourou.git";
|
||||||
flake = false;
|
flake = false;
|
||||||
|
@ -15,151 +16,182 @@
|
||||||
};
|
};
|
||||||
pugixml-src = {
|
pugixml-src = {
|
||||||
url = "github:zeux/pugixml/latest";
|
url = "github:zeux/pugixml/latest";
|
||||||
flake = false;
|
flake = false;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
outputs = flakes: let
|
outputs = flakes:
|
||||||
version = "1.3.0";
|
flakes.flake-utils.lib.eachSystem [ "x86_64-linux" "aarch64-linux" ]
|
||||||
self = flakes.self.packages.x86_64-linux;
|
(system:
|
||||||
nixpkgs = flakes.nixpkgs.legacyPackages.x86_64-linux.pkgsStatic;
|
let
|
||||||
nixpkgs-dyn = flakes.nixpkgs.legacyPackages.x86_64-linux;
|
version = "1.3.1";
|
||||||
gourou-src = flakes.gourou-src;
|
self = flakes.self.packages.${system};
|
||||||
updfparser-src = flakes.updfparser-src;
|
nixpkgs = flakes.nixpkgs.legacyPackages.${system}.pkgsStatic;
|
||||||
base64-src = flakes.base64-src;
|
nixpkgs-dyn = flakes.nixpkgs.legacyPackages.${system};
|
||||||
pugixml-src = flakes.pugixml-src;
|
nixpkgs-fmt = flakes.nixpkgs-fmt.defaultPackage.${system};
|
||||||
cxx = "${nixpkgs.stdenv.cc}/bin/x86_64-unknown-linux-musl-g++";
|
gourou-src = flakes.gourou-src;
|
||||||
ar = "${nixpkgs.stdenv.cc.bintools.bintools_bin}/bin/x86_64-unknown-linux-musl-ar";
|
updfparser-src = flakes.updfparser-src;
|
||||||
obj-flags = "-O2 -static";
|
base64-src = flakes.base64-src;
|
||||||
in rec {
|
pugixml-src = flakes.pugixml-src;
|
||||||
packages.x86_64-linux.libzip-static = nixpkgs.libzip.overrideAttrs (prev: {
|
cxx = "${nixpkgs.stdenv.cc}/bin/${nixpkgs.stdenv.cc.targetPrefix}c++";
|
||||||
cmakeFlags = (prev.cmakeFlags or []) ++ [
|
ar = "${nixpkgs.stdenv.cc.bintools.bintools_bin}/bin/${nixpkgs.stdenv.cc.targetPrefix}ar";
|
||||||
"-DBUILD_SHARED_LIBS=OFF"
|
obj-flags = "-O2 -static";
|
||||||
"-DBUILD_EXAMPLES=OFF"
|
in
|
||||||
"-DBUILD_DOC=OFF"
|
rec {
|
||||||
"-DBUILD_TOOLS=OFF"
|
packages.libzip-static = nixpkgs.libzip.overrideAttrs (prev: {
|
||||||
"-DBUILD_REGRESS=OFF"
|
cmakeFlags = (prev.cmakeFlags or [ ]) ++ [
|
||||||
];
|
"-DBUILD_SHARED_LIBS=OFF"
|
||||||
outputs = ["out"];
|
"-DBUILD_EXAMPLES=OFF"
|
||||||
});
|
"-DBUILD_DOC=OFF"
|
||||||
packages.x86_64-linux.base64 = derivation {
|
"-DBUILD_TOOLS=OFF"
|
||||||
name = "updfparser";
|
"-DBUILD_REGRESS=OFF"
|
||||||
system = "x86_64-linux";
|
];
|
||||||
builder = "${nixpkgs.bash}/bin/bash";
|
outputs = [ "out" ];
|
||||||
PATH = "${nixpkgs.coreutils}/bin";
|
});
|
||||||
args = ["-c" ''
|
packages.base64 = derivation {
|
||||||
mkdir -p $out/include/base64
|
name = "updfparser";
|
||||||
cp ${base64-src}/Base64.h $out/include/base64/Base64.h
|
inherit system;
|
||||||
''];
|
builder = "${nixpkgs.bash}/bin/bash";
|
||||||
};
|
PATH = "${nixpkgs.coreutils}/bin";
|
||||||
packages.x86_64-linux.updfparser = derivation {
|
args = [
|
||||||
name = "updfparser";
|
"-c"
|
||||||
system = "x86_64-linux";
|
''
|
||||||
builder = "${nixpkgs.bash}/bin/bash";
|
mkdir -p $out/include/base64
|
||||||
PATH = "${nixpkgs.coreutils}/bin";
|
cp ${base64-src}/Base64.h $out/include/base64/Base64.h
|
||||||
args = [ "-c" ''
|
''
|
||||||
${cxx} \
|
];
|
||||||
-c ${updfparser-src}/src/*.cpp \
|
};
|
||||||
-I ${updfparser-src}/include \
|
packages.updfparser = derivation {
|
||||||
${obj-flags}
|
name = "updfparser";
|
||||||
mkdir -p $out/lib
|
inherit system;
|
||||||
${ar} crs $out/lib/libupdfparser.a *.o
|
builder = "${nixpkgs.bash}/bin/bash";
|
||||||
'' ];
|
PATH = "${nixpkgs.coreutils}/bin";
|
||||||
};
|
args = [
|
||||||
packages.x86_64-linux.gourou = derivation {
|
"-c"
|
||||||
name = "gourou";
|
''
|
||||||
system = "x86_64-linux";
|
${cxx} \
|
||||||
builder = "${nixpkgs.bash}/bin/bash";
|
-c ${updfparser-src}/src/*.cpp \
|
||||||
PATH = "${nixpkgs.coreutils}/bin";
|
-I ${updfparser-src}/include \
|
||||||
args = [ "-c" ''
|
${obj-flags}
|
||||||
${cxx} \
|
mkdir -p $out/lib
|
||||||
-c \
|
${ar} crs $out/lib/libupdfparser.a *.o
|
||||||
${gourou-src}/src/*.cpp \
|
''
|
||||||
${pugixml-src}/src/pugixml.cpp \
|
];
|
||||||
-I ${self.base64}/include \
|
};
|
||||||
-I ${gourou-src}/include \
|
packages.gourou = derivation {
|
||||||
-I ${pugixml-src}/src \
|
name = "gourou";
|
||||||
-I ${updfparser-src}/include \
|
inherit system;
|
||||||
${obj-flags}
|
builder = "${nixpkgs.bash}/bin/bash";
|
||||||
mkdir -p $out/lib $out/debug
|
PATH = "${nixpkgs.coreutils}/bin";
|
||||||
${ar} crs $out/lib/libgourou.a *.o
|
args = [
|
||||||
cp *.o $out/debug
|
"-c"
|
||||||
'' ];
|
''
|
||||||
};
|
shopt -s extglob
|
||||||
packages.x86_64-linux.utils-common = derivation {
|
${cxx} \
|
||||||
name = "utils-common";
|
-c \
|
||||||
system = "x86_64-linux";
|
${gourou-src}/src/!(pugixml).cpp \
|
||||||
builder = "${nixpkgs.bash}/bin/bash";
|
${pugixml-src}/src/pugixml.cpp \
|
||||||
PATH = "${nixpkgs.coreutils}/bin";
|
-I ${self.base64}/include \
|
||||||
args = [ "-c" ''
|
-I ${gourou-src}/include \
|
||||||
${cxx} \
|
-I ${pugixml-src}/src \
|
||||||
-c ${gourou-src}/utils/drmprocessorclientimpl.cpp \
|
-I ${updfparser-src}/include \
|
||||||
${gourou-src}/utils/utils_common.cpp \
|
${obj-flags}
|
||||||
-I ${gourou-src}/utils \
|
mkdir -p $out/lib $out/debug
|
||||||
-I ${gourou-src}/include \
|
${ar} crs $out/lib/libgourou.a *.o
|
||||||
-I ${pugixml-src}/src \
|
cp *.o $out/debug
|
||||||
-I ${nixpkgs.openssl.dev}/include \
|
''
|
||||||
-I ${nixpkgs.curl.dev}/include \
|
];
|
||||||
-I ${nixpkgs.zlib.dev}/include \
|
};
|
||||||
-I ${self.libzip-static}/include \
|
packages.utils-common = derivation {
|
||||||
${obj-flags}
|
name = "utils-common";
|
||||||
mkdir -p $out/lib
|
inherit system;
|
||||||
${ar} crs $out/lib/libutils-common.a *.o
|
builder = "${nixpkgs.bash}/bin/bash";
|
||||||
'' ];
|
PATH = "${nixpkgs.coreutils}/bin";
|
||||||
};
|
args = [
|
||||||
packages.x86_64-linux.knock = derivation {
|
"-c"
|
||||||
name = "knock";
|
''
|
||||||
system = "x86_64-linux";
|
${cxx} \
|
||||||
builder = "${nixpkgs.bash}/bin/bash";
|
-c ${gourou-src}/utils/drmprocessorclientimpl.cpp \
|
||||||
PATH = "${nixpkgs.coreutils}/bin";
|
${gourou-src}/utils/utils_common.cpp \
|
||||||
args = [ "-c" ''
|
-I ${gourou-src}/utils \
|
||||||
mkdir -p $out/bin
|
-I ${gourou-src}/include \
|
||||||
${cxx} \
|
-I ${pugixml-src}/src \
|
||||||
-o $out/bin/knock \
|
-I ${nixpkgs.openssl.dev}/include \
|
||||||
${./src/knock.cpp} \
|
-I ${nixpkgs.curl.dev}/include \
|
||||||
-D KNOCK_VERSION='"${version}"' \
|
-I ${nixpkgs.zlib.dev}/include \
|
||||||
-Wl,--as-needed -static \
|
-I ${self.libzip-static}/include \
|
||||||
${self.utils-common}/lib/libutils-common.a \
|
${obj-flags}
|
||||||
${self.gourou}/lib/libgourou.a \
|
mkdir -p $out/lib
|
||||||
${self.updfparser}/lib/libupdfparser.a \
|
${ar} crs $out/lib/libutils-common.a *.o
|
||||||
-Wl,--start-group \
|
''
|
||||||
${self.libzip-static}/lib/libzip.a \
|
];
|
||||||
${nixpkgs.libnghttp2}/lib/libnghttp2.a \
|
};
|
||||||
${nixpkgs.libidn2.out}/lib/libidn2.a \
|
packages.knock = derivation {
|
||||||
${nixpkgs.libunistring}/lib/libunistring.a \
|
name = "knock";
|
||||||
${nixpkgs.libssh2}/lib/libssh2.a \
|
inherit system;
|
||||||
${nixpkgs.zstd.out}/lib/libzstd.a \
|
builder = "${nixpkgs.bash}/bin/bash";
|
||||||
${nixpkgs.zlib}/lib/libz.a \
|
PATH = "${nixpkgs.coreutils}/bin";
|
||||||
${nixpkgs.openssl.out}/lib/libcrypto.a \
|
args = [
|
||||||
${nixpkgs.curl.out}/lib/libcurl.a \
|
"-c"
|
||||||
${nixpkgs.openssl.out}/lib/libssl.a \
|
''
|
||||||
-static-libgcc -static-libstdc++ \
|
mkdir -p $out/bin
|
||||||
-Wl,--end-group \
|
${cxx} \
|
||||||
-I ${gourou-src}/utils \
|
-o $out/bin/knock \
|
||||||
-I ${gourou-src}/include \
|
${./src/knock.cpp} \
|
||||||
-I ${pugixml-src}/src \
|
-D KNOCK_VERSION='"${version}"' \
|
||||||
-I ${nixpkgs.openssl.dev}/include \
|
--std=c++17 \
|
||||||
-I ${nixpkgs.curl.dev}/include \
|
-Wl,--as-needed -static \
|
||||||
-I ${nixpkgs.zlib.dev}/include \
|
${self.utils-common}/lib/libutils-common.a \
|
||||||
-I ${self.libzip-static}/include
|
${self.gourou}/lib/libgourou.a \
|
||||||
'' ];
|
${self.updfparser}/lib/libupdfparser.a \
|
||||||
};
|
-Wl,--start-group \
|
||||||
packages.x86_64-linux.default = self.knock;
|
${self.libzip-static}/lib/libzip.a \
|
||||||
packages.x86_64-linux.tests = nixpkgs-dyn.stdenv.mkDerivation {
|
${nixpkgs.libnghttp2}/lib/libnghttp2.a \
|
||||||
name = "tests";
|
${nixpkgs.libidn2.out}/lib/libidn2.a \
|
||||||
src = ./tests;
|
${nixpkgs.libunistring}/lib/libunistring.a \
|
||||||
buildInputs = [ (nixpkgs-dyn.python3.withPackages(p: [
|
${nixpkgs.libssh2}/lib/libssh2.a \
|
||||||
p.beautifulsoup4
|
${nixpkgs.zstd.out}/lib/libzstd.a \
|
||||||
p.requests
|
${nixpkgs.zlib}/lib/libz.a \
|
||||||
])) ];
|
${nixpkgs.openssl.out}/lib/libcrypto.a \
|
||||||
patchPhase = ''
|
${nixpkgs.curl.out}/lib/libcurl.a \
|
||||||
substituteInPlace tests.py --replace "./result/bin/knock" "${self.knock}/bin/knock"
|
${nixpkgs.openssl.out}/lib/libssl.a \
|
||||||
'';
|
-static-libgcc -static-libstdc++ \
|
||||||
installPhase = ''
|
-Wl,--end-group \
|
||||||
mkdir -p $out/bin
|
-I ${gourou-src}/utils \
|
||||||
cp tests.py $out/bin/tests
|
-I ${gourou-src}/include \
|
||||||
chmod +x $out/bin/tests
|
-I ${pugixml-src}/src \
|
||||||
'';
|
-I ${nixpkgs.openssl.dev}/include \
|
||||||
};
|
-I ${nixpkgs.curl.dev}/include \
|
||||||
};
|
-I ${nixpkgs.zlib.dev}/include \
|
||||||
}
|
-I ${self.libzip-static}/include
|
||||||
|
''
|
||||||
|
];
|
||||||
|
};
|
||||||
|
packages.default = self.knock;
|
||||||
|
packages.tests = nixpkgs-dyn.stdenv.mkDerivation {
|
||||||
|
name = "tests";
|
||||||
|
src = ./tests;
|
||||||
|
buildInputs = [
|
||||||
|
(nixpkgs-dyn.python3.withPackages (p: [
|
||||||
|
p.beautifulsoup4
|
||||||
|
p.requests
|
||||||
|
]))
|
||||||
|
];
|
||||||
|
patchPhase = ''
|
||||||
|
substituteInPlace tests.py --replace "./result/bin/knock" "${self.knock}/bin/knock"
|
||||||
|
'';
|
||||||
|
installPhase = ''
|
||||||
|
mkdir -p $out/bin
|
||||||
|
cp tests.py $out/bin/tests
|
||||||
|
chmod +x $out/bin/tests
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
packages.formatter = nixpkgs.writeShellScriptBin "formatter" ''
|
||||||
|
set -x
|
||||||
|
${nixpkgs-dyn.clang-tools}/bin/clang-format -i --verbose ./src/*.cpp
|
||||||
|
${nixpkgs-dyn.nixpkgs-fmt}/bin/nixpkgs-fmt .
|
||||||
|
${nixpkgs-dyn.black}/bin/black ./tests
|
||||||
|
'';
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
61
readme.md
61
readme.md
|
@ -2,13 +2,32 @@
|
||||||
|
|
||||||
Convert ACSM files to PDF/EPUBs with one command on Linux ([and MacOS very soon](https://github.com/BentonEdmondson/knock/issues/58)).
|
Convert ACSM files to PDF/EPUBs with one command on Linux ([and MacOS very soon](https://github.com/BentonEdmondson/knock/issues/58)).
|
||||||
|
|
||||||
|
![Demonstration of CLI usage](./assets/demo.png)
|
||||||
|
|
||||||
*This software does not utilize Adobe Digital Editions nor Wine. It is completely free and open-source software written natively for Linux.*
|
*This software does not utilize Adobe Digital Editions nor Wine. It is completely free and open-source software written natively for Linux.*
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
* Download the latest [release](https://github.com/BentonEdmondson/knock/releases). Make sure it is the correct version for your architecture (run `uname -m` to check).
|
1. Run `uname -ms` and, based on the output, download the latest corresponding [release](https://github.com/BentonEdmondson/knock/releases).
|
||||||
* Rename the binary and make it executable.
|
1. `cd` into the directory that knock is in (e.g. `cd ~/Downloads`).
|
||||||
* Run `knock /path/to/book.acsm` to perform the conversion.
|
1. Run `mv knock-version-arch-os knock` to rename the binary to `knock`.
|
||||||
|
1. Run `chmod +x knock` to make it executable.
|
||||||
|
1. Run `./knock ./path/to/book.acsm` to perform the conversion.
|
||||||
|
1. Run `mv knock ~/.local/bin` to allow it to be run from anywhere (might not work on some distributions).
|
||||||
|
|
||||||
|
### Nix and NixOS (with [flakes](https://nixos.wiki/wiki/Flakes) enabled)
|
||||||
|
|
||||||
|
If you are on a system with [Nix](https://github.com/NixOS/nix), you can use the following.
|
||||||
|
|
||||||
|
```
|
||||||
|
nix profile install github:BentonEdmondson/knock
|
||||||
|
```
|
||||||
|
|
||||||
|
If you are on [NixOS](https://github.com/NixOS/nixpkgs), you can add the flake to your system config.
|
||||||
|
|
||||||
|
## Dependencies
|
||||||
|
|
||||||
|
There are no userspace runtime dependencies.
|
||||||
|
|
||||||
## Verified Book Sources
|
## Verified Book Sources
|
||||||
|
|
||||||
|
@ -16,23 +35,6 @@ Knock should work on any ACSM file, but it has been specifically verified to wor
|
||||||
|
|
||||||
Before buying your ebook, check if it is available for free on [Project Gutenberg](https://gutenberg.org/).
|
Before buying your ebook, check if it is available for free on [Project Gutenberg](https://gutenberg.org/).
|
||||||
|
|
||||||
## The Name
|
|
||||||
|
|
||||||
The name comes from the [D&D 5e spell](https://roll20.net/compendium/dnd5e/Knock#content) for freeing locked items:
|
|
||||||
|
|
||||||
> ### Knock
|
|
||||||
> *2nd level transmutation*\
|
|
||||||
> **Casting Time**: 1 action\
|
|
||||||
> **Range**: 60 feet\
|
|
||||||
> **Components**: V\
|
|
||||||
> **Duration**: Instantaneous\
|
|
||||||
> **Classes**: Bard, Sorcerer, Wizard\
|
|
||||||
> Choose an object that you can see within range. The object can be a door, a box, a chest, a set of manacles, a padlock, or another object that contains a mundane or magical means that prevents access. A target that is held shut by a mundane lock or that is stuck or barred becomes unlocked, unstuck, or unbarred. If the object has multiple locks, only one of them is unlocked. If you choose a target that is held shut with arcane lock, that spell is suppressed for 10 minutes, during which time the target can be opened and shut normally. When you cast the spell, a loud knock, audible from as far away as 300 feet, emanates from the target object.
|
|
||||||
|
|
||||||
## Dependencies
|
|
||||||
|
|
||||||
There are no userspace runtime dependencies.
|
|
||||||
|
|
||||||
## Contributing
|
## Contributing
|
||||||
|
|
||||||
Install [Nix](https://github.com/NixOS/nix) if you don't have it. Enable [flakes](https://nixos.wiki/wiki/Flakes) if you haven't.
|
Install [Nix](https://github.com/NixOS/nix) if you don't have it. Enable [flakes](https://nixos.wiki/wiki/Flakes) if you haven't.
|
||||||
|
@ -57,6 +59,25 @@ nix run .#tests -- ./tests/workspace
|
||||||
|
|
||||||
Test books can be found [here](https://www.adobe.com/solutions/ebook/digital-editions/sample-ebook-library.html).
|
Test books can be found [here](https://www.adobe.com/solutions/ebook/digital-editions/sample-ebook-library.html).
|
||||||
|
|
||||||
|
### Formatting
|
||||||
|
|
||||||
|
```
|
||||||
|
nix run .#formatter
|
||||||
|
```
|
||||||
|
|
||||||
|
## The Name
|
||||||
|
|
||||||
|
The name comes from the [D&D 5e spell](https://roll20.net/compendium/dnd5e/Knock#content) for freeing locked items:
|
||||||
|
|
||||||
|
> ### Knock
|
||||||
|
> *2nd level transmutation*\
|
||||||
|
> **Casting Time**: 1 action\
|
||||||
|
> **Range**: 60 feet\
|
||||||
|
> **Components**: V\
|
||||||
|
> **Duration**: Instantaneous\
|
||||||
|
> **Classes**: Bard, Sorcerer, Wizard\
|
||||||
|
> Choose an object that you can see within range. The object can be a door, a box, a chest, a set of manacles, a padlock, or another object that contains a mundane or magical means that prevents access. A target that is held shut by a mundane lock or that is stuck or barred becomes unlocked, unstuck, or unbarred. If the object has multiple locks, only one of them is unlocked. If you choose a target that is held shut with arcane lock, that spell is suppressed for 10 minutes, during which time the target can be opened and shut normally. When you cast the spell, a loud knock, audible from as far away as 300 feet, emanates from the target object.
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
This software is licensed under GPLv3. The linked libraries have various licenses.
|
This software is licensed under GPLv3. The linked libraries have various licenses.
|
||||||
|
|
180
src/knock.cpp
180
src/knock.cpp
|
@ -1,119 +1,121 @@
|
||||||
#include <filesystem>
|
|
||||||
#include "drmprocessorclientimpl.h"
|
#include "drmprocessorclientimpl.h"
|
||||||
#include "libgourou_common.h"
|
|
||||||
#include "libgourou.h"
|
#include "libgourou.h"
|
||||||
|
#include "libgourou_common.h"
|
||||||
|
#include <filesystem>
|
||||||
|
|
||||||
#ifndef KNOCK_VERSION
|
#ifndef KNOCK_VERSION
|
||||||
#error KNOCK_VERSION must be defined
|
#error KNOCK_VERSION must be defined
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
std::string get_data_dir();
|
std::string get_data_dir();
|
||||||
void verify_absence(std::string file);
|
void verify_absence(std::string file);
|
||||||
void verify_presence(std::string file);
|
void verify_presence(std::string file);
|
||||||
|
|
||||||
int main(int argc, char** argv) try {
|
int main(int argc, char **argv) try {
|
||||||
|
|
||||||
if (argc == 1) {
|
if (argc == 1) {
|
||||||
std::cout
|
std::cout << "info: knock version " << KNOCK_VERSION
|
||||||
<< "info: knock version " << KNOCK_VERSION << ", libgourou version "
|
<< ", libgourou version " << LIBGOUROU_VERSION << "\n"
|
||||||
<< LIBGOUROU_VERSION << "\n"
|
<< "usage: " << argv[0] << " [ACSM]\n"
|
||||||
<< "usage: " << argv[0] << " [ACSM]" << "\n"
|
<< "result: converts file ACSM to a plain EPUB/PDF if present, "
|
||||||
<< "result: converts file ACSM to a plain EPUB/PDF if present, otherwise prints this"
|
"otherwise prints this"
|
||||||
<< std::endl;
|
<< std::endl;
|
||||||
return EXIT_SUCCESS;
|
return EXIT_SUCCESS;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (argc != 2) {
|
if (argc != 2) {
|
||||||
throw std::invalid_argument("the ACSM file must be passed as an argument");
|
throw std::invalid_argument("the ACSM file must be passed as an argument");
|
||||||
}
|
}
|
||||||
|
|
||||||
const std::string acsm_file = argv[1];
|
const std::string acsm_file = argv[1];
|
||||||
verify_presence(acsm_file);
|
verify_presence(acsm_file);
|
||||||
const std::string acsm_stem = acsm_file.substr(0, acsm_file.find_last_of("."));
|
const std::string acsm_stem =
|
||||||
const std::string drm_file = acsm_stem + ".drm";
|
acsm_file.substr(0, acsm_file.find_last_of("."));
|
||||||
const std::string out_file = acsm_stem + ".out";
|
const std::string drm_file = acsm_stem + ".drm";
|
||||||
verify_absence(drm_file);
|
const std::string out_file = acsm_stem + ".out";
|
||||||
verify_absence(out_file);
|
verify_absence(drm_file);
|
||||||
const std::string knock_data = get_data_dir();
|
verify_absence(out_file);
|
||||||
|
const std::string knock_data = get_data_dir();
|
||||||
|
|
||||||
DRMProcessorClientImpl client;
|
DRMProcessorClientImpl client;
|
||||||
gourou::DRMProcessor* processor = gourou::DRMProcessor::createDRMProcessor(
|
gourou::DRMProcessor *processor = gourou::DRMProcessor::createDRMProcessor(
|
||||||
&client,
|
&client,
|
||||||
false, // don't "always generate a new device" (default)
|
false, // don't "always generate a new device" (default)
|
||||||
knock_data
|
knock_data);
|
||||||
);
|
|
||||||
|
|
||||||
std::cout << "anonymously signing in..." << std::endl;
|
std::cout << "anonymously signing in..." << std::endl;
|
||||||
processor->signIn("anonymous", "");
|
processor->signIn("anonymous", "");
|
||||||
processor->activateDevice();
|
processor->activateDevice();
|
||||||
|
|
||||||
std::cout << "downloading the file from Adobe..." << std::endl;
|
std::cout << "downloading the file from Adobe..." << std::endl;
|
||||||
gourou::FulfillmentItem* item = processor->fulfill(acsm_file);
|
gourou::FulfillmentItem *item = processor->fulfill(acsm_file);
|
||||||
gourou::DRMProcessor::ITEM_TYPE type = processor->download(item, drm_file);
|
gourou::DRMProcessor::ITEM_TYPE type = processor->download(item, drm_file);
|
||||||
|
|
||||||
std::cout << "removing DRM from the file..." << std::endl;
|
|
||||||
std::string ext_file;
|
|
||||||
std::string file_type;
|
|
||||||
switch (type) {
|
|
||||||
case gourou::DRMProcessor::ITEM_TYPE::PDF: {
|
|
||||||
// for pdfs the function moves the pdf while removing drm
|
|
||||||
processor->removeDRM(drm_file, out_file, type, nullptr, 0);
|
|
||||||
std::filesystem::remove(drm_file);
|
|
||||||
ext_file = acsm_stem + ".pdf";
|
|
||||||
file_type = "PDF";
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case gourou::DRMProcessor::ITEM_TYPE::EPUB: {
|
|
||||||
// for epubs the drm is removed in-place so in == out
|
|
||||||
processor->removeDRM(drm_file, drm_file, type, nullptr, 0);
|
|
||||||
std::filesystem::rename(drm_file, out_file);
|
|
||||||
ext_file = acsm_stem + ".epub";
|
|
||||||
file_type = "EPUB";
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (std::filesystem::exists(ext_file)) {
|
std::cout << "removing DRM from the file..." << std::endl;
|
||||||
std::cerr
|
std::string ext_file;
|
||||||
<< "warning: failed to update file extension; " + ext_file + " already exists"
|
std::string file_type;
|
||||||
<< std::endl;
|
switch (type) {
|
||||||
ext_file = out_file;
|
case gourou::DRMProcessor::ITEM_TYPE::PDF: {
|
||||||
} else {
|
// for pdfs the function moves the pdf while removing drm
|
||||||
std::filesystem::rename(out_file, ext_file);
|
processor->removeDRM(drm_file, out_file, type, nullptr, 0);
|
||||||
}
|
std::filesystem::remove(drm_file);
|
||||||
|
ext_file = acsm_stem + ".pdf";
|
||||||
|
file_type = "PDF";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case gourou::DRMProcessor::ITEM_TYPE::EPUB: {
|
||||||
|
// for epubs the drm is removed in-place so in == out
|
||||||
|
processor->removeDRM(drm_file, drm_file, type, nullptr, 0);
|
||||||
|
std::filesystem::rename(drm_file, out_file);
|
||||||
|
ext_file = acsm_stem + ".epub";
|
||||||
|
file_type = "EPUB";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
std::cout << file_type + " file generated at " + ext_file << std::endl;
|
if (std::filesystem::exists(ext_file)) {
|
||||||
|
std::cerr << "warning: failed to update file extension; " + ext_file +
|
||||||
|
" already exists"
|
||||||
|
<< std::endl;
|
||||||
|
ext_file = out_file;
|
||||||
|
} else {
|
||||||
|
std::filesystem::rename(out_file, ext_file);
|
||||||
|
}
|
||||||
|
|
||||||
return 0;
|
std::filesystem::remove(acsm_file);
|
||||||
|
std::cout << file_type + " file generated at " + ext_file << std::endl;
|
||||||
|
|
||||||
} catch (const gourou::Exception& e) {
|
return 0;
|
||||||
std::cerr << "error:\n" << e.what();
|
|
||||||
return EXIT_FAILURE;
|
} catch (const gourou::Exception &e) {
|
||||||
} catch (const std::exception& e) {
|
std::cerr << "error:\n" << e.what();
|
||||||
std::cerr << "error: " << e.what() << std::endl;
|
return EXIT_FAILURE;
|
||||||
return EXIT_FAILURE;
|
} catch (const std::exception &e) {
|
||||||
|
std::cerr << "error: " << e.what() << std::endl;
|
||||||
|
return EXIT_FAILURE;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string get_data_dir() {
|
std::string get_data_dir() {
|
||||||
char* xdg_data_home = std::getenv("XDG_DATA_HOME");
|
char *xdg_data_home = std::getenv("XDG_DATA_HOME");
|
||||||
std::string knock_data;
|
std::string knock_data;
|
||||||
if (xdg_data_home != nullptr) {
|
if (xdg_data_home != nullptr) {
|
||||||
knock_data = xdg_data_home;
|
knock_data = xdg_data_home;
|
||||||
} else {
|
} else {
|
||||||
knock_data = std::string(std::getenv("HOME")) + "/.local/share";
|
knock_data = std::string(std::getenv("HOME")) + "/.local/share";
|
||||||
}
|
}
|
||||||
knock_data += "/knock/acsm";
|
knock_data += "/knock/acsm";
|
||||||
return knock_data;
|
return knock_data;
|
||||||
}
|
}
|
||||||
|
|
||||||
void verify_absence(std::string file) {
|
void verify_absence(std::string file) {
|
||||||
if (std::filesystem::exists(file)) {
|
if (std::filesystem::exists(file)) {
|
||||||
throw std::runtime_error("file " + file + " must be moved out of the way or deleted");
|
throw std::runtime_error("file " + file +
|
||||||
}
|
" must be moved out of the way or deleted");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void verify_presence(std::string file) {
|
void verify_presence(std::string file) {
|
||||||
if (!std::filesystem::exists(file)) {
|
if (!std::filesystem::exists(file)) {
|
||||||
throw std::runtime_error("file " + file + " does not exist");
|
throw std::runtime_error("file " + file + " does not exist");
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -14,9 +14,10 @@ if not knock.exists():
|
||||||
if len(sys.argv) != 2:
|
if len(sys.argv) != 2:
|
||||||
print(
|
print(
|
||||||
"error: missing required argument: directory in which to perform the tests",
|
"error: missing required argument: directory in which to perform the tests",
|
||||||
file=sys.stderr
|
file=sys.stderr,
|
||||||
)
|
)
|
||||||
sys.exit()
|
sys.exit()
|
||||||
|
workspace = Path(sys.argv[1])
|
||||||
|
|
||||||
print("Testing " + str(knock))
|
print("Testing " + str(knock))
|
||||||
|
|
||||||
|
@ -26,39 +27,44 @@ if result.returncode != 0:
|
||||||
sys.exit()
|
sys.exit()
|
||||||
print("---")
|
print("---")
|
||||||
|
|
||||||
workspace = Path(sys.argv[1])
|
html = requests.get(
|
||||||
if workspace.exists():
|
"https://www.adobe.com/solutions/ebook/digital-editions/sample-ebook-library.html"
|
||||||
shutil.rmtree(workspace)
|
).text
|
||||||
workspace.mkdir()
|
soup = BeautifulSoup(html, "html.parser")
|
||||||
|
|
||||||
html = requests \
|
|
||||||
.get("https://www.adobe.com/solutions/ebook/digital-editions/sample-ebook-library.html") \
|
|
||||||
.text
|
|
||||||
soup = BeautifulSoup(html, 'html.parser')
|
|
||||||
|
|
||||||
links = []
|
links = []
|
||||||
for a_tag in soup.find_all('a'):
|
for a_tag in soup.find_all("a"):
|
||||||
if a_tag.string != "Download eBook":
|
if a_tag.string != "Download eBook":
|
||||||
continue
|
continue
|
||||||
if not urlparse(a_tag.get("href")).path.endswith(".acsm"):
|
if not urlparse(a_tag.get("href")).path.endswith(".acsm"):
|
||||||
continue
|
continue
|
||||||
|
|
||||||
links.append(a_tag.get("href"))
|
links.append(a_tag.get("href"))
|
||||||
|
|
||||||
for i, link in enumerate(links):
|
if len(links) >= 10:
|
||||||
i = str(i)
|
break
|
||||||
print("Testing URL #" + i + ":\n" + link)
|
|
||||||
file = workspace.joinpath(i + ".acsm")
|
|
||||||
|
|
||||||
r = requests.get(link)
|
for time in ["first", "second"]:
|
||||||
open(file, "wb").write(r.content)
|
|
||||||
|
|
||||||
result = subprocess.run([knock, file])
|
if workspace.exists():
|
||||||
|
shutil.rmtree(workspace)
|
||||||
|
workspace.mkdir()
|
||||||
|
|
||||||
if result.returncode != 0:
|
for i, link in enumerate(links):
|
||||||
print("Test failed: knock failed to convert a file")
|
i = str(i)
|
||||||
sys.exit()
|
|
||||||
|
|
||||||
print("Success\n---")
|
print("Testing URL #" + i + " for the " + time + " time:\n" + link)
|
||||||
|
file = workspace.joinpath(i + ".acsm")
|
||||||
|
|
||||||
|
r = requests.get(link)
|
||||||
|
open(file, "wb").write(r.content)
|
||||||
|
|
||||||
|
result = subprocess.run([knock, file])
|
||||||
|
|
||||||
|
if result.returncode != 0:
|
||||||
|
print("Test failed: knock failed to convert a file")
|
||||||
|
sys.exit()
|
||||||
|
|
||||||
|
print("Success\n---")
|
||||||
|
|
||||||
print("All tests passed")
|
print("All tests passed")
|
||||||
|
|
Loading…
Reference in a new issue