Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file added .github/social-preview-nemo.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
38 changes: 38 additions & 0 deletions .github/social-preview-src/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# Social preview source

The repository preview is rendered at 1280×640 from three lossless sources:

- `background.png`: the original 1774×887 abstract event-horizon artwork.
- the canonical PDrive SVG icon under `share/icons/`.
- deterministic SVG typography and layout generated by `render.py`.
- output paths and distro or file-manager copy from `variants.json`.

The committed outputs are:

- `.github/social-preview.png`: distro-neutral GitHub repository preview.
- `.github/social-preview-nemo.png`: preserved Nemo-specific store artwork.

Render both variants with:

```console
make social-preview
```

Verify that the committed PNGs still match their sources with:

```console
make check-social-preview
```

Both commands process every profile in `variants.json`. Add another file-manager
or distribution profile there, including its repository-relative output path;
the background, icon, typography, and layout remain shared, and the new variant
automatically joins both the build and freshness check.

The background was generated specifically for PDrive with this prompt:

> Create an elegant abstract open-source technology backdrop suggesting a calm
> event horizon, secure cloud storage, and flowing data paths. Use deep
> near-black navy, indigo, restrained violet, cyan, and mint. Keep the left side
> dark and quiet for branding, and place a gentle luminous arc on the right. No
> text, letters, logos, icons, screenshots, UI, people, or watermark.
Binary file added .github/social-preview-src/background.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
38 changes: 38 additions & 0 deletions .github/social-preview-src/overlay.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
88 changes: 88 additions & 0 deletions .github/social-preview-src/render-all.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
#!/usr/bin/env python3
"""Render or verify every configured PDrive social-preview variant."""

from __future__ import annotations

import argparse
import json
import subprocess
import sys
import tempfile
from pathlib import Path


def parse_args() -> argparse.Namespace:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument(
"--check",
action="store_true",
help="fail if a committed preview differs from a fresh render",
)
return parser.parse_args()


def configured_output(repo_dir: Path, value: object) -> Path:
relative = Path(str(value))
if relative.is_absolute():
raise SystemExit(f"Preview output must be repository-relative: {relative}")

output = (repo_dir / relative).resolve()
try:
output.relative_to(repo_dir)
except ValueError as error:
raise SystemExit(f"Preview output escapes the repository: {relative}") from error
return output


def render(render_script: Path, variant: str, output: Path) -> None:
subprocess.run(
[
sys.executable,
str(render_script),
"--variant",
variant,
"--output",
str(output),
],
check=True,
)


def main() -> int:
args = parse_args()
source_dir = Path(__file__).resolve().parent
repo_dir = source_dir.parents[1].resolve()
render_script = source_dir / "render.py"
variants = json.loads((source_dir / "variants.json").read_text(encoding="utf-8"))

if not isinstance(variants, dict) or not variants:
raise SystemExit("At least one social-preview variant must be configured.")

if not args.check:
for name in sorted(variants):
output = configured_output(repo_dir, variants[name]["output"])
render(render_script, name, output)
return 0

stale: list[Path] = []
with tempfile.TemporaryDirectory(prefix="pdrive-social-preview-check-") as temporary:
check_dir = Path(temporary)
for name in sorted(variants):
committed = configured_output(repo_dir, variants[name]["output"])
candidate = check_dir / f"{name}.png"
render(render_script, name, candidate)
if not committed.is_file() or committed.read_bytes() != candidate.read_bytes():
stale.append(committed)

if stale:
for output in stale:
print(f"Stale social preview: {output.relative_to(repo_dir)}", file=sys.stderr)
print("Run make social-preview and commit the results.", file=sys.stderr)
return 1

print(f"All {len(variants)} social-preview variants are current.")
return 0


if __name__ == "__main__":
raise SystemExit(main())
118 changes: 118 additions & 0 deletions .github/social-preview-src/render.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
#!/usr/bin/env python3
"""Render a deterministic PDrive social-preview variant."""

from __future__ import annotations

import argparse
import html
import json
import shutil
import subprocess
import tempfile
from pathlib import Path

WIDTH = 1280
HEIGHT = 640


def parse_args(variants: list[str]) -> argparse.Namespace:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--variant", choices=variants, required=True)
parser.add_argument("--output", type=Path, required=True)
return parser.parse_args()


def image_magick() -> list[str]:
executable = shutil.which("magick") or shutil.which("convert")
if not executable:
raise SystemExit("ImageMagick is required (magick or convert).")
return [executable]


def main() -> int:
source_dir = Path(__file__).resolve().parent
repo_dir = source_dir.parents[1]
variants = json.loads((source_dir / "variants.json").read_text(encoding="utf-8"))
args = parse_args(sorted(variants))
variant = variants[args.variant]

background = source_dir / "background.png"
icon = repo_dir / "share/icons/hicolor/scalable/apps/io.github.claudiuschuster.PDriveControl.svg"
if not background.is_file() or not icon.is_file():
raise SystemExit("Social-preview background or PDrive icon is missing.")

badge_width = int(variant["badge_width"])
second_x = 76 + badge_width + 13
third_x = second_x + 208 + 13
replacements = {
"{{TAGLINE}}": html.escape(str(variant["tagline"])),
"{{BADGE}}": html.escape(str(variant["badge"])),
"{{BADGE_WIDTH}}": str(badge_width),
"{{BADGE_CENTER}}": str(76 + badge_width / 2),
"{{SECOND_X}}": str(second_x),
"{{SECOND_CENTER}}": str(second_x + 104),
"{{THIRD_X}}": str(third_x),
"{{THIRD_CENTER}}": str(third_x + 75),
}
overlay_text = (source_dir / "overlay.svg").read_text(encoding="utf-8")
for marker, value in replacements.items():
overlay_text = overlay_text.replace(marker, value)

command = image_magick()
output = args.output.resolve()
output.parent.mkdir(parents=True, exist_ok=True)
with tempfile.TemporaryDirectory(prefix="pdrive-social-preview-") as temporary:
work_dir = Path(temporary)
overlay_svg = work_dir / "overlay.svg"
overlay_png = work_dir / "overlay.png"
canvas = work_dir / "background.png"
icon_source = work_dir / "icon-source.png"
app_icon = work_dir / "icon.png"
overlay_svg.write_text(overlay_text, encoding="utf-8")

subprocess.run(
command + ["-background", "none", str(overlay_svg), str(overlay_png)],
check=True,
)
subprocess.run(
command + [str(background), "-resize", f"{WIDTH}x{HEIGHT}!", str(canvas)],
check=True,
)
subprocess.run(
command
+ [
"-background",
"none",
str(icon),
"-resize",
"256x256",
str(icon_source),
],
check=True,
)
subprocess.run(
command + [str(icon_source), "-resize", "142x142", str(app_icon)],
check=True,
)
subprocess.run(
command
+ [
str(canvas),
str(overlay_png),
"-composite",
str(app_icon),
"-geometry",
"+76+155",
"-composite",
"-strip",
str(output),
],
check=True,
)

print(f"Rendered {args.variant}: {output}")
return 0


if __name__ == "__main__":
raise SystemExit(main())
14 changes: 14 additions & 0 deletions .github/social-preview-src/variants.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"generic": {
"output": ".github/social-preview.png",
"tagline": "Native file-explorer access. Resilient uploads. Clear live insight.",
"badge": "FILE EXPLORER",
"badge_width": 178
},
"nemo": {
"output": ".github/social-preview-nemo.png",
"tagline": "Native Nemo access. Resilient uploads. Clear live insight.",
"badge": "NEMO NATIVE",
"badge_width": 158
}
}
Binary file added .github/social-preview.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
10 changes: 9 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
SHELL := /bin/bash
.DEFAULT_GOAL := help

.PHONY: help version check check-units check-display verify install install-with-proton-cli uninstall
.PHONY: help version check check-units check-display verify social-preview check-social-preview install install-with-proton-cli uninstall

help: ## Show this action-free command overview.
@printf '%s\n' \
Expand All @@ -11,6 +11,8 @@ help: ## Show this action-free command overview.
' make check-units Validate systemd user units only.' \
' make check-display Run GTK tests on the current display.' \
' make verify Run diff hygiene and the portable suite.' \
' make social-preview Render every configured social-preview variant.' \
' make check-social-preview Verify committed previews match their sources.' \
' make version Print the project version.' \
' make install Install or update user-local files.' \
' make install-with-proton-cli Also enable the optional Proton CLI updater.' \
Expand All @@ -33,6 +35,12 @@ verify: ## Check diff hygiene and run the portable repository suite.
git diff --check
$(MAKE) check

social-preview: ## Render every configured social-preview variant.
python3 .github/social-preview-src/render-all.py

check-social-preview: ## Verify committed previews match their sources.
python3 .github/social-preview-src/render-all.py --check

install: ## Install or update user-local project files.
./install.sh

Expand Down
4 changes: 3 additions & 1 deletion tests/check.sh
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,9 @@ done

for python_file in "${project_dir}/bin/pdrive-state" "${project_dir}/bin/pdrive-ui" \
"${project_dir}/libexec/pdrive-draft-recovery-auto" \
"${project_dir}/tests/test-draft-recovery.py"; do
"${project_dir}/tests/test-draft-recovery.py" \
"${project_dir}/.github/social-preview-src/render.py" \
"${project_dir}/.github/social-preview-src/render-all.py"; do
python3 - "${python_file}" <<'PY'
import pathlib
import sys
Expand Down
2 changes: 2 additions & 0 deletions tests/test-help.sh
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,8 @@ HOME="${test_home}" bash "${project_dir}/libexec/reauth-rclone-proton" --help >/
HOME="${test_home}" "${project_dir}/libexec/pdrive-auth-failure-guard" >/dev/null
HOME="${test_home}" "${project_dir}/libexec/pdrive-auth-failure-guard" --help >/dev/null
HOME="${test_home}" make -s -C "${project_dir}" help >/dev/null
HOME="${test_home}" python3 "${project_dir}/.github/social-preview-src/render.py" --help >/dev/null
HOME="${test_home}" python3 "${project_dir}/.github/social-preview-src/render-all.py" --help >/dev/null
HOME="${test_home}" make -s -C "${project_dir}" version >/dev/null
after="$(snapshot)"

Expand Down