diff --git a/Dockerfile b/Dockerfile index 84317881..ff1aac44 100644 --- a/Dockerfile +++ b/Dockerfile @@ -32,7 +32,7 @@ COPY --from=client /root/client/dist/ /root/client/dist/ COPY --from=server /root/server/dist/ /root/server/dist/ COPY ./Makefile ./Makefile -RUN apt update && apt install make git jo jq -y +RUN apt update && apt install make git jq -y RUN make dist \ && rm -rf server client diff --git a/client/src/components/package-details/MinimalPackageTable.tsx b/client/src/components/package-details/MinimalPackageTable.tsx index bc91289e..a15a0024 100644 --- a/client/src/components/package-details/MinimalPackageTable.tsx +++ b/client/src/components/package-details/MinimalPackageTable.tsx @@ -1,10 +1,10 @@ import { Table, Tbody, Th, Thead, Tr } from '@chakra-ui/react' import { FC } from 'react' -import PackageInfo from '../../types/package-info' +import { ArchDistroString } from '../../types/package-info' import MinimalPackageTableRow from './MinimalPackageTableRow' import { useTranslation } from 'react-i18next' -const MinimalPackageTable: FC<{ packages: (PackageInfo | string)[] }> = ({ +const MinimalPackageTable: FC<{ packages: (ArchDistroString | string)[] }> = ({ packages, }) => { const { t } = useTranslation() @@ -22,11 +22,8 @@ const MinimalPackageTable: FC<{ packages: (PackageInfo | string)[] }> = ({ {packages.map((pkg, i) => ( ))} diff --git a/client/src/components/package-details/MinimalPackageTableRow.tsx b/client/src/components/package-details/MinimalPackageTableRow.tsx index fdc70247..7a053376 100644 --- a/client/src/components/package-details/MinimalPackageTableRow.tsx +++ b/client/src/components/package-details/MinimalPackageTableRow.tsx @@ -11,13 +11,13 @@ import { useTranslation } from 'react-i18next' import { Link as Rlink } from 'react-router-dom' const getDescription = (nameWithDescription: string): string | null => { - return nameWithDescription.includes(':') + return nameWithDescription?.includes(':') ? nameWithDescription.split(':')[1].trim() : null } const getName = (nameWithDescription: string): string => { - return nameWithDescription.includes(':') + return nameWithDescription?.includes(':') ? nameWithDescription.split(':')[0] : nameWithDescription } diff --git a/client/src/components/package-details/PackageDetailsPage.tsx b/client/src/components/package-details/PackageDetailsPage.tsx index 9349b815..d00aa32b 100644 --- a/client/src/components/package-details/PackageDetailsPage.tsx +++ b/client/src/components/package-details/PackageDetailsPage.tsx @@ -1,7 +1,7 @@ import { Container, UseDisclosureProps } from '@chakra-ui/react' import { FC } from 'react' import { Helmet } from 'react-helmet' -import PackageInfo from '../../types/package-info' +import PackageInfo, { ArchDistroString } from '../../types/package-info' import HowToInstall from './HowToInstall' import PackageDependenciesModal from './PackageDependenciesModal' import PackageDetailsHeader from './PackageDetailsHeader' @@ -10,7 +10,7 @@ import PackageRequiredByModal from './PackageRequiredByModal' type PackageDetailsPageProps = { data: PackageInfo - allDependencies: string[] + allDependencies: ArchDistroString[] isMobile: boolean requiredByModal: UseDisclosureProps dependenciesModal: UseDisclosureProps diff --git a/client/src/components/packages/PackageTableRow.tsx b/client/src/components/packages/PackageTableRow.tsx index 3f71d997..da198715 100644 --- a/client/src/components/packages/PackageTableRow.tsx +++ b/client/src/components/packages/PackageTableRow.tsx @@ -53,7 +53,7 @@ const PackageTableRow: FC<{ pkg: PackageInfo; disabled?: boolean }> = ({ = ({ } > - {pkg.maintainers + {(pkg.maintainers ?? []) .map(maintainer => maintainer.split('<')[0].trim()) .join(', ') || t('packageDetails.orphaned')} @@ -81,7 +81,7 @@ const PackageTableRow: FC<{ pkg: PackageInfo; disabled?: boolean }> = ({ diff --git a/client/src/types/package-dependencies.ts b/client/src/types/package-dependencies.ts index dc0836cc..af9cb7e2 100644 --- a/client/src/types/package-dependencies.ts +++ b/client/src/types/package-dependencies.ts @@ -1,8 +1,8 @@ -import PackageInfo from './package-info' +import PackageInfo, { ArchDistroString } from './package-info' export default interface PackageDependencies { - runtimeDependencies: (PackageInfo | string)[] - buildDependencies: (PackageInfo | string)[] - optionalDependencies: (PackageInfo | string)[] - pacstallDependencies: (PackageInfo | string)[] + runtimeDependencies: (ArchDistroString | string)[] + buildDependencies: (ArchDistroString | string)[] + optionalDependencies: (ArchDistroString | string)[] + pacstallDependencies: (ArchDistroString | string)[] } diff --git a/client/src/types/package-info.ts b/client/src/types/package-info.ts index 9309379e..126d0d9c 100644 --- a/client/src/types/package-info.ts +++ b/client/src/types/package-info.ts @@ -1,24 +1,50 @@ export default interface PackageInfo { + architectures: string[] version: string packageName: string maintainers: string[] description: string - source: string[] - runtimeDependencies: string[] - buildDependencies: string[] - optionalDependencies: string[] - conflicts: string[] - gives: string - replaces: string[] - hash?: string - ppa: string[] - pacstallDependencies: string[] - patch: string[] + source: ArchDistroString[] + runtimeDependencies: ArchDistroString[] + buildDependencies: ArchDistroString[] + optionalDependencies: ArchDistroString[] + checkDependencies: ArchDistroString[] + pacstallDependencies: ArchDistroString[] + conflicts: ArchDistroString[] + gives: ArchDistroString + replaces: ArchDistroString[] + sha1sums: ArchDistroString[] + sha224sums: ArchDistroString[] + sha256sums: ArchDistroString[] + sha384sums: ArchDistroString[] + sha512sums: ArchDistroString[] + md5sums: ArchDistroString[] + priority: ArchDistroString[] requiredBy: string[] + suggests: ArchDistroString[] + recommends: ArchDistroString[] latestVersion?: string prettyName: string updateStatus: UpdateStatus lastUpdatedAt: string + enhances: ArchDistroString[] + changelog: string + backup: string[] + compatible: string[] + incompatible: string[] + epoch: string + install: string + license: string[] + mask: string[] + noExtract: string[] + validPgpKeys: string[] + groups: string[] +} + +export interface ArchDistroString { + arch?: string + distro?: string + value: string } export enum UpdateStatus { diff --git a/server/consts/consts.go b/server/consts/consts.go index fa4a9d40..486d06fd 100644 --- a/server/consts/consts.go +++ b/server/consts/consts.go @@ -1,3 +1,3 @@ package consts -const PACSCRIPT_FILE_EXTENSION = "pacscript" +const SRCINFO_FILE_EXTENSION = ".SRCINFO" diff --git a/server/fixtures/test-programs/packages/clipboard-bin/clipboard-bin.pacscript b/server/fixtures/test-programs/packages/clipboard-bin/clipboard-bin.pacscript deleted file mode 100644 index 74d35f2b..00000000 --- a/server/fixtures/test-programs/packages/clipboard-bin/clipboard-bin.pacscript +++ /dev/null @@ -1,35 +0,0 @@ -# __ __________ ______ -# / \ / \_____ \ / __ \ -# \ \/\/ // ____/ > < -# \ // \/ -- \ -# \__/\ / \_______ \______ / -# \/ \/ \/ -maintainer=("wizard-28 ") - -pkgname="clipboard-bin" -gives="clipboard" -pkgver="0.9.0.1" -pkgdesc="Cut, copy, and paste anything in your terminal" -gives="${gives}" -conflicts=("${gives}" "${gives}-git" "${gives}-deb" "${gives}-app") -arch=("amd64" "arm64" "armhf" "ppc64el" "riscv64") -repology=("project: clipboard") -source=("https://github.com/Slackadays/Clipboard/releases/download/${pkgver}/${gives}-linux-${CARCH}.zip") -sha256sums_amd64=("5b90cd7299c1c0d679cfe8c1bd4e89e7fd70ebede2890d90a6f1da98a90e922b") -sha256sums_arm64=("07493b5e9954585160fc54314e23e4897652f06594f6ec7ceba66b32d7f72b82") -sha256sums_armhf=("7654d6f5176e554ed86d84f16924b2ec3d7a7e0000f24a43ee6772397b986dea") -sha256sums_ppc64el=("a7c2c689a777d57fe6638a469c408753d1b4d5d61c8fecd141a4781f54a24e7a") -sha256sums_riscv64=("e92f2c4eeeefd093d25f91f186c1c3ac572ea254369fe7028928246d431407c8") - - -package() { - cd "${_archive}" - if [[ ${CARCH} == "amd64" ]]; then - sudo install -Dm 755 "lib/libcbwayland.so" "${pkgdir}/usr/lib/libcbwayland.so" - fi - sudo install -Dm 755 "lib/libcbx11.so" "${pkgdir}/usr/lib/libcbx11.so" - - sudo install -Dm 755 "bin/cb" "${pkgdir}/usr/bin/cb" -} - -# vim:set ft=sh ts=2 sw=2 et: diff --git a/server/fixtures/test-programs/packages/clipboard-bin/clipboard-bin.snapshot.json b/server/fixtures/test-programs/packages/clipboard-bin/clipboard-bin.snapshot.json deleted file mode 100644 index d5af1e11..00000000 --- a/server/fixtures/test-programs/packages/clipboard-bin/clipboard-bin.snapshot.json +++ /dev/null @@ -1,35 +0,0 @@ -{ - "prettyName": "Clipboard", - "version": "0.9.0.1", - "latestVersion": null, - "packageName": "clipboard-bin", - "maintainers": [ - "wizard-28 \u003cwiz28@pm.me\u003e" - ], - "description": "Cut, copy, and paste anything in your terminal", - "source": [ - "https://github.com/Slackadays/Clipboard/releases/download/0.9.0.1/clipboard-linux-amd64.zip" - ], - "runtimeDependencies": [], - "buildDependencies": [], - "optionalDependencies": [], - "conflicts": [ - "clipboard", - "clipboard-git", - "clipboard-deb", - "clipboard-app" - ], - "breaks": [], - "gives": "clipboard", - "replaces": [], - "hash": null, - "ppa": [], - "pacstallDependencies": [], - "patch": [], - "repology": [ - "project: clipboard" - ], - "requiredBy": [], - "lastUpdatedAt": "0001-01-01T00:00:00Z", - "updateStatus": -1 -} \ No newline at end of file diff --git a/server/fixtures/test-programs/packages/clipboard-with-comments-bin/clipboard-with-comments-bin.pacscript b/server/fixtures/test-programs/packages/clipboard-with-comments-bin/clipboard-with-comments-bin.pacscript deleted file mode 100644 index c75b83d7..00000000 --- a/server/fixtures/test-programs/packages/clipboard-with-comments-bin/clipboard-with-comments-bin.pacscript +++ /dev/null @@ -1,38 +0,0 @@ -# __ __________ ______ -# / \ / \_____ \ / __ \ -# \ \/\/ // ____/ > < -# \ // \/ -- \ -# \__/\ / \_______ \______ / -# \/ \/ \/ -maintainer=("wizard-28 ") - -pkgname="clipboard-bin" -gives="clipboard" -pkgver="0.9.0.1" -pkgdesc="Cut, copy, and paste anything in your terminal" -gives="${gives}" -conflicts=("${gives}" "${gives}-git" "${gives}-deb" "${gives}-app") -arch=("amd64" "arm64" "armhf" "ppc64el" "riscv64") -repology=("project: clipboard") -optdepends=("gnome-disk-utility" - "epiphany-browser: Default browser CMD if not set" - "gnome-control-center: Default wifi CMD if not set") -source=("https://github.com/Slackadays/Clipboard/releases/download/${pkgver}/${gives}-linux-${CARCH}.zip") -sha256sums_amd64=("5b90cd7299c1c0d679cfe8c1bd4e89e7fd70ebede2890d90a6f1da98a90e922b") -sha256sums_arm64=("07493b5e9954585160fc54314e23e4897652f06594f6ec7ceba66b32d7f72b82") -sha256sums_armhf=("7654d6f5176e554ed86d84f16924b2ec3d7a7e0000f24a43ee6772397b986dea") -sha256sums_ppc64el=("a7c2c689a777d57fe6638a469c408753d1b4d5d61c8fecd141a4781f54a24e7a") -sha256sums_riscv64=("e92f2c4eeeefd093d25f91f186c1c3ac572ea254369fe7028928246d431407c8") - - -package() { - cd "${_archive}" - if [[ ${CARCH} == "amd64" ]]; then - sudo install -Dm 755 "lib/libcbwayland.so" "${pkgdir}/usr/lib/libcbwayland.so" - fi - sudo install -Dm 755 "lib/libcbx11.so" "${pkgdir}/usr/lib/libcbx11.so" - - sudo install -Dm 755 "bin/cb" "${pkgdir}/usr/bin/cb" -} - -# vim:set ft=sh ts=2 sw=2 et: diff --git a/server/fixtures/test-programs/packages/clipboard-with-comments-bin/clipboard-with-comments-bin.snapshot.json b/server/fixtures/test-programs/packages/clipboard-with-comments-bin/clipboard-with-comments-bin.snapshot.json deleted file mode 100644 index 7e1c2111..00000000 --- a/server/fixtures/test-programs/packages/clipboard-with-comments-bin/clipboard-with-comments-bin.snapshot.json +++ /dev/null @@ -1,39 +0,0 @@ -{ - "prettyName": "Clipboard", - "version": "0.9.0.1", - "latestVersion": null, - "packageName": "clipboard-bin", - "maintainers": [ - "wizard-28 \u003cwiz28@pm.me\u003e" - ], - "description": "Cut, copy, and paste anything in your terminal", - "source": [ - "https://github.com/Slackadays/Clipboard/releases/download/0.9.0.1/clipboard-linux-amd64.zip" - ], - "runtimeDependencies": [], - "buildDependencies": [], - "optionalDependencies": [ - "gnome-disk-utility", - "epiphany-browser: Default browser CMD if not set", - "gnome-control-center: Default wifi CMD if not set" - ], - "conflicts": [ - "clipboard", - "clipboard-git", - "clipboard-deb", - "clipboard-app" - ], - "breaks": [], - "gives": "clipboard", - "replaces": [], - "hash": null, - "ppa": [], - "pacstallDependencies": [], - "patch": [], - "repology": [ - "project: clipboard" - ], - "requiredBy": [], - "lastUpdatedAt": "0001-01-01T00:00:00Z", - "updateStatus": -1 -} \ No newline at end of file diff --git a/server/fixtures/test-programs/packages/font-downloader/font-downloader.pacscript b/server/fixtures/test-programs/packages/font-downloader/font-downloader.pacscript deleted file mode 100644 index 856042f5..00000000 --- a/server/fixtures/test-programs/packages/font-downloader/font-downloader.pacscript +++ /dev/null @@ -1,21 +0,0 @@ -pkgname="font-downloader" -gives="font-downloader" -pkgver="10.0.0" -makedepends=("meson" "libhandy-1-dev" "gettext") -# HACK: https://github.com/pacstall/pacstall/issues/727 -depends=("python3-gi" "libhandy-1-dev") -source=("https://github.com/GustavoPeredo/Font-Downloader/archive/refs/tags/v${pkgver}.zip") -repology=("project: fontdownloader") -pkgdesc="Install fonts from online sources" -sha256sums=("eeafd4ac9cb0d47fd0c1512e07805d0f7a639cdbbc688647249eaee8d1753e23") -maintainer=("সৌম্যদীপ ") - -build() { - cd "${_archive}" - meson --prefix=/usr build - ninja -C build -} -package() { - cd "${_archive}" - DESTDIR="${pkgdir}" ninja -C build install -} \ No newline at end of file diff --git a/server/fixtures/test-programs/packages/font-downloader/font-downloader.snapshot.json b/server/fixtures/test-programs/packages/font-downloader/font-downloader.snapshot.json deleted file mode 100644 index dad185e4..00000000 --- a/server/fixtures/test-programs/packages/font-downloader/font-downloader.snapshot.json +++ /dev/null @@ -1,37 +0,0 @@ -{ - "prettyName": "Font Downloader", - "version": "10.0.0", - "latestVersion": null, - "packageName": "font-downloader", - "maintainers": [ - "সৌম্যদীপ \u003csoumyadeepghosh2004@zohomail.in\u003e" - ], - "description": "Install fonts from online sources", - "source": [ - "https://github.com/GustavoPeredo/Font-Downloader/archive/refs/tags/v10.0.0.zip" - ], - "runtimeDependencies": [ - "python3-gi", - "libhandy-1-dev" - ], - "buildDependencies": [ - "meson", - "libhandy-1-dev", - "gettext" - ], - "optionalDependencies": [], - "conflicts": [], - "breaks": [], - "gives": "font-downloader", - "replaces": [], - "hash": null, - "ppa": [], - "pacstallDependencies": [], - "patch": [], - "repology": [ - "project: fontdownloader" - ], - "requiredBy": [], - "lastUpdatedAt": "0001-01-01T00:00:00Z", - "updateStatus": -1 -} \ No newline at end of file diff --git a/server/fixtures/test-programs/packages/rhino-setup-git/rhino-setup-git.pacscript b/server/fixtures/test-programs/packages/rhino-setup-git/rhino-setup-git.pacscript deleted file mode 100644 index fab6124e..00000000 --- a/server/fixtures/test-programs/packages/rhino-setup-git/rhino-setup-git.pacscript +++ /dev/null @@ -1,40 +0,0 @@ -pkgname="rhino-setup-git" -source=("https://github.com/rhino-linux/rhino-setup.git") -pkgver="2023.3" -pkgrel="2" -makedepends=("libgtk-4-dev" "libadwaita-1-dev" "gettext" "desktop-file-utils" "rustc" "cargo" "meson" "ninja-build") -depends=("libgtk-4-dev" "libadwaita-1-dev" "gettext" "desktop-file-utils") -gives="rhino-setup" -replaces="${gives}-bin" -pkgdesc="Rhino Linux Setup Prompt" -maintainer=("Oren Klopfer ") -incompatible=("debian:*") - -build() { - cd "${_archive}" - sudo meson build -} - -package() { - cd "${_archive}" - sudo DESTDIR="${pkgdir}" ninja -C build install -} - -post_install() { - for i in "${homedir}" "/etc/skel"; do - if ! [[ -d "${i}/.config/autostart" ]]; then - mkdir -p "${i}/.config/autostart" - fi - if ! [[ -f "${i}/.config/autostart/rhino-setup.desktop" ]]; then - sudo ln -sf "/usr/local/share/applications/org.rhinolinux.RhinoSetup.desktop" "${i}/.config/autostart/rhino-setup.desktop" - fi - done -} - -post_remove() { - for i in "${homedir}" "/etc/skel"; do - if [[ -L "${i}/.config/autostart/rhino-setup.desktop" ]]; then - sudo rm -f "${i}/.config/autostart/rhino-setup.desktop" - fi - done -} diff --git a/server/fixtures/test-programs/packages/rhino-setup-git/rhino-setup-git.snapshot.json b/server/fixtures/test-programs/packages/rhino-setup-git/rhino-setup-git.snapshot.json deleted file mode 100644 index 0f698d94..00000000 --- a/server/fixtures/test-programs/packages/rhino-setup-git/rhino-setup-git.snapshot.json +++ /dev/null @@ -1,44 +0,0 @@ -{ - "prettyName": "Rhino Setup", - "version": "2023.3", - "latestVersion": null, - "packageName": "rhino-setup-git", - "maintainers": [ - "Oren Klopfer \u003coren@taumoda.com\u003e" - ], - "description": "Rhino Linux Setup Prompt", - "source": [ - "https://github.com/rhino-linux/rhino-setup.git" - ], - "runtimeDependencies": [ - "libgtk-4-dev", - "libadwaita-1-dev", - "gettext", - "desktop-file-utils" - ], - "buildDependencies": [ - "libgtk-4-dev", - "libadwaita-1-dev", - "gettext", - "desktop-file-utils", - "rustc", - "cargo", - "meson", - "ninja-build" - ], - "optionalDependencies": [], - "conflicts": [], - "breaks": [], - "gives": "rhino-setup", - "replaces": [ - "rhino-setup-bin" - ], - "hash": null, - "ppa": [], - "pacstallDependencies": [], - "patch": [], - "repology": [], - "requiredBy": [], - "lastUpdatedAt": "0001-01-01T00:00:00Z", - "updateStatus": -1 -} \ No newline at end of file diff --git a/server/fixtures/test-programs/packages/sample-valid-deb/sample-valid-deb.pacscript b/server/fixtures/test-programs/packages/sample-valid-deb/sample-valid-deb.pacscript deleted file mode 100644 index 6ef871b4..00000000 --- a/server/fixtures/test-programs/packages/sample-valid-deb/sample-valid-deb.pacscript +++ /dev/null @@ -1,20 +0,0 @@ -pkgname="sample-valid-deb" -gives="sample-valid" -repology=("project: sample-valid") -pkgver="1.0.0" -source="https://example.com" -pkgdesc="Sample description" -hash="10101010" -arch=('amd64') -makedepends=("go" "gcc") -optdepends=("opt1" "opt2") -pacdeps=("pacdep1" "pacdep2") -depends=("dep1" "dep2") -ppa=("ppa1" "ppa2") -patch=("patch1" "patch2") -provides=("provides1" "provides2") -incompatible=("incompatible1" "incompatible2") -maintainer=("1234 ") -breaks=("breaks1" "breaks2") -conflicts=("conflicts1" "conflicts2") -replaces=("replaces1" "replaces2") diff --git a/server/fixtures/test-programs/packages/sample-valid-deb/sample-valid-deb.snapshot.json b/server/fixtures/test-programs/packages/sample-valid-deb/sample-valid-deb.snapshot.json deleted file mode 100644 index a660408a..00000000 --- a/server/fixtures/test-programs/packages/sample-valid-deb/sample-valid-deb.snapshot.json +++ /dev/null @@ -1,57 +0,0 @@ -{ - "prettyName": "Sample Valid", - "version": "1.0.0", - "latestVersion": null, - "packageName": "sample-valid-deb", - "maintainers": [ - "1234 \u003ctest@pacstall.dev\u003e" - ], - "description": "Sample description", - "source": [ - "https://example.com" - ], - "runtimeDependencies": [ - "dep1", - "dep2" - ], - "buildDependencies": [ - "go", - "gcc" - ], - "optionalDependencies": [ - "opt1", - "opt2" - ], - "conflicts": [ - "conflicts1", - "conflicts2" - ], - "breaks": [ - "breaks1", - "breaks2" - ], - "gives": "sample-valid", - "replaces": [ - "replaces1", - "replaces2" - ], - "hash": "10101010", - "ppa": [ - "ppa1", - "ppa2" - ], - "pacstallDependencies": [ - "pacdep1", - "pacdep2" - ], - "patch": [ - "patch1", - "patch2" - ], - "repology": [ - "project: sample-valid" - ], - "requiredBy": [], - "lastUpdatedAt": "0001-01-01T00:00:00Z", - "updateStatus": -1 -} \ No newline at end of file diff --git a/server/fixtures/test-programs/packages/sample-valid-with-pkgver-func-deb/sample-valid-with-pkgver-func-deb.pacscript b/server/fixtures/test-programs/packages/sample-valid-with-pkgver-func-deb/sample-valid-with-pkgver-func-deb.pacscript deleted file mode 100644 index b75845f6..00000000 --- a/server/fixtures/test-programs/packages/sample-valid-with-pkgver-func-deb/sample-valid-with-pkgver-func-deb.pacscript +++ /dev/null @@ -1,23 +0,0 @@ -pkgname="sample-valid-deb" -gives="sample-valid" -repology=("project: sample-valid") -source=("https://example.com") -pkgdesc="Sample description" -hash="10101010" -arch=('amd64') -makedepends=("go" "gcc") -optdepends=("opt1" "opt2") -pacdeps=("pacdep1" "pacdep2") -depends=("dep1" "dep2") -ppa=("ppa1" "ppa2") -patch=("patch1" "patch2") -provides=("provides1" "provides2") -incompatible=("incompatible1" "incompatible2") -maintainer=("pacstall ") -breaks=("breaks1" "breaks2") -conflicts=("conflicts1" "conflicts2") -replaces=("replaces1" "replaces2") - -pkgver() { - echo "1.2.3" -} \ No newline at end of file diff --git a/server/fixtures/test-programs/packages/sample-valid-with-pkgver-func-deb/sample-valid-with-pkgver-func-deb.snapshot.json b/server/fixtures/test-programs/packages/sample-valid-with-pkgver-func-deb/sample-valid-with-pkgver-func-deb.snapshot.json deleted file mode 100644 index eb11e970..00000000 --- a/server/fixtures/test-programs/packages/sample-valid-with-pkgver-func-deb/sample-valid-with-pkgver-func-deb.snapshot.json +++ /dev/null @@ -1,57 +0,0 @@ -{ - "prettyName": "Sample Valid", - "version": "1.2.3", - "latestVersion": null, - "packageName": "sample-valid-deb", - "maintainers": [ - "pacstall \u003ctest@pacstall.dev\u003e" - ], - "description": "Sample description", - "source": [ - "https://example.com" - ], - "runtimeDependencies": [ - "dep1", - "dep2" - ], - "buildDependencies": [ - "go", - "gcc" - ], - "optionalDependencies": [ - "opt1", - "opt2" - ], - "conflicts": [ - "conflicts1", - "conflicts2" - ], - "breaks": [ - "breaks1", - "breaks2" - ], - "gives": "sample-valid", - "replaces": [ - "replaces1", - "replaces2" - ], - "hash": "10101010", - "ppa": [ - "ppa1", - "ppa2" - ], - "pacstallDependencies": [ - "pacdep1", - "pacdep2" - ], - "patch": [ - "patch1", - "patch2" - ], - "repology": [ - "project: sample-valid" - ], - "requiredBy": [], - "lastUpdatedAt": "0001-01-01T00:00:00Z", - "updateStatus": -1 -} \ No newline at end of file diff --git a/server/go.mod b/server/go.mod index b6a98d74..6b99c7f4 100644 --- a/server/go.mod +++ b/server/go.mod @@ -1,6 +1,8 @@ module pacstall.dev/webserver -go 1.18 +go 1.22.2 + +toolchain go1.22.6 require ( github.com/fatih/color v1.17.0 // for colorizing output @@ -29,6 +31,7 @@ require ( github.com/jinzhu/now v1.1.5 // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.20 // indirect + github.com/pacstall/go-srcinfo v0.0.0-20240916164747-6f75fe362117 // indirect golang.org/x/net v0.27.0 // indirect golang.org/x/sys v0.22.0 // indirect golang.org/x/text v0.16.0 // indirect diff --git a/server/go.sum b/server/go.sum index d95fb57e..25891e12 100644 --- a/server/go.sum +++ b/server/go.sum @@ -41,6 +41,8 @@ github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovk github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/pacstall/go-srcinfo v0.0.0-20240916164747-6f75fe362117 h1:UVCtjWIdoAxLgASvjvuDKcKfRq1bmy5nGOIBinLZ9vE= +github.com/pacstall/go-srcinfo v0.0.0-20240916164747-6f75fe362117/go.mod h1:0LUf6eSIfP1kloShefbkOeYgk8aUbkeDuN955aq1jxY= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= diff --git a/server/model/connection.go b/server/model/connection.go index c460120c..614d7f55 100644 --- a/server/model/connection.go +++ b/server/model/connection.go @@ -2,11 +2,13 @@ package model import ( "fmt" + "time" "gorm.io/driver/mysql" "gorm.io/gorm" "gorm.io/gorm/logger" "pacstall.dev/webserver/config" + "pacstall.dev/webserver/log" ) var database *gorm.DB = nil @@ -23,20 +25,39 @@ func Instance() *gorm.DB { return database } - db, err := gorm.Open(mysql.Open(connectionString), &gorm.Config{ - Logger: logger.Default.LogMode(logger.Silent), + err := retry(5, func() (err error) { + database, err = gorm.Open(mysql.Open(connectionString), &gorm.Config{ + Logger: logger.Default.LogMode(logger.Silent), + }) + + return }) if err != nil { panic(fmt.Sprintf("failed to connect database: %v", err)) } + log.Info("connected to database.") + defer postConnect() - database = db return database } +func retry(trials int, fn func() error) error { + var err error + for i := 0; i < trials; i += 1 { + if err = fn(); err != nil { + log.Warn("failed to connect to database. retrying...") + time.Sleep(3 * time.Second) + } else { + return nil + } + } + + return err +} + func postConnect() { database.AutoMigrate(&ShortenedLink{}) } diff --git a/server/repology/internal/api.go b/server/repology/internal/api.go index abaa95f2..6201a441 100644 --- a/server/repology/internal/api.go +++ b/server/repology/internal/api.go @@ -5,6 +5,8 @@ import ( "io" "net/http" "net/url" + + "github.com/joomcode/errorx" ) const _USER_AGENT = "Pacstall/WebServer/Exporter" @@ -23,17 +25,21 @@ func getProjectSearch(projectName string) (RepologyApiProjectSearchResponse, err resp, err := http.DefaultClient.Do(&request) if err != nil { - return response, err + return response, errorx.Decorate(err, "http request failed %+v", request) } body, err := io.ReadAll(resp.Body) if err != nil { - return response, err + return response, errorx.Decorate(err, "failed to read request body") + } + + if resp.StatusCode < 200 || resp.StatusCode >= 300 { + return response, errorx.RejectedOperation.New("http request failed with status code %v. body \n%v\n", resp.StatusCode, string(body)) } err = json.Unmarshal(body, &response) if err != nil { - return response, err + return response, errorx.Decorate(err, "failed to unmarshal response body '%v'", string(body)) } return response, err diff --git a/server/repology/internal/exporter.go b/server/repology/internal/exporter.go index 7a72cee7..a55ecd02 100644 --- a/server/repology/internal/exporter.go +++ b/server/repology/internal/exporter.go @@ -6,11 +6,14 @@ import ( "strings" "time" + "github.com/joomcode/errorx" "gorm.io/gorm" "pacstall.dev/webserver/log" "pacstall.dev/webserver/model" ) +const RETRY_COUNT = 5 + func ExportRepologyDatabase(db *gorm.DB) error { err := migrateTables(db) if err != nil { @@ -20,7 +23,7 @@ func ExportRepologyDatabase(db *gorm.DB) error { it := 1 lastProjectName := "" - const REPOLOGY_PROJECT_FETCH_THROTTLE = 400 * time.Millisecond + const REPOLOGY_PROJECT_FETCH_THROTTLE = time.Second lastRepoFetch := time.Now() @@ -30,9 +33,24 @@ func ExportRepologyDatabase(db *gorm.DB) error { } log.Debug("page %v | cursor at: %v", it, lastProjectName) - projectPage, err := getProjectSearch(lastProjectName) + + var projectPage map[string][]RepologyApiProject + var err error + + retry: + for i := 0; i < RETRY_COUNT; i += 1 { + projectPage, err = getProjectSearch(lastProjectName) + if err == nil { + break retry + } + + retryDelay := time.Duration(i+1) * REPOLOGY_PROJECT_FETCH_THROTTLE + log.Debug("failed to fetch repology project page '%s'. retrying in %v", lastProjectName, retryDelay) + time.Sleep(retryDelay) + } + if err != nil { - return errors.Join(errors.New("failed to fetch repology project page"), err) + return errorx.Decorate(err, "failed to fetch repology project page '%s'", lastProjectName) } lastRepoFetch = time.Now() diff --git a/server/repology/scheduler.go b/server/repology/scheduler.go index 74324a07..b917befb 100644 --- a/server/repology/scheduler.go +++ b/server/repology/scheduler.go @@ -15,7 +15,7 @@ func ScheduleRefresh(every time.Duration) { log.Info("refreshing Repology database...") err := ExportRepologyDatabase(db) if err != nil { - log.Error("failed to export Repology projects: %v", err) + log.Error("failed to export Repology projects: %+v", err) } else { log.Info("repology database refreshed successfully") } diff --git a/server/server/api/pacscripts/dependencies.go b/server/server/api/pacscripts/dependencies.go index 2aa2cc25..07ddc522 100644 --- a/server/server/api/pacscripts/dependencies.go +++ b/server/server/api/pacscripts/dependencies.go @@ -5,7 +5,6 @@ import ( "net/http" "github.com/gorilla/mux" - "pacstall.dev/webserver/log" "pacstall.dev/webserver/server" "pacstall.dev/webserver/types/array" "pacstall.dev/webserver/types/pac" @@ -13,10 +12,10 @@ import ( ) type pacscriptDependencies struct { - RuntimeDependencies []string `json:"runtimeDependencies"` - BuildDependencies []string `json:"buildDependencies"` - OptionalDependencies []string `json:"optionalDependencies"` - PacstallDependencies []*pac.Script `json:"pacstallDependencies"` + RuntimeDependencies []pac.ArchDistroString `json:"runtimeDependencies"` + BuildDependencies []pac.ArchDistroString `json:"buildDependencies"` + OptionalDependencies []pac.ArchDistroString `json:"optionalDependencies"` + PacstallDependencies []pac.ArchDistroString `json:"pacstallDependencies"` } func GetPacscriptDependenciesHandle(w http.ResponseWriter, req *http.Request) { @@ -45,20 +44,11 @@ func GetPacscriptDependenciesHandle(w http.ResponseWriter, req *http.Request) { return } - pacstallDependencies := make([]*pac.Script, 0) - for _, pkg := range pacpkg.PacstallDependencies { - if found, err := array.FindBy(allPacscripts, func(pi *pac.Script) bool { return pkg == pi.PackageName }); err == nil { - pacstallDependencies = append(pacstallDependencies, found) - } else { - log.Error("could not find pacstall dependency %s of package %s.\n", pkg, pacpkg.PackageName) - } - } - response := pacscriptDependencies{ RuntimeDependencies: pacpkg.RuntimeDependencies, BuildDependencies: pacpkg.BuildDependencies, OptionalDependencies: pacpkg.OptionalDependencies, - PacstallDependencies: pacstallDependencies, + PacstallDependencies: pacpkg.PacstallDependencies, } server.Json(w, response) diff --git a/server/server/api/repology/types.go b/server/server/api/repology/types.go index 98d08fe1..de81339c 100644 --- a/server/server/api/repology/types.go +++ b/server/server/api/repology/types.go @@ -4,7 +4,6 @@ import ( "fmt" "strings" - "pacstall.dev/webserver/consts" "pacstall.dev/webserver/types/pac" ) @@ -23,13 +22,12 @@ type repologyPackage struct { RecipeURL string `json:"recipeUrl"` PackageDetailsURL string `json:"packageDetailsUrl"` Type string `json:"type"` - Patches []string `json:"patches"` } func newRepologyPackage(p *pac.Script) repologyPackage { var source *string = nil if len(p.Source) > 0 { - source = &p.Source[0] + source = &p.Source[0].Value } return repologyPackage{ @@ -39,20 +37,12 @@ func newRepologyPackage(p *pac.Script) repologyPackage { Maintainer: getMaintainer(p), Version: p.Version, URL: source, - Type: getType(p), - RecipeURL: fmt.Sprintf("https://raw.githubusercontent.com/pacstall/pacstall-programs/master/packages/%s/%s.%s", p.PackageName, p.PackageName, consts.PACSCRIPT_FILE_EXTENSION), + Type: string(p.Type()), + RecipeURL: fmt.Sprintf("https://raw.githubusercontent.com/pacstall/pacstall-programs/master/packages/%s/%s.pacscript", p.PackageName, p.PackageName), PackageDetailsURL: fmt.Sprintf("https://pacstall.dev/packages/%s", p.PackageName), - Patches: p.Patch, } } -var pacTypes = map[string]string{ - "-deb": "Debian Native", - "-git": "Source Code", - "-bin": "Precompiled", - "-app": "AppImage", -} - func getMaintainer(p *pac.Script) maintainerDetails { maintainer := "" if len(p.Maintainers) > 0 { @@ -74,13 +64,3 @@ func getMaintainer(p *pac.Script) maintainerDetails { Email: &email, } } - -func getType(p *pac.Script) string { - for suffix, kind := range pacTypes { - if strings.HasSuffix(p.PackageName, suffix) { - return kind - } - } - - return pacTypes["-git"] -} diff --git a/server/server/ssr/pacscript/package.go b/server/server/ssr/pacscript/package.go index 57d656f6..4edeafb7 100644 --- a/server/server/ssr/pacscript/package.go +++ b/server/server/ssr/pacscript/package.go @@ -50,7 +50,7 @@ func registerPacscriptSSRData() {

Find similar packages here.

- `, pkg.PackageName, pkg.PackageName, pkg.Description, strings.Join(pkg.Maintainers, ", "), pkg.Version, pkg.PackageName, pkg.PackageName, consts.PACSCRIPT_FILE_EXTENSION, pkg.PackageName), + `, pkg.PackageName, pkg.PackageName, pkg.Description, strings.Join(pkg.Maintainers, ", "), pkg.Version, pkg.PackageName, pkg.PackageName, consts.SRCINFO_FILE_EXTENSION, pkg.PackageName), } }, ) diff --git a/server/types/equals.go b/server/types/equals.go new file mode 100644 index 00000000..a64bf68e --- /dev/null +++ b/server/types/equals.go @@ -0,0 +1,5 @@ +package types + +type Equaller interface { + Equals(other Equaller) bool +} diff --git a/server/types/pac/parser/last_updated.go b/server/types/pac/parser/last_updated.go index dfd9e3b8..adf1bb8d 100644 --- a/server/types/pac/parser/last_updated.go +++ b/server/types/pac/parser/last_updated.go @@ -10,7 +10,6 @@ import ( "github.com/joomcode/errorx" "pacstall.dev/webserver/config" - "pacstall.dev/webserver/consts" "pacstall.dev/webserver/log" "pacstall.dev/webserver/types/array" "pacstall.dev/webserver/types/pac" @@ -31,8 +30,8 @@ func getPackageLastUpdatedTuples() ([]packageLastUpdatedTuple, error) { programsPath := path.Join(wordingDirectoryAbsolute, config.GitClonePath) script := fmt.Sprintf(` cd %v - for i in ./packages/*/*.%s; do echo $i; git log -1 --pretty=\"%%at\" $i; done - `, programsPath, consts.PACSCRIPT_FILE_EXTENSION) + for i in ./packages/*/*.pacscript; do echo $i; git log -1 --pretty=\"%%at\" $i; done + `, programsPath) outputBytes, err := pacsh.ExecBash(programsPath, "last_updated.sh", []byte(script)) if err != nil { @@ -52,7 +51,7 @@ func getPackageLastUpdatedTuples() ([]packageLastUpdatedTuple, error) { lastUpdatedString = lastUpdatedString[1 : len(lastUpdatedString)-1] packageNameWithExtension := path.Base(packagePath) - packageName := strings.TrimSuffix(packageNameWithExtension, "."+consts.PACSCRIPT_FILE_EXTENSION) + packageName := strings.TrimSuffix(packageNameWithExtension, ".pacscript") if packageName == "" || strings.HasPrefix(packageName, "-") { return nil, errorx.IllegalState.New("failed to parse package name from package path '%v'", packagePath) diff --git a/server/types/pac/parser/pacscript.go b/server/types/pac/parser/pacscript.go index 616ff5b0..82c3032b 100644 --- a/server/types/pac/parser/pacscript.go +++ b/server/types/pac/parser/pacscript.go @@ -1,66 +1,18 @@ package parser import ( - "fmt" - "strings" - "pacstall.dev/webserver/types/array" "pacstall.dev/webserver/types/pac" - "pacstall.dev/webserver/types/pac/parser/pacsh" ) -func removeDebianCheck(script string) string { - if !strings.Contains(script, "/etc/os-release)\" == \"debian\"") { - return script - } - - if strings.Index(script, "if") != 0 { - return script - } - - debianCheckEnd := strings.Index(script, "fi") - if debianCheckEnd == -1 { - return script - } - - return script[debianCheckEnd+len("fi"):] -} - -func buildCustomFormatScript(header []byte) []byte { - // TODO: remove after `preinstall` gets implemented - script := removeDebianCheck(string(header)) + "\n" - - script += "echo ''\n" - for _, bashName := range pacsh.PacscriptVars { - // If the variable is a function, then we replace it with the output of the function - script += fmt.Sprintf(` -if [[ "$(declare -F -p %v)" ]]; then - %v=$(%v) -fi -`, bashName, bashName, bashName) - } - - script = script + "\njo -p -- " - - for _, bashName := range pacsh.PacscriptVars { - script += fmt.Sprintf("-s %v=\"$%v\" ", bashName, bashName) - } - - for _, bashName := range pacsh.PacscriptArrays { - script += fmt.Sprintf("%v=$(jo -a ${%v[@]}) ", bashName, bashName) - } - - return []byte(script) -} - func computeRequiredBy(script *pac.Script, scripts []*pac.Script) { - pickBeforeColon := func(it *array.Iterator[string]) string { - return strings.Split(it.Value, ": ")[0] + pickName := func(it *array.Iterator[pac.ArchDistroString]) string { + return it.Value.Value } script.RequiredBy = make([]string, 0) for _, otherScript := range scripts { - otherScriptDependencies := array.Map(otherScript.PacstallDependencies, pickBeforeColon) + otherScriptDependencies := array.SwitchMap(otherScript.PacstallDependencies, pickName) if array.Contains(otherScriptDependencies, array.Is(script.PackageName)) { script.RequiredBy = append(script.RequiredBy, otherScript.PackageName) } diff --git a/server/types/pac/parser/pacsh/git_version.go b/server/types/pac/parser/pacsh/git_version.go new file mode 100644 index 00000000..c40d00f9 --- /dev/null +++ b/server/types/pac/parser/pacsh/git_version.go @@ -0,0 +1,27 @@ +package pacsh + +import ( + "github.com/joomcode/errorx" + "pacstall.dev/webserver/types/array" + "pacstall.dev/webserver/types/pac" + "pacstall.dev/webserver/types/pac/parser/pacsh/internal" +) + +func ApplyGitVersion(p *pac.Script) error { + sources := internal.NewGitSources(array.SwitchMap(p.Source, func(it *array.Iterator[pac.ArchDistroString]) string { + return it.Value.Value + })) + + version, err := sources.ParseGitPackageVersion() + if err != nil { + return errorx.Decorate(err, "failed to parse git version for package '%s'", p.PackageName) + } + + if p.Epoch != "" { + p.Version = p.Epoch + ":" + version + "-" + p.Release + } else { + p.Version = version + "-" + p.Release + } + + return nil +} diff --git a/server/types/pac/parser/pacsh/parse_pac_output.go b/server/types/pac/parser/pacsh/parse_pac_output.go index d6d590c8..21fd336f 100644 --- a/server/types/pac/parser/pacsh/parse_pac_output.go +++ b/server/types/pac/parser/pacsh/parse_pac_output.go @@ -1,178 +1,19 @@ package pacsh import ( - "encoding/json" - "strings" - - "github.com/joomcode/errorx" - "pacstall.dev/webserver/types/array" + "github.com/pacstall/go-srcinfo" "pacstall.dev/webserver/types/pac" - "pacstall.dev/webserver/types/pac/parser/pacsh/internal" ) -var ParsePacOutput = parseOutput -var PacscriptVars []string = []string{"pkgname", "pkgdesc", "gives", "hash", "pkgver"} -var PacscriptArrays []string = []string{"source", "arch", "maintainer", "depends", "conflicts", "breaks", "replaces", "makedepends", "optdepends", "pacdeps", "patch", "ppa", "repology"} - -type Stringable struct { - Data string -} - -func (s *Stringable) UnmarshalJSON(data []byte) error { - s.Data = string(data) - s.Data = strings.ReplaceAll(s.Data, "\"", "") - return nil -} - -func (s *Stringable) String() string { - return s.Data -} - -type StringableArrayWithComments struct { - Data []Stringable -} - -func (s StringableArrayWithComments) toStringArray() []string { - return array.SwitchMapPtr(s.Data, func(it *array.PtrIterator[Stringable]) string { - val := *it.Value - return val.String() - }) -} - -func (s *StringableArrayWithComments) UnmarshalJSON(data []byte) error { - err := json.Unmarshal(data, &s.Data) - if err != nil { - return err - } - - out := make([]string, 0) - item := "" - hasComments := false - for _, word := range s.Data { - strWord := word.String() - wordHasComment := strings.HasSuffix(strWord, ":") - - if wordHasComment { - hasComments = true - if item != "" { - out = append(out, strings.TrimSpace(item)) - } - item = strWord - } else if !hasComments { - out = append(out, strWord) - } else { - item += " " + strWord - } - - } - - if item != "" { - out = append(out, strings.TrimSpace(item)) - } - - s.Data = array.SwitchMap(out, func(it *array.Iterator[string]) Stringable { - return Stringable{it.Value} - }) - - return nil -} - -type pacscriptJsonStructure struct { - Pkgname string `json:"pkgname"` - Pkgdesc string `json:"pkgdesc"` - Gives *string `json:"gives"` - Hash *string `json:"hash"` - Pkgver *string `json:"pkgver"` - Source StringableArrayWithComments `json:"source"` - Maintainer StringableArrayWithComments `json:"maintainer"` - Depends StringableArrayWithComments `json:"depends"` - Conflicts StringableArrayWithComments `json:"conflicts"` - Arch StringableArrayWithComments `json:"arch"` - Breaks StringableArrayWithComments `json:"breaks"` - Replaces StringableArrayWithComments `json:"replaces"` - Makedepends StringableArrayWithComments `json:"makedepends"` - Optdepends StringableArrayWithComments `json:"optdepends"` - Pacdeps StringableArrayWithComments `json:"pacdeps"` - Patch StringableArrayWithComments `json:"patch"` - Ppa StringableArrayWithComments `json:"ppa"` - Repology StringableArrayWithComments `json:"repology"` -} - -var _GIT_VERSION = "git" -var _EMPTI_STR = "" - -func parseOutput(data []byte) (out pac.Script, err error) { - // remove prefixes if any - runeIndex := strings.IndexRune(string(data), '{') - if runeIndex >= 0 { - str := string(data) - str = str[runeIndex:] - data = []byte(str) - } - - var parsedContent pacscriptJsonStructure - err = json.Unmarshal(data, &parsedContent) +func ParsePacOutput(data []byte) (*pac.Script, error) { + out, err := srcinfo.Parse(string(data)) if err != nil { - return out, errorx.IllegalFormat.Wrap(err, "failed to deserialize json content '%v'", string(data)) - } - if parsedContent.Pkgver == nil { - if strings.HasSuffix(parsedContent.Pkgname, "-git") { - parsedContent.Pkgver = &_GIT_VERSION - } else { - return out, errorx.IllegalArgument.New("expected version to be non-empty") - } + return nil, err } - if parsedContent.Gives == nil { - parsedContent.Gives = &_EMPTI_STR - } + ps := pac.FromSrcInfo(*out) - out = pac.Script{ - PackageName: parsedContent.Pkgname, - Maintainers: parseMaintainers(parsedContent.Maintainer.toStringArray()), - Description: parsedContent.Pkgdesc, - Source: parsedContent.Source.toStringArray(), - Gives: *parsedContent.Gives, - Hash: parsedContent.Hash, - Version: *parsedContent.Pkgver, - RuntimeDependencies: parsedContent.Depends.toStringArray(), - BuildDependencies: parsedContent.Makedepends.toStringArray(), - OptionalDependencies: parsedContent.Optdepends.toStringArray(), - Conflicts: parsedContent.Conflicts.toStringArray(), - Replaces: parsedContent.Replaces.toStringArray(), - Breaks: parsedContent.Breaks.toStringArray(), - PacstallDependencies: parsedContent.Pacdeps.toStringArray(), - PPA: parsedContent.Ppa.toStringArray(), - Patch: parsedContent.Patch.toStringArray(), - RequiredBy: make([]string, 0), - Repology: parsedContent.Repology.toStringArray(), - LatestVersion: nil, - UpdateStatus: pac.UpdateStatus.Unknown, - } - - if pkgver, err := internal.NewGitSources(out.Source).ParseGitPackageVersion(); err == nil && pkgver != "" { - out.Version = pkgver - } - - if out.Hash != nil && len(*out.Hash) == 0 { - out.Hash = nil - } - - out.PrettyName = getPrettyName(out) - - return -} - -func parseMaintainers(maintainers []string) []string { - maintainersSplitByLA := strings.Split(strings.Join(maintainers, " "), ">") - - out := []string{} - for _, maintainer := range maintainersSplitByLA { - if len(maintainer) == 0 { - continue - } - out = append(out, strings.TrimSpace(maintainer)+">") - } + ps.PrettyName = getPrettyName(ps) - return out + return ps, nil } diff --git a/server/types/pac/parser/pacsh/pretty-name.go b/server/types/pac/parser/pacsh/pretty-name.go index 1bed2dd1..d1e12030 100644 --- a/server/types/pac/parser/pacsh/pretty-name.go +++ b/server/types/pac/parser/pacsh/pretty-name.go @@ -3,25 +3,19 @@ package pacsh import ( "strings" + "pacstall.dev/webserver/types" "pacstall.dev/webserver/types/pac" ) -var pacTypes = map[string]string{ - "-deb": "Debian Native", - "-git": "Source Code", - "-bin": "Precompiled", - "-app": "AppImage", -} - -func getPrettyName(p pac.Script) string { +func getPrettyName(p *pac.Script) string { name := "" if name == "" { name = p.PackageName } - for suffix := range pacTypes { - if strings.HasSuffix(name, suffix) { + for suffix := range types.PackageTypeSuffixToPackageTypeName { + if strings.HasSuffix(name, string(suffix)) { name = name[0 : len(name)-len(suffix)] } } diff --git a/server/types/pac/parser/pacsh/temp_dir_test.go b/server/types/pac/parser/pacsh/temp_dir_test.go deleted file mode 100644 index ce00df49..00000000 --- a/server/types/pac/parser/pacsh/temp_dir_test.go +++ /dev/null @@ -1,155 +0,0 @@ -package pacsh - -import ( - "os" - "strings" - "testing" - "time" - - "pacstall.dev/webserver/types/array" -) - -func cleanup() { - statFile = os.Stat - removeAll = os.RemoveAll - makeDir = os.Mkdir - removeFile = os.Remove -} - -type testFileInfo struct { - name string - size int64 - mode os.FileMode - modTime time.Time - isDir bool -} - -func (t testFileInfo) Name() string { - return t.name -} - -func (t testFileInfo) Size() int64 { - return t.size -} - -func (t testFileInfo) Mode() os.FileMode { - return t.mode -} - -func (t testFileInfo) ModTime() time.Time { - return t.modTime -} - -func (t testFileInfo) IsDir() bool { - return t.isDir -} - -func (t testFileInfo) Sys() interface{} { - return nil -} - -func Test_CreateTempDirectory_NoExisting(t *testing.T) { - defer cleanup() - - makeDirCalled := 0 - removeDirCalled := 0 - statFileCalled := 0 - - statFile = func(path string) (os.FileInfo, error) { - if statFileCalled == 1 { - statFileCalled += 1 - return nil, os.ErrNotExist - } - - statFileCalled += 1 - name, _ := array.Last(strings.Split(path, "/")) - return testFileInfo{ - name: name, - size: 0, - mode: 0777, - modTime: time.Now(), - isDir: true, - }, nil - } - - makeDir = func(path string, perm os.FileMode) error { - makeDirCalled += 1 - return nil - } - - removeAll = func(path string) error { - removeDirCalled += 1 - return nil - } - - err := CreateTempDirectory("/tmp") - - if err != nil { - t.Errorf("expected no error, got %v", err) - } - - if removeDirCalled != 1 { - t.Error("expected removeAll to be called 1 time but was called", removeDirCalled) - } - - if makeDirCalled != 1 { - t.Error("expected makeDir to be called 1 time but was called", makeDirCalled) - } - - if statFileCalled != 2 { - t.Error("expected statFile to be called 2 times but was called", statFileCalled) - } -} - -func Test_CreateTempDirectory_AlreadyExisting(t *testing.T) { - defer cleanup() - - makeDirCalled := 0 - removeDirCalled := 0 - statFileCalled := 0 - - statFile = func(path string) (os.FileInfo, error) { - if statFileCalled == 0 { - statFileCalled += 1 - return nil, os.ErrNotExist - } - - statFileCalled += 1 - name, _ := array.Last(strings.Split(path, "/")) - return testFileInfo{ - name: name, - size: 0, - mode: 0777, - modTime: time.Now(), - isDir: true, - }, nil - } - - makeDir = func(path string, perm os.FileMode) error { - makeDirCalled += 1 - return nil - } - - removeAll = func(path string) error { - removeDirCalled += 1 - return nil - } - - err := CreateTempDirectory("/tmp") - - if err != nil { - t.Errorf("expected no error, got %v", err) - } - - if removeDirCalled != 0 { - t.Error("expected removeAll to be called 0 times but was called", removeDirCalled) - } - - if makeDirCalled != 1 { - t.Error("expected makeDir to be called 1 time but was called", makeDirCalled) - } - - if statFileCalled != 1 { - t.Error("expected statFile to be called 2 times but was called", statFileCalled) - } -} diff --git a/server/types/pac/parser/parallelism/channels/exhaust.go b/server/types/pac/parser/parallelism/channels/exhaust.go new file mode 100644 index 00000000..f9d63c09 --- /dev/null +++ b/server/types/pac/parser/parallelism/channels/exhaust.go @@ -0,0 +1,6 @@ +package channels + +func Exhaust[T any](in <-chan T) { + for range in { + } +} diff --git a/server/types/pac/parser/parse.go b/server/types/pac/parser/parse.go index e93a226d..26eca283 100644 --- a/server/types/pac/parser/parse.go +++ b/server/types/pac/parser/parse.go @@ -1,7 +1,6 @@ package parser import ( - "fmt" "os" "path" "strings" @@ -22,6 +21,7 @@ import ( ) const PACKAGE_LIST_FILE_NAME = "./packagelist" +const MAX_GIT_VERSION_CONCURRENCY = 5 func ParseAll() error { if err := git.RefreshPrograms(config.GitClonePath, config.GitURL, config.PacstallPrograms.Branch); err != nil { @@ -38,6 +38,8 @@ func ParseAll() error { return errorx.Decorate(err, "failed to parse pacscripts") } + log.Info("pacscript parsing done. computing dependency graph") + for _, script := range loadedPacscripts { computeRequiredBy(script, loadedPacscripts) } @@ -46,12 +48,25 @@ func ParseAll() error { return s1.PackageName < s2.PackageName }) + log.Info("dependency graph done. setting up git updated-at dates") + if err := setLastUpdatedAt(loadedPacscripts); err != nil { return errorx.Decorate(err, "failed to set last updated at") } + log.Info("updated-at dates done. fetching git versions") + + gitPacscripts := array.Filter(loadedPacscripts, func(it *array.Iterator[*pac.Script]) bool { + return strings.HasSuffix(it.Value.PackageName, string(types.PACKAGE_TYPE_SUFFIX_GIT)) + }) + + channels.Exhaust(batch.Run(MAX_GIT_VERSION_CONCURRENCY, gitPacscripts, func(p *pac.Script) (interface{}, error) { + err := pacsh.ApplyGitVersion(p) + return nil, err + })) + pacstore.Update(loadedPacscripts) - log.Info("successfully parsed %v (%v / %v) packages", types.Percent(float64(len(loadedPacscripts))/float64(len(pkgList))), len(loadedPacscripts), len(pkgList)) + log.Info("successfully loaded %v (%v / %v) packages", types.Percent(float64(len(loadedPacscripts))/float64(len(pkgList))), len(loadedPacscripts), len(pkgList)) return nil } @@ -81,45 +96,37 @@ func parsePacscriptFiles(names []string) ([]*pac.Script, error) { out, err := ParsePacscriptFile(config.GitClonePath, pacName) if config.Repology.Enabled { - if err := repology.Sync(&out); err != nil { - log.Debug("failed to sync %v with repology. Error: %v", pacName, err) + if err := repology.Sync(out); err != nil { + log.Debug("failed to sync %v with repology. Error: %+v", pacName, err) } } - return &out, err + return out, err }) return channels.ToSlice(outChan), nil } func readPacscriptFile(rootDir, name string) (scriptBytes []byte, fileName string, err error) { - fileName = fmt.Sprintf("%s.%s", name, consts.PACSCRIPT_FILE_EXTENSION) - scriptPath := path.Join(rootDir, "packages", name, fileName) + scriptPath := path.Join(rootDir, "packages", name, consts.SRCINFO_FILE_EXTENSION) scriptBytes, err = os.ReadFile(scriptPath) if err != nil { return nil, "", errorx.Decorate(err, "failed to read file '%v'", scriptPath) } - return scriptBytes, fileName, nil + return scriptBytes, consts.SRCINFO_FILE_EXTENSION, nil } -func ParsePacscriptFile(programsDirPath, name string) (pac.Script, error) { - pacshell, filename, err := readPacscriptFile(programsDirPath, name) - if err != nil { - return pac.Script{}, errorx.Decorate(err, "failed to read pacscript '%v'", name) - } - - pacshell = buildCustomFormatScript(pacshell) - - stdout, err := pacsh.ExecBash(config.TempDir, filename, pacshell) +func ParsePacscriptFile(programsDirPath, name string) (*pac.Script, error) { + srcInfoData, _, err := readPacscriptFile(programsDirPath, name) if err != nil { - return pac.Script{}, errorx.Decorate(err, "failed to execute pacscript '%v'", name) + return nil, errorx.Decorate(err, "failed to read pacscript '%v'", name) } - pacscript, err := pacsh.ParsePacOutput(stdout) + pacscript, err := pacsh.ParsePacOutput(srcInfoData) if err != nil { - return pac.Script{}, errorx.Decorate(err, "failed to parse pacscript '%v'", name) + return nil, errorx.Decorate(err, "failed to parse pacscript '%v'", name) } return pacscript, nil diff --git a/server/types/pac/parser/parse_test.go b/server/types/pac/parser/parse_test.go index 89fde1c4..984664ce 100644 --- a/server/types/pac/parser/parse_test.go +++ b/server/types/pac/parser/parse_test.go @@ -7,6 +7,7 @@ import ( "path" "testing" + "pacstall.dev/webserver/types" "pacstall.dev/webserver/types/pac" "pacstall.dev/webserver/types/pac/parser" "pacstall.dev/webserver/types/pac/parser/pacsh" @@ -29,7 +30,24 @@ func assertEquals(t *testing.T, what string, expected interface{}, actual interf } } -func assertArrayEquals(t *testing.T, what string, expected []string, actual []string) { +func assertArrayEquals[T types.Equaller](t *testing.T, what string, expected []T, actual []T) { + if len(actual) == len(expected) && len(actual) == 0 { + return + } + + if len(actual) != len(expected) { + t.Errorf("pacscript.%v expected len '%v', got len '%v' (expected '%#v', got '%#v')", what, len(expected), len(actual), expected, actual) + return + } + + for idx := range expected { + if !expected[idx].Equals(actual[idx]) { + t.Errorf("pacscript.%v[%v] expected '%#v', got '%#v'", what, idx, expected, actual) + } + } +} + +func assertStringArrayEquals(t *testing.T, what string, expected []string, actual []string) { if len(actual) == len(expected) && len(actual) == 0 { return } @@ -46,18 +64,11 @@ func assertArrayEquals(t *testing.T, what string, expected []string, actual []st } } -func assertPacscriptEquals(t *testing.T, expected pac.Script, actual pac.Script) { +func assertPacscriptEquals(t *testing.T, expected *pac.Script, actual *pac.Script) { assertEquals(t, "package name", expected.PackageName, actual.PackageName) - assertArrayEquals(t, "maintainers", expected.Maintainers, actual.Maintainers) + assertStringArrayEquals(t, "maintainers", expected.Maintainers, actual.Maintainers) assertEquals(t, "description", expected.Description, actual.Description) assertEquals(t, "gives", expected.Gives, actual.Gives) - if expected.Hash != nil && actual.Hash == nil { - t.Errorf("expected hash '%v', got nil", *expected.Hash) - } else if expected.Hash == nil && actual.Hash != nil { - t.Errorf("expected hash nil, got %v", *actual.Hash) - } else if expected.Hash != nil && actual.Hash != nil { - assertEquals(t, "hash", *expected.Hash, *actual.Hash) - } assertEquals(t, "version", expected.Version, actual.Version) assertArrayEquals(t, "breaks", expected.Breaks, actual.Breaks) assertArrayEquals(t, "conflicts", expected.Conflicts, actual.Conflicts) @@ -68,10 +79,8 @@ func assertPacscriptEquals(t *testing.T, expected pac.Script, actual pac.Script) assertArrayEquals(t, "build dependencies", expected.BuildDependencies, actual.BuildDependencies) assertArrayEquals(t, "optional dependencies", expected.OptionalDependencies, actual.OptionalDependencies) assertArrayEquals(t, "pacstall dependencies", expected.PacstallDependencies, actual.PacstallDependencies) - assertArrayEquals(t, "ppa", expected.PPA, actual.PPA) - assertArrayEquals(t, "patch", expected.Patch, actual.Patch) - assertArrayEquals(t, "required by", expected.RequiredBy, actual.RequiredBy) - assertArrayEquals(t, "repology", expected.Repology, actual.Repology) + assertStringArrayEquals(t, "required by", expected.RequiredBy, actual.RequiredBy) + assertStringArrayEquals(t, "repology", expected.Repology, actual.Repology) assertEquals(t, "update status", expected.UpdateStatus, actual.UpdateStatus) } @@ -121,7 +130,7 @@ func assertPacscriptMatchesSnapshot(t *testing.T, pkgname string) { return } - assertPacscriptEquals(t, *expected, actual) + assertPacscriptEquals(t, expected, actual) } func Test_PacscriptSnapshots(t *testing.T) { diff --git a/server/types/pac/parser/search.go b/server/types/pac/parser/search.go index 4e8c5776..f1b99407 100644 --- a/server/types/pac/parser/search.go +++ b/server/types/pac/parser/search.go @@ -37,7 +37,11 @@ func FilterPackages(packages []*pac.Script, filter, filterBy string) []*pac.Scri case "name": return filterByFunc(func(pi *pac.Script) bool { return strings.Contains(pi.PackageName, filter) || - strings.Contains(pi.Gives, filter) || + func(ps []pac.ArchDistroString, filter string) bool { + return array.Any(ps, func(it pac.ArchDistroString) bool { + return strings.Contains(it.Value, filter) + }) + }(pi.Gives, filter) || strings.Contains(pi.Description, filter) }) diff --git a/server/types/pac/script.go b/server/types/pac/script.go index b97f12da..96b43298 100644 --- a/server/types/pac/script.go +++ b/server/types/pac/script.go @@ -1,6 +1,13 @@ package pac -import "time" +import ( + "strings" + "time" + + "github.com/pacstall/go-srcinfo" + "pacstall.dev/webserver/types" + "pacstall.dev/webserver/types/array" +) type updateStatus struct { Unknown UpdateStatusValue @@ -20,27 +27,142 @@ var UpdateStatus = updateStatus{ type UpdateStatusValue = int +type ArchDistroString struct { + Arch string `json:"arch,omitempty"` + Distro string `json:"distro,omitempty"` + Value string `json:"value"` +} + +func (a ArchDistroString) Equals(b types.Equaller) bool { + other, ok := b.(ArchDistroString) + if !ok { + return false + } + + return a.Arch == other.Arch && a.Distro == other.Distro && a.Value == other.Value +} + type Script struct { - PrettyName string `json:"prettyName"` - Version string `json:"version"` - LatestVersion *string `json:"latestVersion"` - PackageName string `json:"packageName"` - Maintainers []string `json:"maintainers"` - Description string `json:"description"` - Source []string `json:"source"` - RuntimeDependencies []string `json:"runtimeDependencies"` - BuildDependencies []string `json:"buildDependencies"` - OptionalDependencies []string `json:"optionalDependencies"` - Conflicts []string `json:"conflicts"` - Breaks []string `json:"breaks"` - Gives string `json:"gives"` - Replaces []string `json:"replaces"` - Hash *string `json:"hash"` - PPA []string `json:"ppa"` - PacstallDependencies []string `json:"pacstallDependencies"` - Patch []string `json:"patch"` - Repology []string `json:"repology"` - RequiredBy []string `json:"requiredBy"` - LastUpdatedAt time.Time `json:"lastUpdatedAt"` - UpdateStatus int `json:"updateStatus"` // enum UpdateStatus + Architectures []string `json:"architectures"` + PrettyName string `json:"prettyName"` + Version string `json:"version"` + Release string `json:"release"` + LatestVersion *string `json:"latestVersion"` + PackageName string `json:"packageName"` + Maintainers []string `json:"maintainers"` + Description string `json:"description"` + Source []ArchDistroString `json:"source"` + RuntimeDependencies []ArchDistroString `json:"runtimeDependencies"` + BuildDependencies []ArchDistroString `json:"buildDependencies"` + OptionalDependencies []ArchDistroString `json:"optionalDependencies"` + CheckDependencies []ArchDistroString `json:"checkDependencies"` + Conflicts []ArchDistroString `json:"conflicts"` + Breaks []ArchDistroString `json:"breaks"` + Gives []ArchDistroString `json:"gives"` + Replaces []ArchDistroString `json:"replaces"` + Sha1Sums []ArchDistroString `json:"sha1sums"` + Sha224Sums []ArchDistroString `json:"sha224sums"` + Sha256Sums []ArchDistroString `json:"sha256sums"` + Sha384Sums []ArchDistroString `json:"sha384sums"` + Sha512Sums []ArchDistroString `json:"sha512sums"` + Md5Sums []ArchDistroString `json:"md5sums"` + Priority []ArchDistroString `json:"priority"` + Recommends []ArchDistroString `json:"recommends"` + Suggests []ArchDistroString `json:"suggests"` + PacstallDependencies []ArchDistroString `json:"pacstallDependencies"` + Enhances []ArchDistroString `json:"enhances"` + Repology []string `json:"repology"` + RequiredBy []string `json:"requiredBy"` + LastUpdatedAt time.Time `json:"lastUpdatedAt"` + UpdateStatus int `json:"updateStatus"` // enum UpdateStatus + Changelog string `json:"changelog"` + Backup []string `json:"backup"` + Compatible []string `json:"compatible"` + Incompatible []string `json:"incompatible"` + Epoch string `json:"epoch"` + Install string `json:"install"` + License []string `json:"license"` + Mask []string `json:"mask"` + NoExtract []string `json:"noExtract"` + ValidPGPKeys []string `json:"validPgpKeys"` + Groups []string `json:"groups"` +} + +func (p *Script) Type() types.PackageTypeName { + for suffix, name := range types.PackageTypeSuffixToPackageTypeName { + if strings.HasSuffix(p.PackageName, string(suffix)) { + return name + } + } + + return types.PackageTypeSuffixToPackageTypeName["-git"] +} + +func FromSrcInfo(info srcinfo.Srcinfo) *Script { + return &Script{ + Version: info.Version(), + Release: info.Pkgrel, + LatestVersion: nil, + PackageName: info.Packages[0].Pkgname, + Maintainers: info.Maintainer, + Description: info.Pkgdesc, + Source: toArchDistroStrings(info.Source), + RuntimeDependencies: toArchDistroStrings(info.Depends), + BuildDependencies: toArchDistroStrings(info.MakeDepends), + OptionalDependencies: toArchDistroStrings(info.OptDepends), + Conflicts: toArchDistroStrings(info.Conflicts), + Breaks: toArchDistroStrings(info.Breaks), + Gives: toArchDistroStrings(info.Gives), + Replaces: toArchDistroStrings(info.Replaces), + PacstallDependencies: toArchDistroStrings(info.Pacdeps), + Architectures: info.Arch, + Repology: info.Repology, + RequiredBy: []string{}, + Sha1Sums: toArchDistroStrings(info.SHA1Sums), + Sha224Sums: toArchDistroStrings(info.SHA224Sums), + Sha256Sums: toArchDistroStrings(info.SHA256Sums), + Sha384Sums: toArchDistroStrings(info.SHA384Sums), + Sha512Sums: toArchDistroStrings(info.SHA512Sums), + PrettyName: "", + Changelog: info.Changelog, + Backup: orEmptyArray(info.Backup), + Compatible: orEmptyArray(info.Compatible), + Incompatible: orEmptyArray(info.Incompatible), + Epoch: info.Epoch, + Install: info.Install, + License: orEmptyArray(info.License), + Mask: orEmptyArray(info.Mask), + NoExtract: orEmptyArray(info.NoExtract), + ValidPGPKeys: orEmptyArray(info.ValidPGPKeys), + Groups: orEmptyArray(info.Groups), + Enhances: toArchDistroStrings(info.Enhances), + CheckDependencies: toArchDistroStrings(info.CheckDepends), + Md5Sums: toArchDistroStrings(info.MD5Sums), + Priority: toArchDistroStrings(info.Priority), + Suggests: toArchDistroStrings(info.Suggests), + Recommends: toArchDistroStrings(info.Recommends), + UpdateStatus: UpdateStatus.Unknown, + } +} + +func toArchDistroStrings(ads []srcinfo.ArchDistroString) []ArchDistroString { + return array.SwitchMap(ads, func(it *array.Iterator[srcinfo.ArchDistroString]) ArchDistroString { + return toArchDistroString(it.Value) + }) +} + +func toArchDistroString(ads srcinfo.ArchDistroString) ArchDistroString { + return ArchDistroString{ + Arch: ads.Arch, + Distro: ads.Distro, + Value: ads.Value, + } +} + +func orEmptyArray[T interface{}](items []T) []T { + if items == nil { + return []T{} + } + + return items } diff --git a/server/types/pkgtype.go b/server/types/pkgtype.go new file mode 100644 index 00000000..765ce8dc --- /dev/null +++ b/server/types/pkgtype.go @@ -0,0 +1,26 @@ +package types + +type PackageTypeName string + +const ( + PACKAGE_TYPE_DEB PackageTypeName = "Debian Native" + PACKAGE_TYPE_GIT PackageTypeName = "Source Code" + PACKAGE_TYPE_BIN PackageTypeName = "Precompiled" + PACKAGE_TYPE_APP PackageTypeName = "AppImage" +) + +type PackageTypeSuffix string + +const ( + PACKAGE_TYPE_SUFFIX_DEB PackageTypeSuffix = "-deb" + PACKAGE_TYPE_SUFFIX_GIT PackageTypeSuffix = "-git" + PACKAGE_TYPE_SUFFIX_BIN PackageTypeSuffix = "-bin" + PACKAGE_TYPE_SUFFIX_APP PackageTypeSuffix = "-app" +) + +var PackageTypeSuffixToPackageTypeName = map[PackageTypeSuffix]PackageTypeName{ + PACKAGE_TYPE_SUFFIX_DEB: PACKAGE_TYPE_DEB, + PACKAGE_TYPE_SUFFIX_GIT: PACKAGE_TYPE_GIT, + PACKAGE_TYPE_SUFFIX_BIN: PACKAGE_TYPE_BIN, + PACKAGE_TYPE_SUFFIX_APP: PACKAGE_TYPE_APP, +}