Skip to content

Commit

Permalink
Add available_sys method (#47)
Browse files Browse the repository at this point in the history
* add available_sys method + dnf backend
* add test
* implement apt backend
* implement alpm backend
* update NEWS
  • Loading branch information
Enchufa2 committed Nov 14, 2022
1 parent c01305d commit 8b488f5
Show file tree
Hide file tree
Showing 14 changed files with 249 additions and 89 deletions.
2 changes: 1 addition & 1 deletion DESCRIPTION
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
Package: bspm
Type: Package
Title: Bridge to System Package Manager
Version: 0.3.10
Version: 0.3.10.1
Authors@R: c(
person("Iñaki", "Ucar", email="iucar@fedoraproject.org",
role=c("aut", "cph", "cre"), comment=c(ORCID="0000-0001-6403-5550")))
Expand Down
1 change: 1 addition & 0 deletions NAMESPACE
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
# Generated by roxygen2: do not edit by hand

export(available_sys)
export(disable)
export(discover)
export(enable)
Expand Down
5 changes: 5 additions & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
# bspm 0.3.10.1

- New function `available_sys()` returns a matrix of available packages with
`"Package"`, `"Version"`, and `"Repository"` (#47 addressing #41).

# bspm 0.3.10

- Check backend availability on `enable()`, and trigger a warning if the
Expand Down
13 changes: 7 additions & 6 deletions R/bridge.R
Original file line number Diff line number Diff line change
Expand Up @@ -42,16 +42,16 @@ backend_check <- function() {

backend_call <- function(method, pkgs=NULL) {
if (root())
return(invisible(root_call(method, pkgs)))
return(root_call(method, pkgs))

if (sudo_preferred())
return(invisible(sudo_call(method, pkgs, force=TRUE)))
return(sudo_call(method, pkgs, force=TRUE))

if (dbus_service_alive())
return(invisible(dbus_call(method, pkgs)))
return(dbus_call(method, pkgs))

if (interactive())
return(invisible(sudo_call(method, pkgs)))
return(sudo_call(method, pkgs))

stop("cannot connect to the system package manager", call.=FALSE)
}
Expand All @@ -71,9 +71,9 @@ root_call <- function(method, pkgs=NULL, sudo=NULL) {
on.exit(unlink(tmp2, recursive=TRUE, force=TRUE))

cmd <- system.file("service/bspm.py", package="bspm")
args <- method
args <- c(method, "-o", tmp)
if (!is.null(pkgs))
args <- c(args, "-o", tmp, pkgs)
args <- c(args, pkgs)
if (!is.null(sudo)) {
args <- c(cmd, args)
cmd <- sudo
Expand Down Expand Up @@ -104,6 +104,7 @@ dbus_call <- function(method, pkgs=NULL) {
args <- c("call", "--timeout=1h", BUS_NAME, OPATH, IFACE, method)
if (!is.null(pkgs))
args <- c(args, "ias", Sys.getpid(), length(pkgs), pkgs)
else args <- c(args, "i", Sys.getpid())
out <- system2nowarn(cmd, args, stdout=TRUE, stderr=TRUE)

if (!length(out))
Expand Down
28 changes: 25 additions & 3 deletions R/manager.R
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,9 @@
#'
#' # now remove it
#' bspm::remove_sys("units")
#'
#' # get available packages
#' bspm::available_sys()
#' }
#'
#' @name manager
Expand All @@ -50,12 +53,31 @@ install_sys <- function(pkgs) {
deps <- unique(unlist(deps, use.names=FALSE))
if (length(deps)) backend_call("install", deps)
}
not.avail
invisible(not.avail)
}

#' @name manager
#' @export
remove_sys <- function(pkgs) backend_call("remove", pkgs)
remove_sys <- function(pkgs) invisible(backend_call("remove", pkgs))

#' @return Function \code{available_sys} returns a matrix with one row per
#' package. Row names are the package names, and column names include
#' \code{"Package"}, \code{"Version"}, \code{"Repository"}.
#'
#' @name manager
#' @export
available_sys <- function() {
pkgs <- do.call(rbind, strsplit(backend_call("available"), ";"))
colnames(pkgs) <- c("Package", "Version", "Repository")

vers <- package_version(pkgs[, "Version"])
pkgs <- pkgs[order(pkgs[, "Package"], vers, decreasing=TRUE), ]
pkgs <- pkgs[!duplicated(pkgs[, "Package"]), ]
pkgs <- pkgs[order(pkgs[, "Package"]), ]

rownames(pkgs) <- pkgs[, 1]
pkgs
}

#' @details The \code{discover} method is only needed when e.g. a new repository
#' is added that contains packages with different prefixes (for example, your
Expand All @@ -66,4 +88,4 @@ remove_sys <- function(pkgs) backend_call("remove", pkgs)
#'
#' @name manager
#' @export
discover <- function() backend_call("discover")
discover <- function() invisible(backend_call("discover"))
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,7 @@ New backends for other package managers can be added to `inst/service/backend`.
Each backend must implement the following functions:

- `def discover() -> dict({ "prefixes" : list, "exclusions" : list })`
- `def available(prefixes : list, exclusions : list) -> list`
- `def install(prefixes : list, pkgs : list, exclusions : list) -> list`
- `def remove(prefixes : list, pkgs : list, exclusions : list) -> list`

Expand Down
2 changes: 1 addition & 1 deletion inst/service/backend/__init__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
libnames = ["dnf", "apt", "alpm"]
attrs = ["discover", "install", "remove"]
attrs = ["discover", "available", "install", "remove"]

for libname in libnames:
try:
Expand Down
22 changes: 21 additions & 1 deletion inst/service/backend/_utils.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from contextlib import suppress
from pathlib import Path
from os import path
import time
import time, re

CACHE_INVALIDATION_TIME = 5 # minutes

Expand Down Expand Up @@ -32,3 +32,23 @@ def cache_update(method, force=False):
if force or time.time() - cache_time > CACHE_INVALIDATION_TIME * 60:
method()
Path(cache_file).touch()

def pkg_strip(prefixes, name):
for prefix in sorted(prefixes, reverse=True):
name = name.replace(prefix, "")
return name

def ver_strip(version):
version = list(reversed(version.split(":", 1)))[0]
version = version.rsplit("-", 1)[0]
version = version.rsplit("+")[0]
# remove things like .r79, see r-cran-rniftilib
version = re.sub("\.r[0-9]+$", "", version)
return version

def pkg_record(prefixes, name, version, repo):
return ";".join([
pkg_strip(prefixes, name),
ver_strip(version),
repo.replace(" ", "_")
])
92 changes: 58 additions & 34 deletions inst/service/backend/alpm.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from ._utils import mark
from ._utils import mark, pkg_record
from functools import partial
import pycman

Expand All @@ -12,29 +12,39 @@ def discover():
"exclusions": []
}

def _cb_dl(filename, tx, total):
global _db_sync, _target
if _db_sync:
if tx == 3:
print(" %s" % filename.split(".")[0])
else:
if filename != _target:
_target = filename
print(" %s" % filename.split(".pkg.")[0])

def _cb_event(id, msg):
global _target
if id == 9:
print(":: Processing package changes...")
elif id == 21:
_target = None
print(":: Retrieving packages...")

def _cb_progress(target, percent, n, i):
global _target
if len(target) > 0 and target != _target:
_target = target
print("(%d/%d) %s" % (i, n, target))
def _get_handle():
def _cb_dl(filename, tx, total):
global _db_sync, _target
if _db_sync:
if tx == 3:
print(" %s" % filename.split(".")[0])
else:
if filename != _target:
_target = filename
print(" %s" % filename.split(".pkg.")[0])

def _cb_event(id, msg):
global _target
if id == 9:
print(":: Processing package changes...")
elif id == 21:
_target = None
print(":: Retrieving packages...")

def _cb_progress(target, percent, n, i):
global _target
if len(target) > 0 and target != _target:
_target = target
print("(%d/%d) %s" % (i, n, target))

args = pycman.action_sync.parse_options([])
handle = pycman.config.init_with_config_and_options(args)

handle.dlcb = _cb_dl
handle.eventcb = _cb_event
handle.progresscb = _cb_progress

return handle

def _update(handle):
global _db_sync
Expand All @@ -52,28 +62,42 @@ def _remove(handle, t, repos, name):
pkg = handle.get_localdb().get_pkg(name)
t.remove_pkg(pkg)

def available(prefixes, exclusions):
handle = _get_handle()

q = []
for db in handle.get_syncdbs():
for prefix in prefixes:
q += [_ for _ in db.search(prefix) if _.name.startswith(prefix)]
pkgs = []
for pkg in q:
if pkg.name in exclusions:
continue
pkgs.append(pkg_record(
prefixes,
pkg.name,
pkg.version,
pkg.db.name
))

return pkgs

def operation(op, prefixes, pkgs, exclusions):
args = pycman.action_sync.parse_options([])
handle = pycman.config.init_with_config_and_options(args)

handle.dlcb = _cb_dl
handle.eventcb = _cb_event
handle.progresscb = _cb_progress

handle = _get_handle()
repos = dict((db.name, db) for db in handle.get_syncdbs())
t = handle.init_transaction()
_update(handle)

fun = partial(op, handle, t, repos)
notavail = mark(fun, prefixes, pkgs, exclusions, trans="lower")

try:
t.prepare()
t.commit()
except:
pass
t.release()

return notavail

def install(prefixes, pkgs, exclusions):
Expand Down
39 changes: 32 additions & 7 deletions inst/service/backend/apt.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from ._utils import mark, cache_update
from ._utils import mark, cache_update, pkg_record
from functools import partial
import re
import apt
Expand All @@ -14,32 +14,57 @@ def discover():
cache = apt.Cache()
cache_update(partial(cache.update, aprogress), force=True)
cache.open()

pkgs = [x for x in cache.keys() if re.match("^r-(.*)-(.*)", x)]
prefixes = {"-".join(x.split("-")[0:2]) + "-" for x in pkgs}


cache.close()

return {
"prefixes": list(prefixes - {"r-doc-", "r-base-"}),
"exclusions": []
}

def available(prefixes, exclusions):
aprogress = apt.progress.text.AcquireProgress()
cache = apt.Cache()
cache_update(partial(cache.update, aprogress))
cache.open()

q = [x for x in cache.keys() if re.match("|".join(prefixes), x)]
pkgs = []
for pkg in q:
if pkg in exclusions:
continue
pkgs.append(pkg_record(
prefixes,
cache[pkg].candidate.source_name,
cache[pkg].candidate.version,
cache[pkg].candidate.origins[0].origin
))

cache.close()

return pkgs

def operation(op, prefixes, pkgs, exclusions):
def cc(cache, method):
def wrapper(pkgname):
getattr(cache[pkgname], "mark_" + method)()
return wrapper

oprogress = apt.progress.text.OpProgress()
aprogress = apt.progress.text.AcquireProgress()

cache = apt.Cache(oprogress)
cache_update(partial(cache.update, aprogress))
cache.open(oprogress)

notavail = mark(cc(cache, op), prefixes, pkgs, exclusions, trans="lower")

cache.commit(aprogress)
cache.close()

return notavail

def install(prefixes, pkgs, exclusions):
Expand Down

0 comments on commit 8b488f5

Please sign in to comment.