diff --git a/DESCRIPTION b/DESCRIPTION index c7db427..ec9b66b 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,7 +1,7 @@ Type: Package Package: rotor Title: Log Rotation and Conditional Backups -Version: 0.3.4.9000 +Version: 0.3.5 Authors@R: person(given = "Stefan", family = "Fleck", diff --git a/NEWS.md b/NEWS.md index 00bfca7..72a9ec1 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,9 +1,11 @@ -# rotor (dev) +# rotor 0.3.5 * Backups now retain their original timestamp (created, last modified) where possible (even when zipped) * fixed broken behaviour when pruning with max_backups where max_backups is the maximum number of files +* `parse_size()` now accepts (and rounds down) decimal file sizes, but throws + a warning # rotor 0.3.4 diff --git a/R/parsers.R b/R/parsers.R index d0da618..e94e0d6 100644 --- a/R/parsers.R +++ b/R/parsers.R @@ -7,16 +7,20 @@ parse_size <- function(x){ assert(is_scalar(x) && !is.na(x)) if (is_integerish(x)){ - return(as.integer(x)) - } else { - assert(is.character(x)) - } + res <- as.integer(x) + } else if (is.numeric(x)){ + res <- as.integer(floor(x)) + warning("`x` ist not an integer file size, rounding down to ", res, " bits") - unit_start <- regexec("[kmgt]", tolower(x))[[1]] + } else if (is.character(x)){ + unit_start <- regexec("[kmgt]", tolower(x))[[1]] + num <- trimws(substr(x, 1, unit_start - 1L)) + unit <- trimws(substr(x, unit_start, nchar(x))) + res <- as.numeric(num) * parse_info_unit(unit) - num <- trimws(substr(x, 1, unit_start - 1L)) - unit <- trimws(substr(x, unit_start, nchar(x))) - res <- as.numeric(num) * parse_info_unit(unit) + } else { + stop(ValueError(paste("`x` is not a valid file size but ", preview_object(x)))) + } assert(is_scalar(res) && !is.na(res) && is_scalar_numeric(res)) res diff --git a/README.Rmd b/README.Rmd index e2ba752..fc38ebd 100644 --- a/README.Rmd +++ b/README.Rmd @@ -166,8 +166,8 @@ unlink(td, recursive = TRUE) ## Cache -rotor also provides a simple on-disk key-value store that can be used as a -persistent cache. +rotor also provides a simple on-disk key-value store that can be pruned by size, +age or number of files. ```{r} cache <- Cache$new(file.path(tempdir(), "cache-test"), hashfun = digest::digest) @@ -205,10 +205,13 @@ anything outside of base R) Optional dependencies: -* [digest](https://github.com/eddelbuettel/digest) or +* [digest](https://github.com/eddelbuettel/digest), + [ulid](https://cran.r-project.org/package=ulid), or [uuid](https://CRAN.R-project.org/package=uuid ) for generating - hashes or UIDs when using Cache. Storage keys for cache files can also be set + hashes or UIDs when using `Cache`. Storage keys for cache files can also be set manually, in which case no external dependencies are required. * [zip](https://CRAN.R-project.org/package=zip) is supported as an alternative to the integrated zip function in R. Might work better on some systems and worse on others. +* [crayon](https://cran.r-project.org/package=crayon) for + terminal colors diff --git a/README.md b/README.md index e4f39b3..25554db 100644 --- a/README.md +++ b/README.md @@ -83,8 +83,8 @@ backup(tf, compression = TRUE) # display backups of a file list_backups(tf) -#> [1] "/tmp/Rtmpw73XUg/rotor/mylogfile.1.log.zip" -#> [2] "/tmp/Rtmpw73XUg/rotor/mylogfile.2.log" +#> [1] "/tmp/RtmpRZc2Qd/rotor/mylogfile.1.log.zip" +#> [2] "/tmp/RtmpRZc2Qd/rotor/mylogfile.2.log" ``` `rotate()` also backs up a file, but replaces the original file with an @@ -93,9 +93,9 @@ empty one. ``` r rotate(tf) list_backups(tf) -#> [1] "/tmp/Rtmpw73XUg/rotor/mylogfile.1.log" -#> [2] "/tmp/Rtmpw73XUg/rotor/mylogfile.2.log.zip" -#> [3] "/tmp/Rtmpw73XUg/rotor/mylogfile.3.log" +#> [1] "/tmp/RtmpRZc2Qd/rotor/mylogfile.1.log" +#> [2] "/tmp/RtmpRZc2Qd/rotor/mylogfile.2.log.zip" +#> [3] "/tmp/RtmpRZc2Qd/rotor/mylogfile.3.log" # the original file is now empty readLines(tf) @@ -118,10 +118,10 @@ backup(tf, max_backups = 4) backup(tf, max_backups = 4) list_backups(tf) -#> [1] "/tmp/Rtmpw73XUg/rotor/mylogfile.1.log" -#> [2] "/tmp/Rtmpw73XUg/rotor/mylogfile.2.log" -#> [3] "/tmp/Rtmpw73XUg/rotor/mylogfile.3.log" -#> [4] "/tmp/Rtmpw73XUg/rotor/mylogfile.4.log.zip" +#> [1] "/tmp/RtmpRZc2Qd/rotor/mylogfile.1.log" +#> [2] "/tmp/RtmpRZc2Qd/rotor/mylogfile.2.log" +#> [3] "/tmp/RtmpRZc2Qd/rotor/mylogfile.3.log" +#> [4] "/tmp/RtmpRZc2Qd/rotor/mylogfile.4.log.zip" ``` We can also use `prune_backups()` to delete old backups. Other than @@ -154,29 +154,29 @@ backup_time(tf, format = "%Y%m%dT%H%M%S") # ISO 8601 compatible backup_info(tf) #> path name -#> 1 /tmp/Rtmpw73XUg/rotor/mylogfile.2020-07-24_10-54-30.log mylogfile -#> 2 /tmp/Rtmpw73XUg/rotor/mylogfile.2020-07-24--10-54-30.log mylogfile -#> 5 /tmp/Rtmpw73XUg/rotor/mylogfile.20200724T105430.log mylogfile -#> 3 /tmp/Rtmpw73XUg/rotor/mylogfile.2020-07-24.log mylogfile -#> 4 /tmp/Rtmpw73XUg/rotor/mylogfile.2020-07.log mylogfile +#> 1 /tmp/RtmpRZc2Qd/rotor/mylogfile.2020-12-11_20-23-35.log mylogfile +#> 2 /tmp/RtmpRZc2Qd/rotor/mylogfile.2020-12-11--20-23-35.log mylogfile +#> 5 /tmp/RtmpRZc2Qd/rotor/mylogfile.20201211T202335.log mylogfile +#> 3 /tmp/RtmpRZc2Qd/rotor/mylogfile.2020-12-11.log mylogfile +#> 4 /tmp/RtmpRZc2Qd/rotor/mylogfile.2020-12.log mylogfile #> sfx ext size isdir mode mtime -#> 1 2020-07-24_10-54-30 log 26 FALSE 664 2020-07-24 10:54:30 -#> 2 2020-07-24--10-54-30 log 26 FALSE 664 2020-07-24 10:54:30 -#> 5 20200724T105430 log 26 FALSE 664 2020-07-24 10:54:30 -#> 3 2020-07-24 log 26 FALSE 664 2020-07-24 10:54:30 -#> 4 2020-07 log 26 FALSE 664 2020-07-24 10:54:30 -#> ctime atime uid gid uname grname -#> 1 2020-07-24 10:54:30 2020-07-24 10:54:30 11861 11861 fleck fleck -#> 2 2020-07-24 10:54:30 2020-07-24 10:54:30 11861 11861 fleck fleck -#> 5 2020-07-24 10:54:30 2020-07-24 10:54:30 11861 11861 fleck fleck -#> 3 2020-07-24 10:54:30 2020-07-24 10:54:30 11861 11861 fleck fleck -#> 4 2020-07-24 10:54:30 2020-07-24 10:54:30 11861 11861 fleck fleck +#> 1 2020-12-11_20-23-35 log 26 FALSE 664 2020-12-11 20:23:35 +#> 2 2020-12-11--20-23-35 log 26 FALSE 664 2020-12-11 20:23:35 +#> 5 20201211T202335 log 26 FALSE 664 2020-12-11 20:23:35 +#> 3 2020-12-11 log 26 FALSE 664 2020-12-11 20:23:35 +#> 4 2020-12 log 26 FALSE 664 2020-12-11 20:23:35 +#> ctime atime uid gid uname grname +#> 1 2020-12-11 20:23:35 2020-12-11 20:23:35 1000 1000 hoelk hoelk +#> 2 2020-12-11 20:23:35 2020-12-11 20:23:35 1000 1000 hoelk hoelk +#> 5 2020-12-11 20:23:35 2020-12-11 20:23:35 1000 1000 hoelk hoelk +#> 3 2020-12-11 20:23:35 2020-12-11 20:23:35 1000 1000 hoelk hoelk +#> 4 2020-12-11 20:23:35 2020-12-11 20:23:35 1000 1000 hoelk hoelk #> timestamp -#> 1 2020-07-24 10:54:30 -#> 2 2020-07-24 10:54:30 -#> 5 2020-07-24 10:54:30 -#> 3 2020-07-24 00:00:00 -#> 4 2020-07-01 00:00:00 +#> 1 2020-12-11 20:23:35 +#> 2 2020-12-11 20:23:35 +#> 5 2020-12-11 20:23:35 +#> 3 2020-12-11 00:00:00 +#> 4 2020-12-01 00:00:00 ``` If we examine the “timestamp” column in the example above, we see that @@ -203,20 +203,20 @@ prune_backups(tf, "2018-04-01") ## Cache -rotor also provides a simple on-disk key-value store that can be used as -a persistent cache. +rotor also provides a simple on-disk key-value store that can be pruned +by size, age or number of files. ``` r cache <- Cache$new(file.path(tempdir(), "cache-test"), hashfun = digest::digest) -#> creating directory '/tmp/Rtmpw73XUg/cache-test' +#> creating directory '/tmp/RtmpRZc2Qd/cache-test' key1 <- cache$push(iris) key2 <- cache$push(cars) key3 <- cache$push(mtcars) cache$files$path -#> [1] "/tmp/Rtmpw73XUg/cache-test/d3c5d071001b61a9f6131d3004fd0988" -#> [2] "/tmp/Rtmpw73XUg/cache-test/f98a59010652c8e1ee062ed4c43f648e" -#> [3] "/tmp/Rtmpw73XUg/cache-test/a63c70e73b58d0823ab3bcbd3b543d6f" +#> [1] "/tmp/RtmpRZc2Qd/cache-test/d3c5d071001b61a9f6131d3004fd0988" +#> [2] "/tmp/RtmpRZc2Qd/cache-test/f98a59010652c8e1ee062ed4c43f648e" +#> [3] "/tmp/RtmpRZc2Qd/cache-test/a63c70e73b58d0823ab3bcbd3b543d6f" head(cache$read(key1)) #> Sepal.Length Sepal.Width Petal.Length Petal.Width Species @@ -229,7 +229,7 @@ head(cache$read(key1)) cache$prune(max_files = 1) cache$files$path -#> [1] "/tmp/Rtmpw73XUg/cache-test/a63c70e73b58d0823ab3bcbd3b543d6f" +#> [1] "/tmp/RtmpRZc2Qd/cache-test/a63c70e73b58d0823ab3bcbd3b543d6f" cache$purge() # deletes all cached files cache$destroy() # deletes the cache directory ``` @@ -251,11 +251,14 @@ anything outside of base R) Optional dependencies: - - [digest](https://github.com/eddelbuettel/digest) or + - [digest](https://github.com/eddelbuettel/digest), + [ulid](https://cran.r-project.org/package=ulid), or [uuid](https://CRAN.R-project.org/package=uuid) for generating - hashes or UIDs when using Cache. Storage keys for cache files can + hashes or UIDs when using `Cache`. Storage keys for cache files can also be set manually, in which case no external dependencies are required. - [zip](https://CRAN.R-project.org/package=zip) is supported as an alternative to the integrated zip function in R. Might work better on some systems and worse on others. + - [crayon](https://cran.r-project.org/package=crayon) for terminal + colors diff --git a/cran-comments.md b/cran-comments.md index 2dab083..bde4bbd 100644 --- a/cran-comments.md +++ b/cran-comments.md @@ -1,14 +1,15 @@ ## Test environments -* ubuntu 20.04, R 4.0.2 -* ubuntu 18.04 (via RStudio Server), R 4.0.2 -* ubuntu 16.04 (via travis), R 3.6.1 -* win-builder, R devel -* macOS 10.13.6 High Sierra, R-release, brew (via Rhub) -* rhub::check_for_cran() +- ubuntu 20.04, R 4.0.2 +- ubuntu 18.04 (via RStudio Server), R 4.0.2 +- ubuntu 16.04 (via travis), R 3.6.1 +- win-builder, R devel +- R-hub ubuntu-gcc-release (r-release) +- R-hub windows-x86_64-devel (r-devel) +- R-hub fedora-clang-devel (r-devel) +- R-hub macos-highsierra-release (r-release) ## R CMD check results 0 errors | 0 warnings | 0 notes -More fixes for file-system timestamp related bugs that I could not reproduce on -my test systems. Sorry again for all the trouble. +Maintenance release with some small bug fixes diff --git a/tests/testthat/test_BackupQueue.R b/tests/testthat/test_BackupQueue.R index a0a6250..25c46ab 100644 --- a/tests/testthat/test_BackupQueue.R +++ b/tests/testthat/test_BackupQueue.R @@ -397,8 +397,8 @@ test_that("BackupQueue$push() works as expected", { dir.create(td, recursive = TRUE) on.exit(unlink(td, recursive = TRUE)) - if (!is_zipcmd_available()) - skip("Test requires a workings system zip command") + skip_if_not(is_zipcmd_available(), "system zip-command is available") + tf <- file.path(td, "test.log") file.create(tf) @@ -430,8 +430,7 @@ test_that("BackupQueueIndex$push() can push to different directory", { dir.create(td, recursive = TRUE) on.exit(unlink(td, recursive = TRUE)) - if (!is_zipcmd_available()) - skip("Test requires a workings system zip command") + skip_if_not(is_zipcmd_available(), "system zip-command is available") tf <- file.path(td, "test.log") bu_dir <- file.path(td, "backups") @@ -575,7 +574,7 @@ test_that("BackupQueueIndex: rotations trigger as expected", { expect_false(bq$should_rotate(size = file.size(tf) + 1)) # size threshold is met - expect_true(bq$should_rotate(size = file.size(tf) / 2)) + expect_true(bq$should_rotate(size = round(file.size(tf) / 2))) }) @@ -884,8 +883,7 @@ test_that("BackupQueueDateTime$push() can push to different directory", { dir.create(td, recursive = TRUE) on.exit(unlink(td, recursive = TRUE)) - if (!is_zipcmd_available()) - skip("Test requires a workings system zip command") + skip_if_not(is_zipcmd_available(), "system zip-command is available") tf <- file.path(td, "test.log") bu_dir <- file.path(td, "backups") @@ -978,13 +976,13 @@ test_that("BackupQueueDateTime: rotations trigger as expected", { expect_false(bq$should_rotate(size = file.size(tf) + 1, age = Inf)) # size threshold is met but not age - expect_false(bq$should_rotate(size = file.size(tf) / 2, age = "2 days", now = as.POSIXct("2020-01-01 02:00:00"), last_rotation = fake_time)) + expect_false(bq$should_rotate(size = round(file.size(tf) / 2), age = "2 days", now = as.POSIXct("2020-01-01 02:00:00"), last_rotation = fake_time)) # age threshold is met but not size expect_false(bq$should_rotate(size = file.size(tf) + 1, age = "2 days", now = as.POSIXct("2020-01-04 02:00:00"), last_rotation = fake_time)) # both criteria are met - expect_true(bq$should_rotate(size = file.size(tf) / 2, age = "2 days", now = as.POSIXct("2020-01-06 02:00:00"), last_rotation = fake_time)) + expect_true(bq$should_rotate(size = round(file.size(tf) / 2), age = "2 days", now = as.POSIXct("2020-01-06 02:00:00"), last_rotation = fake_time)) }) @@ -1268,8 +1266,7 @@ test_that("BackupQueueDateTime$push() can push to different directory", { dir.create(td, recursive = TRUE) on.exit(unlink(td, recursive = TRUE)) - if (!is_zipcmd_available()) - skip("Test requires a workings system zip command") + skip_if_not(is_zipcmd_available(), "system zip-command is available") tf <- file.path(td, "test.log") bu_dir <- file.path(td, "backups") @@ -1400,13 +1397,13 @@ test_that("BackupQueueDate: rotations trigger as expected", { expect_false(bq$should_rotate(size = file.size(tf) + 1, age = Inf)) # size threshold is met but not age - expect_false(bq$should_rotate(size = file.size(tf) / 2, age = "2 days", now = as.POSIXct("2020-01-01 02:00:00"), last_rotation = fake_time)) + expect_false(bq$should_rotate(size = floor(file.size(tf) / 2), age = "2 days", now = as.POSIXct("2020-01-01 02:00:00"), last_rotation = fake_time)) # age threshold is met but not size - expect_false(bq$should_rotate(size = file.size(tf) + 1, age = "2 days", now = as.POSIXct("2020-01-04 02:00:00"), last_rotation = fake_time)) + expect_false(bq$should_rotate(size = round(file.size(tf) + 1), age = "2 days", now = as.POSIXct("2020-01-04 02:00:00"), last_rotation = fake_time)) # both criteria are met - expect_true(bq$should_rotate(size = file.size(tf) / 2, age = "2 days", now = as.POSIXct("2020-01-06 02:00:00"), last_rotation = fake_time)) + expect_true(bq$should_rotate(size = floor(file.size(tf) / 2), age = "2 days", now = as.POSIXct("2020-01-06 02:00:00"), last_rotation = fake_time)) }) diff --git a/tests/testthat/test_copy_or_compress.R b/tests/testthat/test_copy_or_compress.R index 1708fe0..d31b703 100644 --- a/tests/testthat/test_copy_or_compress.R +++ b/tests/testthat/test_copy_or_compress.R @@ -25,8 +25,7 @@ create_testfile <- function(){ test_that("copy_or_compress works with default zip command", { - if (!is_zipcmd_available()) - skip("Test requires a workings system zip command") + skip_if_not(is_zipcmd_available(), "system zip-command is available") tf <- file.path(td, "compresstest.log") on.exit(unlink(tf)) @@ -42,8 +41,7 @@ test_that("copy_or_compress works with default zip command", { test_that("copy_or_compress works with internal zip command", { - if (!is_zipcmd_available()) - skip("Test requires a workings system zip command") + skip_if_not(is_zipcmd_available(), "system zip-command is available") tf <- file.path(td, "compresstest.log") on.exit(unlink(tf)) @@ -60,6 +58,7 @@ test_that("copy_or_compress works with internal zip command", { test_that("copy_or_compress works with zip::zipr", { skip_if_not_installed("zip") + skip_if_not(is_zipcmd_available(), "system zip-command is available") tf <- file.path(td, "compresstest.log") on.exit(unlink(tf)) @@ -75,6 +74,9 @@ test_that("copy_or_compress works with zip::zipr", { test_that("copy_or_compress preserves timestamp", { + skip_if_not_installed("zip") + skip_if_not(is_zipcmd_available(), "system zip-command is available") + tf <- create_testfile() on.exit(unlink(tf)) @@ -91,3 +93,4 @@ test_that("copy_or_compress preserves timestamp", { expect_true(equalish(file.mtime(zip), file.mtime(tf), timestamp_tolerance)) }) + diff --git a/tests/testthat/test_parsers.R b/tests/testthat/test_parsers.R index 838bb38..8edef90 100644 --- a/tests/testthat/test_parsers.R +++ b/tests/testthat/test_parsers.R @@ -39,6 +39,15 @@ test_that("parse_info_unit works", { +test_that("parse_size throws warning when it encounters floats", { + x <- parse_size(18) + expect_warning({y <- parse_size(18.9)}) + expect_identical(x, y) +}) + + + + test_that("parse_datetime works as expected", { d <- as.Date("2019-12-01") expect_equal(parse_datetime(d), as.POSIXct(as.character(d))) @@ -114,5 +123,4 @@ test_that("parse_date works as expected", { expect_equal(parse_date("20190412"), d) expect_equal(parse_date("201904"), as.Date("2019-04-01")) expect_equal(parse_date("2019"), as.Date("2019-01-01")) - }) diff --git a/tests/testthat/test_rotate.R b/tests/testthat/test_rotate.R index 1dd1916..b66cb86 100644 --- a/tests/testthat/test_rotate.R +++ b/tests/testthat/test_rotate.R @@ -14,8 +14,7 @@ teardown({ test_that("backup/rotate happy path", { - if (!is_zipcmd_available()) - skip("Test requires a workings system zip command") + skip_if_not(is_zipcmd_available(), "system zip-command is available") tf <- file.path(td, "test.log") saveRDS(iris, tf) diff --git a/tests/testthat/test_utils-predicates.R b/tests/testthat/test_utils-predicates.R index e82912f..5cba8d3 100644 --- a/tests/testthat/test_utils-predicates.R +++ b/tests/testthat/test_utils-predicates.R @@ -5,7 +5,7 @@ context("utils-predicates") test_that("is_zipcmd_available detects zipcommand", { # can only return true on platforms with a zip command - skip_if_not(is_zipcmd_available(), "No zipcommand found") + skip_if_not(is_zipcmd_available(), "system zip-command is available") expect_true(is_zipcmd_available()) })