├── zio1 ├── project │ └── build.properties ├── zio-path │ └── src │ │ └── main │ │ └── scala │ │ ├── utils │ │ ├── Header.java │ │ ├── UrlRequest.java │ │ └── Archive.java │ │ └── ZPath.scala ├── build.sbt └── LICENSE ├── zio2 ├── project │ └── build.properties ├── build.sbt ├── zio-path │ └── src │ │ └── main │ │ └── scala │ │ ├── utils │ │ └── Archive.java │ │ └── ZPath.scala └── LICENSE ├── .gitignore └── README.md /zio1/project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version=1.9.7 2 | 3 | -------------------------------------------------------------------------------- /zio2/project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version=1.9.7 2 | 3 | -------------------------------------------------------------------------------- /zio1/zio-path/src/main/scala/utils/Header.java: -------------------------------------------------------------------------------- 1 | package io.github.karimagnusson.zio.path.utils; 2 | 3 | 4 | public class Header { 5 | 6 | String key; 7 | String value; 8 | 9 | public Header(String headerKey, String headerValue) { 10 | key = headerKey; 11 | value = headerValue; 12 | } 13 | 14 | public String getKey() { 15 | return key; 16 | } 17 | 18 | public String getValue() { 19 | return value; 20 | } 21 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.class 2 | *.log 3 | *.class 4 | **/.DS_Store 5 | .DS_Store 6 | .sbt 7 | .bsp 8 | .history 9 | .cache-main 10 | .scala_dependencies 11 | target/ 12 | project/project/ 13 | project/target/ 14 | hs_err_pid* 15 | zio1/src/main/scala/Test.scala 16 | zio1/files/ 17 | zio1/project/project/ 18 | zio1/project/target/ 19 | zio1/publish.sbt 20 | zio1/project/plugins.sbt 21 | zio2/src/main/scala/Test.scala 22 | zio2/files/ 23 | zio2/project/project/ 24 | zio2/project/target/ 25 | zio2/publish.sbt 26 | zio2/project/plugins.sbt -------------------------------------------------------------------------------- /zio2/build.sbt: -------------------------------------------------------------------------------- 1 | 2 | inThisBuild(List( 3 | organization := "io.github.karimagnusson", 4 | homepage := Some(url("https://kuzminki.info/")), 5 | licenses := List("Apache-2.0" -> url("http://www.apache.org/licenses/LICENSE-2.0")), 6 | developers := List( 7 | Developer( 8 | "karimagnusson", 9 | "Kari Magnusson", 10 | "kotturinn@gmail.com", 11 | url("https://github.com/karimagnusson") 12 | ) 13 | ) 14 | )) 15 | 16 | ThisBuild / version := "2.0.2-RC5" 17 | ThisBuild / versionScheme := Some("early-semver") 18 | 19 | scalaVersion := "3.3.1" 20 | 21 | lazy val scala3 = "3.3.1" 22 | lazy val scala213 = "2.13.12" 23 | lazy val scala212 = "2.12.18" 24 | lazy val supportedScalaVersions = List(scala212, scala213, scala3) 25 | 26 | lazy val root = (project in file(".")) 27 | .aggregate(zioPath) 28 | .settings( 29 | crossScalaVersions := Nil, 30 | publish / skip := true 31 | ) 32 | 33 | lazy val zioPath = (project in file("zio-path")) 34 | .settings( 35 | name := "zio-path", 36 | crossScalaVersions := supportedScalaVersions, 37 | libraryDependencies ++= Seq( 38 | "dev.zio" %% "zio" % "2.0.21", 39 | "dev.zio" %% "zio-streams" % "2.0.21", 40 | "org.apache.commons" % "commons-compress" % "1.26.1", 41 | "org.scala-lang.modules" %% "scala-collection-compat" % "2.11.0" 42 | ), 43 | scalacOptions ++= Seq( 44 | "-encoding", "utf8", 45 | "-feature", 46 | "-language:higherKinds", 47 | "-language:existentials", 48 | "-language:implicitConversions", 49 | "-deprecation", 50 | "-unchecked" 51 | ), 52 | scalacOptions ++= { 53 | CrossVersion.partialVersion(scalaVersion.value) match { 54 | case Some((3, _)) => Seq("-rewrite") 55 | case _ => Seq("-Xlint") 56 | } 57 | } 58 | ) 59 | 60 | -------------------------------------------------------------------------------- /zio1/build.sbt: -------------------------------------------------------------------------------- 1 | 2 | inThisBuild(List( 3 | organization := "io.github.karimagnusson", 4 | homepage := Some(url("https://kuzminki.info/")), 5 | licenses := List("Apache-2.0" -> url("http://www.apache.org/licenses/LICENSE-2.0")), 6 | developers := List( 7 | Developer( 8 | "karimagnusson", 9 | "Kari Magnusson", 10 | "kotturinn@gmail.com", 11 | url("https://github.com/karimagnusson") 12 | ) 13 | ) 14 | )) 15 | 16 | ThisBuild / version := "1.0.2-RC4" 17 | ThisBuild / versionScheme := Some("early-semver") 18 | 19 | scalaVersion := "3.3.1" 20 | 21 | lazy val scala3 = "3.3.1" 22 | lazy val scala213 = "2.13.12" 23 | lazy val scala212 = "2.12.18" 24 | lazy val supportedScalaVersions = List(scala212, scala213, scala3) 25 | 26 | lazy val root = (project in file(".")) 27 | .aggregate(zioPath) 28 | .settings( 29 | crossScalaVersions := Nil, 30 | publish / skip := true 31 | ) 32 | 33 | lazy val zioPath = (project in file("zio-path")) 34 | .settings( 35 | name := "zio-path", 36 | crossScalaVersions := supportedScalaVersions, 37 | libraryDependencies ++= Seq( 38 | "dev.zio" %% "zio" % "1.0.18", 39 | "dev.zio" %% "zio-streams" % "1.0.18", 40 | "org.apache.commons" % "commons-compress" % "1.26.1", 41 | "org.scala-lang.modules" %% "scala-collection-compat" % "2.11.0" 42 | ), 43 | scalacOptions ++= Seq( 44 | "-encoding", "utf8", 45 | "-feature", 46 | "-language:higherKinds", 47 | "-language:existentials", 48 | "-language:implicitConversions", 49 | "-deprecation", 50 | "-unchecked" 51 | ), 52 | scalacOptions ++= { 53 | CrossVersion.partialVersion(scalaVersion.value) match { 54 | case Some((3, _)) => Seq("-rewrite") 55 | case _ => Seq("-Xlint") 56 | } 57 | } 58 | ) 59 | 60 | 61 | 62 | 63 | -------------------------------------------------------------------------------- /zio1/zio-path/src/main/scala/utils/UrlRequest.java: -------------------------------------------------------------------------------- 1 | package io.github.karimagnusson.zio.path.utils; 2 | 3 | 4 | import java.net.URL; 5 | import java.nio.file.Path; 6 | import java.nio.file.Files; 7 | import java.io.IOException; 8 | import java.io.InputStream; 9 | import java.io.OutputStream; 10 | import java.io.FileInputStream; 11 | import java.io.FileOutputStream; 12 | import java.io.BufferedInputStream; 13 | import java.io.BufferedOutputStream; 14 | import java.io.BufferedReader; 15 | import java.io.InputStreamReader; 16 | import java.net.HttpURLConnection; 17 | import java.net.URLConnection; 18 | 19 | 20 | public class UrlRequest { 21 | 22 | public static void download(URL url, Path path, Header[] headers) throws IOException { 23 | 24 | HttpURLConnection conn = (HttpURLConnection) url.openConnection(); 25 | conn.setRequestMethod("GET"); 26 | 27 | for (Header header : headers) { 28 | conn.setRequestProperty(header.getKey(), header.getValue()); 29 | } 30 | 31 | int code = conn.getResponseCode(); 32 | if (code == HttpURLConnection.HTTP_OK) { 33 | try (InputStream in = conn.getInputStream(); 34 | FileOutputStream out = new FileOutputStream(path.toFile())) { 35 | 36 | int bytesRead = -1; 37 | byte[] buffer = new byte[1024]; 38 | while ((bytesRead = in.read(buffer)) != -1) { 39 | out.write(buffer, 0, bytesRead); 40 | } 41 | } 42 | } else { 43 | throw new IOException("Request failed with code " + code); 44 | } 45 | } 46 | 47 | public static String upload(URL url, Path path, Header[] headers) throws IOException { 48 | 49 | HttpURLConnection conn = (HttpURLConnection) url.openConnection(); 50 | conn.setDoOutput(true); 51 | conn.setRequestMethod("POST"); 52 | 53 | String mime = Files.probeContentType(path); 54 | if (mime != null) { 55 | conn.setRequestProperty("Content-Type", mime); 56 | } 57 | 58 | conn.setRequestProperty("Content-Length", String.valueOf(Files.size(path))); 59 | 60 | for (Header header : headers) { 61 | conn.setRequestProperty(header.getKey(), header.getValue()); 62 | } 63 | 64 | try (BufferedOutputStream bos = new BufferedOutputStream(conn.getOutputStream()); 65 | BufferedInputStream bis = new BufferedInputStream(new FileInputStream(path.toFile()))) { 66 | 67 | int i; 68 | while ((i = bis.read()) > 0) { 69 | bos.write(i); 70 | } 71 | } 72 | 73 | int code = conn.getResponseCode(); 74 | if (code == HttpURLConnection.HTTP_OK) { 75 | 76 | StringBuffer rsp = new StringBuffer(); 77 | 78 | try (BufferedReader in = new BufferedReader(new InputStreamReader(conn.getInputStream()))) { 79 | String line; 80 | while ((line = in.readLine()) != null) { 81 | rsp.append(line); 82 | } 83 | in.close(); 84 | } 85 | 86 | return rsp.toString(); 87 | } else { 88 | throw new IOException("Request failed with code " + code); 89 | } 90 | } 91 | } 92 | 93 | -------------------------------------------------------------------------------- /zio2/zio-path/src/main/scala/utils/Archive.java: -------------------------------------------------------------------------------- 1 | package io.github.karimagnusson.zio.path.utils; 2 | 3 | import java.nio.file.Files; 4 | import java.nio.file.Path; 5 | import java.io.File; 6 | import java.io.IOException; 7 | import java.io.InputStream; 8 | import java.io.OutputStream; 9 | import java.io.FileInputStream; 10 | import java.io.FileOutputStream; 11 | import java.io.ByteArrayInputStream; 12 | import java.io.ByteArrayOutputStream; 13 | import java.io.BufferedInputStream; 14 | import java.io.BufferedOutputStream; 15 | import java.util.zip.GZIPInputStream; 16 | import java.util.zip.GZIPOutputStream; 17 | import java.util.zip.ZipFile; 18 | import java.util.zip.ZipEntry; 19 | import java.util.zip.ZipInputStream; 20 | import java.util.zip.ZipOutputStream; 21 | import java.util.stream.Stream; 22 | import org.apache.commons.compress.compressors.gzip.GzipCompressorInputStream; 23 | import org.apache.commons.compress.compressors.gzip.GzipCompressorOutputStream; 24 | import org.apache.commons.compress.archivers.tar.TarArchiveInputStream; 25 | import org.apache.commons.compress.archivers.tar.TarArchiveOutputStream; 26 | import org.apache.commons.compress.archivers.tar.TarArchiveEntry; 27 | 28 | 29 | public class Archive { 30 | 31 | public static void tar(Path inDir, Path outFile, boolean isGzip) throws IOException { 32 | 33 | try (BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(outFile.toFile())); 34 | TarArchiveOutputStream tos = new TarArchiveOutputStream( 35 | isGzip ? new GzipCompressorOutputStream(bos) : bos)) { 36 | 37 | Path relDir = inDir.getParent(); 38 | 39 | for (Path path : Files.walk(inDir).toList()) { 40 | 41 | File file = path.toFile(); 42 | 43 | if (Files.isSymbolicLink(path)) { 44 | continue; 45 | } 46 | 47 | TarArchiveEntry tarEntry = new TarArchiveEntry( 48 | file, 49 | relDir.relativize(path).toString() 50 | ); 51 | 52 | tos.putArchiveEntry(tarEntry); 53 | if (file.isFile()) { 54 | tos.write(Files.readAllBytes(path)); 55 | } 56 | tos.closeArchiveEntry(); 57 | } 58 | tos.finish(); 59 | } 60 | } 61 | 62 | public static void untar(Path inFile, Path outDir, boolean isGzip) throws IOException { 63 | 64 | try (BufferedInputStream bis = new BufferedInputStream(new FileInputStream(inFile.toFile())); 65 | TarArchiveInputStream tar = new TarArchiveInputStream( 66 | isGzip ? new GzipCompressorInputStream(bis) : bis)) { 67 | 68 | Files.createDirectories(outDir); 69 | 70 | TarArchiveEntry entry; 71 | 72 | while ((entry = tar.getNextEntry()) != null) { 73 | 74 | Path dest = outDir.resolve(entry.getName()); 75 | if (entry.isDirectory()) { 76 | Files.createDirectories(dest); 77 | } else { 78 | Files.copy(tar, dest); 79 | } 80 | } 81 | } 82 | } 83 | 84 | public static void zip(Path inDir, Path outFile) throws IOException { 85 | 86 | try (ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(outFile.toFile()))) { 87 | 88 | Path parentDir = inDir.getParent(); 89 | 90 | for (Path path : Files.walk(inDir).toList()) { 91 | 92 | File file = path.toFile(); 93 | String relPath = parentDir.relativize(path).toString(); 94 | 95 | if (Files.isSymbolicLink(path)) { 96 | continue; 97 | } 98 | 99 | if (file.isFile()) { 100 | 101 | zos.putNextEntry(new ZipEntry(relPath.toString())); 102 | 103 | FileInputStream fis = new FileInputStream(file); 104 | 105 | byte[] buffer = new byte[1024]; 106 | int len; 107 | while ((len = fis.read(buffer)) > 0) { 108 | zos.write(buffer, 0, len); 109 | } 110 | 111 | fis.close(); 112 | 113 | } else { 114 | 115 | if (!relPath.endsWith("/")) { 116 | relPath = relPath + "/"; 117 | } 118 | 119 | zos.putNextEntry(new ZipEntry(relPath.toString())); 120 | } 121 | 122 | zos.closeEntry(); 123 | } 124 | } 125 | } 126 | 127 | public static void unzip(Path inFile, Path outDir) throws IOException { 128 | 129 | try (ZipInputStream zis = new ZipInputStream(new FileInputStream(inFile.toFile()))) { 130 | 131 | Files.createDirectories(outDir); 132 | 133 | ZipEntry entry = zis.getNextEntry(); 134 | 135 | while ((entry = zis.getNextEntry()) != null) { 136 | 137 | Path dest = outDir.resolve(entry.getName()); 138 | if (entry.isDirectory()) { 139 | Files.createDirectories(dest); 140 | } else { 141 | Files.createDirectories(dest.getParent()); 142 | Files.copy(zis, dest); 143 | } 144 | } 145 | } 146 | } 147 | } 148 | 149 | 150 | 151 | 152 | 153 | 154 | -------------------------------------------------------------------------------- /zio1/zio-path/src/main/scala/utils/Archive.java: -------------------------------------------------------------------------------- 1 | package io.github.karimagnusson.zio.path.utils; 2 | 3 | import java.nio.file.Files; 4 | import java.nio.file.Path; 5 | import java.io.File; 6 | import java.io.IOException; 7 | import java.io.InputStream; 8 | import java.io.OutputStream; 9 | import java.io.FileInputStream; 10 | import java.io.FileOutputStream; 11 | import java.io.ByteArrayInputStream; 12 | import java.io.ByteArrayOutputStream; 13 | import java.io.BufferedInputStream; 14 | import java.io.BufferedOutputStream; 15 | import java.util.zip.GZIPInputStream; 16 | import java.util.zip.GZIPOutputStream; 17 | import java.util.zip.ZipFile; 18 | import java.util.zip.ZipEntry; 19 | import java.util.zip.ZipInputStream; 20 | import java.util.zip.ZipOutputStream; 21 | import java.util.stream.Stream; 22 | import org.apache.commons.compress.compressors.gzip.GzipCompressorInputStream; 23 | import org.apache.commons.compress.compressors.gzip.GzipCompressorOutputStream; 24 | import org.apache.commons.compress.archivers.tar.TarArchiveInputStream; 25 | import org.apache.commons.compress.archivers.tar.TarArchiveOutputStream; 26 | import org.apache.commons.compress.archivers.tar.TarArchiveEntry; 27 | 28 | 29 | public class Archive { 30 | 31 | public static void gzip(Path inPath, Path outPath) throws IOException { 32 | 33 | try (InputStream in = Files.newInputStream(inPath); 34 | OutputStream fout = Files.newOutputStream(outPath); 35 | BufferedOutputStream out = new BufferedOutputStream(fout); 36 | GzipCompressorOutputStream gzOut = new GzipCompressorOutputStream(out)) { 37 | 38 | final byte[] buffer = new byte[1024]; 39 | int n = 0; 40 | while (-1 != (n = in.read(buffer))) { 41 | gzOut.write(buffer, 0, n); 42 | } 43 | } 44 | } 45 | 46 | public static void ungzip(Path inPath, Path outPath) throws IOException { 47 | 48 | try (InputStream fin = Files.newInputStream(inPath); 49 | BufferedInputStream in = new BufferedInputStream(fin); 50 | OutputStream out = Files.newOutputStream(outPath); 51 | GzipCompressorInputStream gzIn = new GzipCompressorInputStream(in)) { 52 | 53 | final byte[] buffer = new byte[1024]; 54 | int n = 0; 55 | while (-1 != (n = gzIn.read(buffer))) { 56 | out.write(buffer, 0, n); 57 | } 58 | } 59 | } 60 | 61 | public static void tar(Path inDir, Path outFile, boolean isGzip) throws IOException { 62 | 63 | try (BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(outFile.toFile())); 64 | TarArchiveOutputStream tos = new TarArchiveOutputStream( 65 | isGzip ? new GzipCompressorOutputStream(bos) : bos)) { 66 | 67 | Path relDir = inDir.getParent(); 68 | 69 | for (Path path : Files.walk(inDir).toList()) { 70 | 71 | File file = path.toFile(); 72 | 73 | if (Files.isSymbolicLink(path)) { 74 | continue; 75 | } 76 | 77 | TarArchiveEntry tarEntry = new TarArchiveEntry( 78 | file, 79 | relDir.relativize(path).toString() 80 | ); 81 | 82 | tos.putArchiveEntry(tarEntry); 83 | if (file.isFile()) { 84 | tos.write(Files.readAllBytes(path)); 85 | } 86 | tos.closeArchiveEntry(); 87 | } 88 | tos.finish(); 89 | } 90 | } 91 | 92 | public static void untar(Path inFile, Path outDir, boolean isGzip) throws IOException { 93 | 94 | try (BufferedInputStream bis = new BufferedInputStream(new FileInputStream(inFile.toFile())); 95 | TarArchiveInputStream tar = new TarArchiveInputStream( 96 | isGzip ? new GzipCompressorInputStream(bis) : bis)) { 97 | 98 | Files.createDirectories(outDir); 99 | 100 | TarArchiveEntry entry; 101 | 102 | while ((entry = tar.getNextEntry()) != null) { 103 | 104 | Path dest = outDir.resolve(entry.getName()); 105 | if (entry.isDirectory()) { 106 | Files.createDirectories(dest); 107 | } else { 108 | Files.copy(tar, dest); 109 | } 110 | } 111 | } 112 | } 113 | 114 | public static void zip(Path inDir, Path outFile) throws IOException { 115 | 116 | try (ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(outFile.toFile()))) { 117 | 118 | Path parentDir = inDir.getParent(); 119 | 120 | for (Path path : Files.walk(inDir).toList()) { 121 | 122 | File file = path.toFile(); 123 | String relPath = parentDir.relativize(path).toString(); 124 | 125 | if (Files.isSymbolicLink(path)) { 126 | continue; 127 | } 128 | 129 | if (file.isFile()) { 130 | 131 | zos.putNextEntry(new ZipEntry(relPath.toString())); 132 | 133 | FileInputStream fis = new FileInputStream(file); 134 | 135 | byte[] buffer = new byte[1024]; 136 | int len; 137 | while ((len = fis.read(buffer)) > 0) { 138 | zos.write(buffer, 0, len); 139 | } 140 | 141 | fis.close(); 142 | 143 | } else { 144 | 145 | if (!relPath.endsWith("/")) { 146 | relPath = relPath + "/"; 147 | } 148 | 149 | zos.putNextEntry(new ZipEntry(relPath.toString())); 150 | } 151 | 152 | zos.closeEntry(); 153 | } 154 | } 155 | } 156 | 157 | public static void unzip(Path inFile, Path outDir) throws IOException { 158 | 159 | try (ZipInputStream zis = new ZipInputStream(new FileInputStream(inFile.toFile()))) { 160 | 161 | Files.createDirectories(outDir); 162 | 163 | ZipEntry entry = zis.getNextEntry(); 164 | 165 | while ((entry = zis.getNextEntry()) != null) { 166 | 167 | Path dest = outDir.resolve(entry.getName()); 168 | if (entry.isDirectory()) { 169 | Files.createDirectories(dest); 170 | } else { 171 | Files.createDirectories(dest.getParent()); 172 | Files.copy(zis, dest); 173 | } 174 | } 175 | } 176 | } 177 | } 178 | 179 | 180 | 181 | 182 | 183 | 184 | -------------------------------------------------------------------------------- /zio1/LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /zio2/LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Twitter URL](https://img.shields.io/twitter/url/https/twitter.com/bukotsunikki.svg?style=social&label=Follow%20%40kuzminki_lib)](https://twitter.com/kuzminki_lib) 2 | 3 | # zio-path 4 | 5 | zio-path is a simple library for working with files and folders in ZIO. It is a wrapper for java.nio.files.Path. Files and folders are handled separately with ZFile and ZDir which provide convenient methods for working with files and folders in ZIO. This library is available for ZIO 1 and ZIO 2. 6 | 7 | This documentation is for version 2.0.1. Below is another for 2.0.2-RC5 that adds file download and upload and support for gzip, zip and tar. 8 | 9 | Please report bugs if you find them and feel free to DM me on Twitter if you have any questions. 10 | 11 | #### Sbt 12 | ```sbt 13 | // available for Scala 2.12, 2.13 and 3 14 | 15 | // for ZIO 1 16 | libraryDependencies += "io.github.karimagnusson" %% "zio-path" % "1.0.1" 17 | 18 | // for ZIO 2 19 | libraryDependencies += "io.github.karimagnusson" %% "zio-path" % "2.0.1" 20 | ``` 21 | 22 | #### Create instance 23 | ```scala 24 | import io.github.karimagnusson.zio.path._ 25 | 26 | // ZFile and ZDir are wrappers for Path and created in the same way. 27 | val textFile = ZFile(Paths.get("/path/to/files/file.txt")) 28 | val filesDir = ZDir.rel("files") // relative path 29 | val imgFile = ZFile.get(filesDir, "image.jpg") 30 | ``` 31 | 32 | #### Example 33 | ```scala 34 | import io.github.karimagnusson.zio.path._ 35 | 36 | val filesDir = ZDir.rel("files") 37 | val textFile = filesDir.file("text.txt") 38 | val oldFolder = filesDir.dir("old-folder") 39 | 40 | val job = for { 41 | lines <- textFile.readLines 42 | _ <- filesDir.file("text-copy.txt").write(lines) 43 | imgDir <- filesDir.mkdir("images") 44 | _ <- imgDir.file("pic.jpg").fillFrom(new URL("http://images.com/pic.jpg")) 45 | _ <- oldFolder.delete 46 | files <- filesDir.listFiles 47 | } yield files 48 | ``` 49 | 50 | #### ZPath 51 | 52 | ##### Static: 53 | ```scala 54 | def fromPath(path: Path) = Task[ZPath] // ZFile or ZDir 55 | def rel(parts: String*) = Task[ZPath] // ZFile or ZDir 56 | def get(first: String, rest: String*) = Task[ZPath] // ZFile or ZDir 57 | def pickFiles(paths: List[ZPath]): List[ZFile] 58 | def pickDirs(paths: List[ZPath]): List[ZDir] 59 | ``` 60 | 61 | Methods common to `ZFile` and `ZDir` 62 | ##### Methods: 63 | ```scala 64 | val path: Path 65 | def name: String // Name of the file or folder 66 | def isFile: Boolean 67 | def isDir: Boolean 68 | def startsWithDot: Boolean 69 | def parent: ZDir 70 | def delete: IO[IOException, Unit] // A folder will be deleted recursively 71 | def copy(dest: ZDir): Task[Unit] // A folder will be copied with all its contents 72 | def size: IO[IOException, Long] // If folder, then the size of all the containing files and folders 73 | def isEmpty: IO[IOException, Boolean] 74 | def nonEmpty: IO[IOException, Boolean] 75 | def exists: UIO[Boolean] 76 | def info: IO[IOException, ZPathInfo] 77 | ``` 78 | 79 | #### ZFile 80 | 81 | ##### Static: 82 | ```scala 83 | def fromPath(path: Path) = ZFile 84 | def rel(parts: String*): ZFile // Relative to working directory. Returns full path. 85 | def get(first: String, rest: String*): ZFile 86 | def deleteFiles(files: Seq[ZFile]): IO[IOException, Unit] 87 | ``` 88 | 89 | ##### Methods: 90 | ```scala 91 | def isFile: Boolean 92 | def isDir: Boolean 93 | def ext: Option[String] 94 | def extUpper: String 95 | def extLower: String 96 | def relTo(dir: ZDir): ZFile // The rest of the path relative to dir 97 | def assert: IO[IOException, ZFile] // Assert that the file axists and that it is a file 98 | def create: Task[ZFile] 99 | def size: IO[IOException, Long] 100 | def isEmpty: IO[IOException, Boolean] 101 | def nonEmpty: IO[IOException, Boolean] 102 | def delete: IO[IOException, Unit] 103 | def readBytes: IO[IOException, Array[Byte]] 104 | def readString: IO[IOException, String] 105 | def readLines: IO[IOException, List[String]] 106 | def write(bytes: Array[Byte]): Task[Unit] 107 | def write(str: String): Task[Unit] 108 | def write(lines: Seq[String]): Task[Unit] 109 | def append(bytes: Array[Byte]): Task[Unit] 110 | def append(str: String): Task[Unit] 111 | def append(lines: Seq[String]): Task[Unit] 112 | def copy(target: ZFile): Task[Unit] 113 | def copy(dest: ZDir): Task[Unit] 114 | def rename(target: ZFile): Task[ZFile] 115 | def rename(fileName: String): Task[ZFile] 116 | def moveTo(dest: ZDir): Task[ZFile] 117 | def fillFrom(url: URL): Task[Long] // Download file contents from URL to this file 118 | def asSink: ZSink[Any, Throwable, Byte, Byte, Long] 119 | def asStringSink: ZSink[Any, Throwable, String, Byte, Long] 120 | def streamBytes: ZStream[Any, Throwable, Byte] 121 | def streamLines: ZStream[Any, Throwable, String] 122 | ``` 123 | 124 | #### ZDir 125 | 126 | ##### Static: 127 | ```scala 128 | def fromPath(path: Path): ZDir 129 | def rel(parts: String*): ZDir // Relative to working directory. Returns full path. 130 | def get(first: String, rest: String*): ZDir 131 | def mkdirs(dirs: Seq[ZDir]): IO[IOException, Seq[ZDir]]: Seq[ZDir] 132 | ``` 133 | 134 | ##### Methods: 135 | ```scala 136 | def isFile: Boolean 137 | def isDir: Boolean 138 | def relTo(other: ZDir): ZDir // The rest of the path relative to other 139 | def add(other: ZPath): ZPath 140 | def add(other: ZFile): ZFile 141 | def add(other: ZDir): ZDir 142 | def file(fileName: String): ZFile 143 | def dir(dirName: String): ZDir 144 | def assert: IO[IOException, ZDir] // Assert that the folder exists and that it is a folder 145 | def size: IO[IOException, Long] // The combined size of all the containing files and folders 146 | def isEmpty: IO[IOException, Boolean] 147 | def nonEmpty: IO[IOException, Boolean] 148 | def create: Task[ZDir] 149 | def mkdir(dirName: String): Task[ZDir] 150 | def mkdirs(dirNames: Seq[String]): IO[IOException, Seq[ZDir]] 151 | def rename(dest: ZDir): Task[ZDir] 152 | def rename(dirName: String): Task[ZDir] 153 | def moveTo(dest: ZDir): Task[ZDir] 154 | def moveHere(paths: Seq[ZPath]): Task[Seq[ZPath]] 155 | def delete: IO[IOException, Unit] // Delete the folder and all its contents 156 | def copy(other: ZDir): Task[Unit] // Copy the folder and all its contents 157 | def list: IO[IOException, List[ZPath]] // List all the files and folders 158 | def listFiles: IO[IOException, List[ZFile]] 159 | def listDirs: IO[IOException, List[ZDir]] 160 | def walk: IO[IOException, List[ZPath]] 161 | def walkFiles: IO[IOException, List[ZFile]] 162 | def walkDirs: IO[IOException, List[ZDir]] 163 | def streamWalk: ZStream[Any, Throwable, ZPath] 164 | def streamWalkFiles: ZStream[Any, Throwable, ZFile] 165 | def streamWalkDirs: ZStream[Any, Throwable, ZDir] 166 | ``` 167 | 168 | #### Version 2.0.2-RC5 169 | 170 | The latest release candidate adds methods to download and upload files and compress and uncompress gzip, zip and tar files. 171 | 172 | Download and upload uses ZIO streams. 173 | 174 | Gzip uses ZIO streams but zip and tar use Java code on the blocking thread pool. It is not ideal to run heavy processes on the blocking thread pool, so in future versions tar and zip will also use ZIO streams. 175 | 176 | #### Example 177 | ```scala 178 | import io.github.karimagnusson.zio.path._ 179 | 180 | val filesDir = ZDir.rel("files") 181 | val textFile = filesDir.file("text.txt") 182 | val oldFolder = filesDir.dir("old-folder") 183 | 184 | val job = for { 185 | lines <- textFile.readLines 186 | _ <- filesDir.file("text-copy.txt").write(lines) 187 | _ <- filesDir.file("profile.jpg").download("http://mysite.com/profile.jpg") 188 | textGz <- textFile.gzip // gzip 'text.txt' 189 | _ <- textGz.upload("http://mysite.com/files") // upload 'text.txt.gz' 190 | _ <- oldFolder.dir("docs").copyTo(filesDir) // Copy 'docs' and it's contents to 'files' 191 | _ <- oldFolder.delete // Delete the folder and it's contents 192 | files <- filesDir.listFiles 193 | } yield files 194 | ``` 195 | 196 | #### ZPath 197 | 198 | ##### Static: 199 | ```scala 200 | def fromPath(path: Path) = Task[ZPath] // ZFile or ZDir 201 | def rel(parts: String*) = Task[ZPath] // ZFile or ZDir 202 | def get(first: String, rest: String*) = Task[ZPath] // ZFile or ZDir 203 | def urlStream(url: String): ZStream[Any, IOException, Byte] 204 | def urlStream(url: String, headers: Map[String, String]): ZStream[Any, IOException, Byte] 205 | def urlSink(url: String): ZSink[Any, IOException, Byte, Byte, String] 206 | def urlSink(url: String, headers: Map[String, String]): ZSink[Any, IOException, Byte, Byte, String] 207 | ``` 208 | 209 | Methods common to `ZFile` and `ZDir` 210 | ##### Methods: 211 | ```scala 212 | val path: Path 213 | def name: String // Name of the file or folder 214 | def isFile: Boolean 215 | def isDir: Boolean 216 | def startsWithDot: Boolean 217 | def parent: ZDir 218 | def delete: IO[IOException, Unit] // A folder will be deleted recursively 219 | def copyTo(dest: ZDir): Task[Unit] // A folder will be copied with all its contents 220 | def size: IO[IOException, Long] // If folder, then the size of all the containing files and folders 221 | def isEmpty: IO[IOException, Boolean] 222 | def nonEmpty: IO[IOException, Boolean] 223 | def exists: UIO[Boolean] 224 | def info: IO[IOException, ZPathInfo] 225 | def toString: String 226 | ``` 227 | 228 | #### ZFile 229 | 230 | ##### Static: 231 | ```scala 232 | def fromPath(path: Path) = ZFile 233 | def rel(parts: String*): ZFile // Relative to working directory. Returns full path. 234 | def get(first: String, rest: String*): ZFile 235 | def deleteFiles(files: Seq[ZFile]): IO[IOException, Unit] 236 | ``` 237 | 238 | ##### Methods: 239 | ```scala 240 | def isFile: Boolean 241 | def isDir: Boolean 242 | def ext: Option[String] 243 | def extUpper: String 244 | def extLower: String 245 | def relTo(dir: ZDir): ZFile // The rest of the path relative to dir 246 | def assert: IO[IOException, ZFile] // Assert that the file exists and that it is a file 247 | def create: Task[ZFile] 248 | def size: IO[IOException, Long] 249 | def isEmpty: IO[IOException, Boolean] 250 | def nonEmpty: IO[IOException, Boolean] 251 | def delete: IO[IOException, Unit] 252 | def readBytes: IO[IOException, Array[Byte]] 253 | def readString: IO[IOException, String] 254 | def readLines: IO[IOException, List[String]] 255 | def write(bytes: Array[Byte]): Task[Unit] 256 | def write(str: String): Task[Unit] 257 | def write(lines: Seq[String]): Task[Unit] 258 | def append(bytes: Array[Byte]): Task[Unit] 259 | def append(str: String): Task[Unit] 260 | def append(lines: Seq[String]): Task[Unit] 261 | def copyTo(target: ZFile): Task[Unit] 262 | def copyTo(dest: ZDir): Task[Unit] 263 | def rename(target: ZFile): Task[ZFile] 264 | def rename(fileName: String): Task[ZFile] 265 | def moveTo(dest: ZDir): Task[ZFile] 266 | def mimeType: IO[IOException, Option[String]] 267 | def gzip: Task[ZFile] 268 | def gzip(out: ZFile): Task[ZFile] 269 | def ungzip: Task[ZFile] 270 | def ungzip(out: ZFile): Task[ZFile] 271 | def unzip: Task[ZDir] 272 | def unzip(dest: ZDir): Task[ZDir] 273 | def untar: Task[ZDir] 274 | def untar(dest: ZDir): Task[ZDir] 275 | def untarGz: Task[ZDir] 276 | def untarGz(dest: ZDir): Task[ZDir] 277 | def download(url: String): Task[ZFile] // Download file contents from URL to this file 278 | def download(url: String, headers: Map[String, String]): Task[ZFile] 279 | def upload(url: String): Task[String] 280 | def upload(url: String, headers: Map[String, String]): Task[String] 281 | def asSink: ZSink[Any, Throwable, Byte, Byte, Long] 282 | def asStringSink: ZSink[Any, Throwable, String, Byte, Long] 283 | def streamBytes: ZStream[Any, Throwable, Byte] 284 | def streamLines: ZStream[Any, Throwable, String] 285 | ``` 286 | 287 | #### ZDir 288 | 289 | ##### Static: 290 | ```scala 291 | def fromPath(path: Path): ZDir 292 | def rel(parts: String*): ZDir // Relative to working directory. Returns full path. 293 | def get(first: String, rest: String*): ZDir 294 | def mkdirs(dirs: Seq[String]): IO[IOException, Seq[ZDir]]: Seq[ZDir] 295 | ``` 296 | 297 | ##### Methods: 298 | ```scala 299 | def isFile: Boolean 300 | def isDir: Boolean 301 | def relTo(other: ZDir): ZDir // The rest of the path relative to other 302 | def add(other: ZPath): ZPath 303 | def add(other: ZFile): ZFile 304 | def add(other: ZDir): ZDir 305 | def file(fileName: String): ZFile 306 | def dir(dirName: String): ZDir 307 | def assert: IO[IOException, ZDir] // Assert that the folder exists and that it is a folder 308 | def size: IO[IOException, Long] // The combined size of all the containing files and folders 309 | def isEmpty: IO[IOException, Boolean] 310 | def nonEmpty: IO[IOException, Boolean] 311 | def create: Task[ZDir] 312 | def mkdir(dirName: String): Task[ZDir] 313 | def mkdirs(dirNames: Seq[String]): IO[IOException, Seq[ZDir]] 314 | def rename(dest: ZDir): Task[ZDir] 315 | def rename(dirName: String): Task[ZDir] 316 | def moveTo(dest: ZDir): Task[ZDir] 317 | def moveHere(paths: Seq[ZPath]): Task[Seq[ZPath]] 318 | def delete: IO[IOException, Unit] // Delete the folder and all its contents 319 | def copyTo(other: ZDir): Task[Unit] // Copy the folder and all its contents 320 | def zip: Task[ZFile] 321 | def zip(out: ZFile): Task[ZFile] 322 | def tar: Task[ZFile] 323 | def tar(dest: ZDir): Task[ZFile] 324 | def tarGz: Task[ZFile] 325 | def tarGz(dest: ZDir): Task[ZFile] 326 | def list: IO[IOException, List[ZPath]] // List all the files and folders 327 | def listFiles: IO[IOException, List[ZFile]] 328 | def listDirs: IO[IOException, List[ZDir]] 329 | def walk: IO[IOException, List[ZPath]] 330 | def walkFiles: IO[IOException, List[ZFile]] 331 | def walkDirs: IO[IOException, List[ZDir]] 332 | def streamWalk: ZStream[Any, Throwable, ZPath] 333 | def streamWalkFiles: ZStream[Any, Throwable, ZFile] 334 | def streamWalkDirs: ZStream[Any, Throwable, ZDir] 335 | ``` 336 | 337 | 338 | 339 | 340 | 341 | 342 | 343 | 344 | 345 | 346 | 347 | 348 | 349 | 350 | 351 | 352 | 353 | -------------------------------------------------------------------------------- /zio1/zio-path/src/main/scala/ZPath.scala: -------------------------------------------------------------------------------- 1 | package io.github.karimagnusson.zio.path 2 | 3 | import java.net.{URL, URI} 4 | import java.io.IOException 5 | import java.nio.file.attribute.FileTime 6 | import java.nio.file.{ 7 | Files, 8 | Paths, 9 | Path, 10 | StandardOpenOption 11 | } 12 | 13 | import scala.jdk.CollectionConverters._ 14 | import zio._ 15 | import zio.blocking._ 16 | import zio.stream.{ZStream, ZTransducer, ZSink} 17 | import io.github.karimagnusson.zio.path.utils._ 18 | 19 | 20 | case class ZPathInfo( 21 | path: Path, 22 | isDir: Boolean, 23 | isHidden: Boolean, 24 | isReadable: Boolean, 25 | isWritable: Boolean, 26 | isSymbolicLink: Boolean, 27 | lastModified: FileTime 28 | ) 29 | 30 | 31 | object ZPath { 32 | 33 | lazy val root = Paths.get("").toAbsolutePath.toString 34 | 35 | private def toZPath(path: Path): RIO[Blocking, ZPath] = effectBlocking { 36 | if (Files.isDirectory(path)) ZDir(path) else ZFile(path) 37 | } 38 | 39 | def fromPath(path: Path): RIO[Blocking, ZPath] = 40 | toZPath(path) 41 | 42 | def rel(parts: String*): RIO[Blocking, ZPath] = 43 | toZPath(Paths.get(root, parts: _*)) 44 | 45 | def get(first: String, rest: String*): RIO[Blocking, ZPath] = 46 | toZPath(Paths.get(first, rest: _*)) 47 | } 48 | 49 | 50 | sealed trait ZPath { 51 | 52 | val path: Path 53 | def name = path.getFileName.toString 54 | def isFile: Boolean 55 | def isDir: Boolean 56 | def show = path.toString 57 | def startsWithDot = name.head == '.' 58 | def parent = ZDir(path.getParent) 59 | 60 | def delete: ZIO[Blocking, IOException, Unit] 61 | def copy(dest: ZDir): RIO[Blocking, Unit] 62 | def copyTo(dest: ZDir): RIO[Blocking, Unit] 63 | def size: ZIO[Blocking, IOException, Long] 64 | def isEmpty: ZIO[Blocking, IOException, Boolean] 65 | def nonEmpty: ZIO[Blocking, IOException, Boolean] 66 | 67 | def exists: URIO[Blocking, Boolean] = effectBlocking { 68 | Files.exists(path) 69 | }.orDie 70 | 71 | def info: ZIO[Blocking, IOException, ZPathInfo] = effectBlocking { 72 | ZPathInfo( 73 | path, 74 | Files.isDirectory(path), 75 | Files.isHidden(path), 76 | Files.isReadable(path), 77 | Files.isWritable(path), 78 | Files.isSymbolicLink(path), 79 | Files.getLastModifiedTime(path) 80 | ) 81 | }.refineToOrDie[IOException] 82 | 83 | override def toString = path.toString 84 | } 85 | 86 | 87 | object ZFile { 88 | 89 | def fromPath(path: Path) = 90 | ZFile(path) 91 | 92 | def rel(parts: String*) = 93 | ZFile(Paths.get(ZPath.root, parts: _*)) 94 | 95 | def get(first: String, rest: String*) = 96 | ZFile(Paths.get(first, rest: _*)) 97 | 98 | def deleteFiles(files: Seq[ZFile]): ZIO[Blocking, IOException, Unit] = 99 | effectBlocking { 100 | files.foreach(f => Files.deleteIfExists(f.path)) 101 | }.unit.refineToOrDie[IOException] 102 | } 103 | 104 | 105 | case class ZFile(path: Path) extends ZPath { 106 | 107 | def isFile = true 108 | def isDir = false 109 | 110 | def ext = name.split('.').lastOption 111 | def extUpper = ext.map(_.toUpperCase).getOrElse("") 112 | def extLower = ext.map(_.toLowerCase).getOrElse("") 113 | 114 | def relTo(dir: ZDir) = ZFile(dir.path.relativize(path)) 115 | 116 | def assert: ZIO[Blocking, IOException, ZFile] = 117 | effectBlocking { 118 | if (!Files.exists(path)) 119 | throw new IOException(s"path does not exist: $path") 120 | if (!Files.isRegularFile(path)) 121 | throw new IOException(s"path is not a file: $path") 122 | this 123 | }.refineToOrDie[IOException] 124 | 125 | def create: RIO[Blocking, ZFile] = 126 | effectBlocking(Files.createFile(path)).map(_ => this) 127 | 128 | def size: ZIO[Blocking, IOException, Long] = 129 | effectBlocking(Files.size(path)).refineToOrDie[IOException] 130 | 131 | def isEmpty: ZIO[Blocking, IOException, Boolean] = size.map(_ == 0) 132 | def nonEmpty: ZIO[Blocking, IOException, Boolean] = size.map(_ > 0) 133 | 134 | def delete: ZIO[Blocking, IOException, Unit] = effectBlocking { 135 | Files.deleteIfExists(path) 136 | }.unit.refineToOrDie[IOException] 137 | 138 | // read 139 | 140 | def readBytes: ZIO[Blocking, IOException, Array[Byte]] = effectBlocking { 141 | Files.readAllBytes(path) 142 | }.refineToOrDie[IOException] 143 | 144 | def readString: ZIO[Blocking, IOException, String] = for { 145 | bytes <- readBytes 146 | } yield bytes.map(_.toChar).mkString 147 | 148 | def readLines: ZIO[Blocking, IOException, List[String]] = for { 149 | content <- readString 150 | } yield content.split("\n").toList 151 | 152 | // write 153 | 154 | def write(bytes: Array[Byte]): RIO[Blocking, Unit] = 155 | effectBlocking(Files.write(path, bytes)) 156 | 157 | def write(str: String): RIO[Blocking, Unit] = 158 | write(str.getBytes) 159 | 160 | def write(lines: Seq[String]): RIO[Blocking, Unit] = 161 | write(lines.mkString("\n").getBytes) 162 | 163 | // append 164 | 165 | def append(bytes: Array[Byte]): RIO[Blocking, Unit] = effectBlocking { 166 | Files.write(path, bytes, StandardOpenOption.APPEND) 167 | } 168 | 169 | def append(str: String): RIO[Blocking, Unit] = 170 | append(str.getBytes) 171 | 172 | def append(lines: Seq[String]): RIO[Blocking, Unit] = 173 | append(("\n" + lines.mkString("\n")).getBytes) 174 | 175 | // copy 176 | 177 | @deprecated("use copyTo", "2.0.2") 178 | def copy(target: ZFile): RIO[Blocking, Unit] = copyTo(target) 179 | 180 | @deprecated("use copyTo", "2.0.2") 181 | def copy(dest: ZDir): RIO[Blocking, Unit] = copyTo(dest.file(name)) 182 | 183 | def copyTo(target: ZFile): RIO[Blocking, Unit] = effectBlocking { 184 | Files.copy(path, target.path) 185 | } 186 | 187 | def copyTo(dest: ZDir): RIO[Blocking, Unit] = copyTo(dest.file(name)) 188 | 189 | // rename 190 | 191 | def rename(target: ZFile): RIO[Blocking, ZFile] = effectBlocking { 192 | Files.move(path, target.path) 193 | }.map(_ => target) 194 | 195 | def rename(fileName: String): RIO[Blocking, ZFile] = 196 | rename(parent.file(fileName)) 197 | 198 | def moveTo(dest: ZDir): RIO[Blocking, ZFile] = 199 | rename(dest.file(name)) 200 | 201 | // mime 202 | 203 | def mimeType: RIO[Blocking, Option[String]] = effectBlocking { 204 | Option(Files.probeContentType(path)) 205 | } 206 | 207 | // gzip 208 | 209 | def gzip: RIO[Blocking, ZFile] = 210 | gzip(parent.file(name + ".gz")) 211 | 212 | def gzip(out: ZFile): RIO[Blocking, ZFile] = 213 | ZStream 214 | .fromFile(path) 215 | .transduce(ZTransducer.gzip()) 216 | .run(ZSink.fromFile(path)) 217 | .map(_ => out) 218 | 219 | def ungzip: RIO[Blocking, ZFile] = 220 | ungzip(parent.file(name.substring(0, name.size - 3))) 221 | 222 | def ungzip(out: ZFile): RIO[Blocking, ZFile] = 223 | ZStream 224 | .fromFile(path) 225 | .transduce(ZTransducer.gunzip()) 226 | .run(ZSink.fromFile(path)) 227 | .map(_ => out) 228 | 229 | // zip 230 | 231 | def unzip: RIO[Blocking, ZDir] = unzip(parent) 232 | 233 | def unzip(dest: ZDir): RIO[Blocking, ZDir] = effectBlocking { 234 | Archive.unzip(path, dest.path) 235 | }.map(_ => dest) 236 | 237 | // untar 238 | 239 | def untar: RIO[Blocking, ZDir] = untar(parent) 240 | 241 | def untar(dest: ZDir): RIO[Blocking, ZDir] = effectBlocking { 242 | Archive.untar(path, dest.path, false) 243 | }.map(_ => dest) 244 | 245 | def untarGz: RIO[Blocking, ZDir] = untarGz(parent) 246 | 247 | def untarGz(dest: ZDir): RIO[Blocking, ZDir] = effectBlocking { 248 | Archive.untar(path, dest.path, true) 249 | }.map(_ => dest) 250 | 251 | // stream 252 | 253 | @deprecated("use copyTo", "2.0.2") 254 | def fillFrom(url: URL): RIO[Blocking, ZFile] = download(url.toString) 255 | 256 | def download(url: String): RIO[Blocking, ZFile] = 257 | download(url, Map.empty[String, String]) 258 | 259 | def download(url: String, headers: Map[String, String]): RIO[Blocking, ZFile] = for { 260 | javaUrl <- ZIO.effect(new URI(url).toURL()) 261 | javaHeaders <- ZIO.effect(headers.map(kv => new Header(kv._1, kv._2)).toArray) 262 | _ <- effectBlocking { 263 | UrlRequest.download(javaUrl, path, javaHeaders) 264 | } 265 | } yield this 266 | 267 | def upload(url: String): RIO[Blocking, String] = upload(url, Map.empty[String, String]) 268 | 269 | def upload(url: String, headers: Map[String, String]): RIO[Blocking, String] = for { 270 | javaUrl <- ZIO.effect(new URI(url).toURL()) 271 | ct <- mimeType.map(_.getOrElse("application/octet-stream")) 272 | cs <- size.map(_.toString) 273 | ah <- ZIO.succeed(headers ++ Map("Content-Type" -> ct, "Content-Size" -> cs)) 274 | javaHeaders <- ZIO.effect(ah.map(kv => new Header(kv._1, kv._2)).toArray) 275 | rsp <- effectBlocking { 276 | UrlRequest.upload(javaUrl, path, javaHeaders) 277 | } 278 | } yield rsp 279 | 280 | def asSink: ZSink[Blocking, Throwable, Byte, Byte, Long] = 281 | ZSink.fromFile(path) 282 | 283 | def asStringSink: ZSink[Blocking, Throwable, String, Byte, Long] = 284 | asSink.contramapChunks[String](_.flatMap(_.getBytes)) 285 | 286 | def streamBytes: ZStream[Blocking, Throwable, Byte] = 287 | ZStream.fromFile(path) 288 | 289 | def streamLines: ZStream[Blocking, Throwable, String] = 290 | streamBytes 291 | .transduce(ZTransducer.utf8Decode >>> ZTransducer.splitLines) 292 | } 293 | 294 | 295 | object ZDir { 296 | 297 | def fromPath(path: Path) = 298 | ZDir(path) 299 | 300 | def rel(parts: String*) = 301 | ZDir(Paths.get(ZPath.root, parts: _*)) 302 | 303 | def get(first: String, rest: String*) = 304 | ZDir(Paths.get(first, rest: _*)) 305 | 306 | def mkdirs(dirNames: Seq[String]): ZIO[Blocking, IOException, List[ZDir]] = 307 | effectBlocking { 308 | val zDirs = dirNames.map(d => get(d)).toList 309 | zDirs.foreach(d => Files.createDirectories(d.path)) 310 | zDirs 311 | }.refineToOrDie[IOException] 312 | } 313 | 314 | 315 | case class ZDir(path: Path) extends ZPath { 316 | 317 | private def pickFiles(paths: List[ZPath]): List[ZFile] = 318 | paths.filter(_.isFile).map(_.asInstanceOf[ZFile]) 319 | 320 | private def pickDirs(paths: List[ZPath]): List[ZDir] = 321 | paths.filter(_.isDir).map(_.asInstanceOf[ZDir]) 322 | 323 | private def listDir(p: Path): List[Path] = 324 | Files.list(p).iterator.asScala.toList 325 | 326 | private def walkDir(p: Path): List[Path] = 327 | Files.walk(p).iterator.asScala.toList 328 | 329 | private val toZPath: Path => ZPath = { p => 330 | if (Files.isDirectory(p)) ZDir(p) else ZFile(p) 331 | } 332 | 333 | def isFile = false 334 | def isDir = true 335 | 336 | def relTo(other: ZDir) = ZDir(other.path.relativize(path)) 337 | 338 | def add(other: ZFile) = ZFile(path.resolve(other.path)) 339 | def add(other: ZDir) = ZDir(path.resolve(other.path)) 340 | def add(other: ZPath): ZPath = other match { 341 | case p: ZFile => add(p) 342 | case p: ZDir => add(p) 343 | } 344 | 345 | def file(fileName: String) = add(ZFile.get(fileName)) 346 | def dir(dirName: String) = add(ZDir.get(dirName)) 347 | 348 | def assert: ZIO[Blocking, IOException, ZDir] = 349 | effectBlocking { 350 | if (!Files.exists(path)) 351 | throw new IOException(s"path does not exist: $path") 352 | if (!Files.isDirectory(path)) 353 | throw new IOException(s"path is not a file: $path") 354 | this 355 | }.refineToOrDie[IOException] 356 | 357 | 358 | def size: ZIO[Blocking, IOException, Long] = effectBlocking { 359 | walkDir(path).foldLeft(0L) { (acc, p) => acc + Files.size(p) } 360 | }.refineToOrDie[IOException] 361 | 362 | def isEmpty: ZIO[Blocking, IOException, Boolean] = list.map(_.isEmpty) 363 | def nonEmpty: ZIO[Blocking, IOException, Boolean] = list.map(_.nonEmpty) 364 | 365 | def create: RIO[Blocking, ZDir] = 366 | effectBlocking(Files.createDirectories(path)).map(_ => this) 367 | 368 | def mkdir(dirName: String): RIO[Blocking, ZDir] = 369 | dir(dirName).create 370 | 371 | def mkdirs(dirNames: Seq[String]): ZIO[Blocking, IOException, Seq[ZDir]] = (for { 372 | zDirs <- ZIO.effect(dirNames.map(dir).toList) 373 | _ <- effectBlocking(zDirs.map(d => Files.createDirectories(d.path))) 374 | } yield zDirs).refineToOrDie[IOException] 375 | 376 | def rename(dest: ZDir): RIO[Blocking, ZDir] = 377 | effectBlocking(Files.move(path, dest.path)).map(_ => dest) 378 | 379 | def rename(dirName: String): RIO[Blocking, ZDir] = 380 | rename(parent.dir(dirName)) 381 | 382 | def moveTo(dest: ZDir): RIO[Blocking, ZDir] = 383 | rename(dest.dir(name)) 384 | 385 | def moveHere(paths: Seq[ZPath]): RIO[Blocking, Seq[ZPath]] = effectBlocking { 386 | paths.foreach(p => Files.move(p.path, path.resolve(p.name))) 387 | paths.toList 388 | }.refineToOrDie[IOException] 389 | 390 | // copy 391 | 392 | @deprecated("use copyTo", "2.0.2") 393 | def copy(other: ZDir): RIO[Blocking, Unit] = copyTo(other) 394 | 395 | def copyTo(other: ZDir): RIO[Blocking, Unit] = effectBlocking { 396 | def loop(source: ZPath, dest: ZDir): Unit = { 397 | source match { 398 | case sourceDir: ZDir => 399 | val nextDest = dest.dir(sourceDir.name) 400 | Files.createDirectory(nextDest.path) 401 | listDir(sourceDir.path).map(toZPath).foreach { p => 402 | loop(sourceDir.add(p), nextDest) 403 | } 404 | case sourceFile: ZFile => 405 | Files.copy(sourceFile.path, dest.file(sourceFile.name).path) 406 | } 407 | } 408 | loop(this, other) 409 | } 410 | 411 | // delete 412 | 413 | private def deleteAny(p: Path): Unit = { 414 | if (Files.isDirectory(p)) { 415 | listDir(p).foreach(deleteAny) 416 | Files.deleteIfExists(p) 417 | } else { 418 | Files.deleteIfExists(p) 419 | } 420 | } 421 | 422 | def delete: ZIO[Blocking, IOException, Unit] = effectBlocking { 423 | if (Files.exists(path)) 424 | deleteAny(path) 425 | }.refineToOrDie[IOException] 426 | 427 | def empty: ZIO[Blocking, IOException, ZDir] = effectBlocking { 428 | listDir(path).foreach(deleteAny) 429 | }.map(_ => this).refineToOrDie[IOException] 430 | 431 | // zip 432 | 433 | def zip: RIO[Blocking, ZFile] = zip(parent.file(name + ".zip")) 434 | 435 | def zip(out: ZFile): RIO[Blocking, ZFile] = effectBlocking { 436 | Archive.zip(path, out.path) 437 | }.map(_ => out) 438 | 439 | // tar 440 | 441 | def tar: RIO[Blocking, ZFile] = tar(parent) 442 | 443 | def tar(dest: ZDir): RIO[Blocking, ZFile] = for { 444 | tarFile <- ZIO.succeed(dest.file(name + ".tar")) 445 | _ <- effectBlocking(Archive.tar(path, tarFile.path, false)) 446 | } yield tarFile 447 | 448 | def tarGz: RIO[Blocking, ZFile] = tarGz(parent) 449 | 450 | def tarGz(dest: ZDir): RIO[Blocking, ZFile] = for { 451 | tarGzFile <- ZIO.succeed(dest.file(name + ".tar.gz")) 452 | _ <- effectBlocking(Archive.tar(path, tarGzFile.path, true)) 453 | } yield tarGzFile 454 | 455 | // list 456 | 457 | def list: ZIO[Blocking, IOException, List[ZPath]] = effectBlocking { 458 | listDir(path).map(toZPath) 459 | }.refineToOrDie[IOException] 460 | 461 | def listFiles: ZIO[Blocking, IOException, List[ZFile]] = 462 | list.map(pickFiles(_)) 463 | 464 | def listDirs: ZIO[Blocking, IOException, List[ZDir]] = 465 | list.map(pickDirs(_)) 466 | 467 | // walk 468 | 469 | def walk: ZIO[Blocking, IOException, List[ZPath]] = effectBlocking { 470 | walkDir(path).map(toZPath) 471 | }.refineToOrDie[IOException] 472 | 473 | def walkFiles: ZIO[Blocking, IOException, List[ZFile]] = 474 | walk.map(pickFiles(_)) 475 | 476 | def walkDirs: ZIO[Blocking, IOException, List[ZDir]] = 477 | walk.map(pickDirs(_)) 478 | 479 | def streamWalk: ZStream[Blocking, Throwable, ZPath] = 480 | ZStream.unfoldChunkM(new WalkIter(path))(_.next) 481 | 482 | def streamWalkFiles: ZStream[Blocking, Throwable, ZFile] = 483 | streamWalk.filter(_.isFile).map(_.asInstanceOf[ZFile]) 484 | 485 | def streamWalkDirs: ZStream[Blocking, Throwable, ZDir] = 486 | streamWalk.filter(_.isDir).map(_.asInstanceOf[ZDir]) 487 | 488 | private class WalkIter(path: Path) { 489 | 490 | var iterator: Option[Iterator[Path]] = None 491 | 492 | def iter = iterator match { 493 | case Some(iter) => ZIO.succeed(iter) 494 | case None => for { 495 | iter <- effectBlocking(Files.walk(path).iterator.asScala) 496 | _ <- ZIO.succeed { iterator = Some(iter) } 497 | } yield iter 498 | } 499 | 500 | def take(iter: Iterator[Path]) = effectBlocking { 501 | iter.take(100).toList.map(toZPath) 502 | } 503 | 504 | val toChunk: List[ZPath] => Option[(Chunk[ZPath], WalkIter)] = { 505 | case Nil => None 506 | case batch => Some((Chunk.fromIterable(batch), this)) 507 | } 508 | 509 | def next = iter.flatMap(take).map(toChunk) 510 | } 511 | } 512 | 513 | 514 | 515 | 516 | 517 | 518 | 519 | 520 | 521 | 522 | 523 | 524 | 525 | 526 | 527 | 528 | 529 | 530 | 531 | -------------------------------------------------------------------------------- /zio2/zio-path/src/main/scala/ZPath.scala: -------------------------------------------------------------------------------- 1 | package io.github.karimagnusson.zio.path 2 | 3 | import java.net.{URL, URI, HttpURLConnection} 4 | import java.io.IOException 5 | import java.io.InputStream 6 | import java.nio.file.attribute.FileTime 7 | import java.nio.file.{ 8 | Files, 9 | Paths, 10 | Path, 11 | StandardOpenOption 12 | } 13 | 14 | import scala.io.{Source, Codec} 15 | import scala.jdk.CollectionConverters._ 16 | import zio._ 17 | import zio.stream.{ZStream, ZPipeline, ZSink} 18 | import io.github.karimagnusson.zio.path.utils._ 19 | 20 | 21 | case class ZPathInfo( 22 | path: Path, 23 | isDir: Boolean, 24 | isHidden: Boolean, 25 | isReadable: Boolean, 26 | isWritable: Boolean, 27 | isSymbolicLink: Boolean, 28 | lastModified: FileTime 29 | ) 30 | 31 | 32 | object ZPath { 33 | 34 | lazy val root = Paths.get("").toAbsolutePath.toString 35 | 36 | private def toZPath(path: Path): Task[ZPath] = ZIO.attemptBlocking { 37 | if (Files.isDirectory(path)) ZDir(path) else ZFile(path) 38 | } 39 | 40 | def fromPath(path: Path): Task[ZPath] = 41 | toZPath(path) 42 | 43 | def rel(parts: String*): Task[ZPath] = 44 | toZPath(Paths.get(root, parts: _*)) 45 | 46 | def get(first: String, rest: String*): Task[ZPath] = 47 | toZPath(Paths.get(first, rest: _*)) 48 | 49 | // url stream 50 | 51 | private def readInput(is: InputStream): String = 52 | Source.fromInputStream(is)(Codec.UTF8).mkString 53 | 54 | def urlStream(url: String): ZStream[Any, IOException, Byte] = 55 | urlStream(url, Map.empty) 56 | 57 | def urlStream( 58 | url: String, 59 | headers: Map[String, String] 60 | ): ZStream[Any, IOException, Byte] = ZStream.fromInputStreamZIO( 61 | ZIO.attemptBlocking { 62 | val javaUrl = new URI(url).toURL() 63 | val conn = javaUrl.openConnection().asInstanceOf[HttpURLConnection] 64 | conn.setRequestMethod("GET") 65 | headers.foreach(h => conn.setRequestProperty(h._1, h._2)) 66 | if (conn.getResponseCode() != HttpURLConnection.HTTP_OK) { 67 | throw new IOException(readInput(conn.getInputStream())) 68 | } 69 | conn.getInputStream() 70 | }.refineToOrDie[IOException] 71 | ) 72 | 73 | def urlSink(url: String): ZSink[Any, IOException, Byte, Byte, String] = 74 | urlSink(url, Map.empty) 75 | 76 | def urlSink( 77 | url: String, 78 | headers: Map[String, String] 79 | ): ZSink[Any, IOException, Byte, Byte, String] = ZSink.unwrap { 80 | ZIO.attemptBlocking { 81 | val javaUrl = new URI(url).toURL() 82 | val conn = javaUrl.openConnection().asInstanceOf[HttpURLConnection] 83 | conn.setDoOutput(true) 84 | conn.setRequestMethod("POST") 85 | headers.foreach(h => conn.setRequestProperty(h._1, h._2)) 86 | ZSink 87 | .fromOutputStream(conn.getOutputStream()) 88 | .mapZIO { _ => 89 | ZIO.attemptBlocking( 90 | readInput(conn.getInputStream()) 91 | ).refineToOrDie[IOException] 92 | } 93 | .orElse { 94 | ZSink.fail(new IOException( 95 | readInput(conn.getInputStream()) 96 | )) 97 | } 98 | }.catchSome { 99 | case ex: IOException => 100 | ZIO.succeed(ZSink.fail(ex)) 101 | }.refineToOrDie[IOException] 102 | } 103 | } 104 | 105 | 106 | sealed trait ZPath { 107 | 108 | val path: Path 109 | def name = path.getFileName.toString 110 | def isFile: Boolean 111 | def isDir: Boolean 112 | def show = path.toString 113 | def startsWithDot = name.head == '.' 114 | def parent = ZDir(path.getParent) 115 | 116 | def delete: IO[IOException, Unit] 117 | def copy(dest: ZDir): Task[Unit] 118 | def copyTo(dest: ZDir): Task[Unit] 119 | def size: IO[IOException, Long] 120 | def isEmpty: IO[IOException, Boolean] 121 | def nonEmpty: IO[IOException, Boolean] 122 | 123 | def exists: UIO[Boolean] = ZIO.attemptBlocking { 124 | Files.exists(path) 125 | }.orDie 126 | 127 | def info: IO[IOException, ZPathInfo] = ZIO.attemptBlocking { 128 | ZPathInfo( 129 | path, 130 | Files.isDirectory(path), 131 | Files.isHidden(path), 132 | Files.isReadable(path), 133 | Files.isWritable(path), 134 | Files.isSymbolicLink(path), 135 | Files.getLastModifiedTime(path) 136 | ) 137 | }.refineToOrDie[IOException] 138 | 139 | override def toString = path.toString 140 | } 141 | 142 | 143 | object ZFile { 144 | 145 | def fromPath(path: Path) = 146 | ZFile(path) 147 | 148 | def rel(parts: String*) = 149 | ZFile(Paths.get(ZPath.root, parts: _*)) 150 | 151 | def get(first: String, rest: String*) = 152 | ZFile(Paths.get(first, rest: _*)) 153 | 154 | def deleteFiles(files: Seq[ZFile]): IO[IOException, Unit] = 155 | ZIO.attemptBlocking { 156 | files.foreach(f => Files.deleteIfExists(f.path)) 157 | }.unit.refineToOrDie[IOException] 158 | } 159 | 160 | 161 | case class ZFile(path: Path) extends ZPath { 162 | 163 | def isFile = true 164 | def isDir = false 165 | 166 | def ext = name.split('.').lastOption 167 | def extUpper = ext.map(_.toUpperCase).getOrElse("") 168 | def extLower = ext.map(_.toLowerCase).getOrElse("") 169 | 170 | def relTo(dir: ZDir) = ZFile(dir.path.relativize(path)) 171 | 172 | def assert: IO[IOException, ZFile] = ZIO.attemptBlocking { 173 | if (!Files.exists(path)) 174 | throw new IOException(s"path does not exist: $path") 175 | if (!Files.isRegularFile(path)) 176 | throw new IOException(s"path is not a file: $path") 177 | this 178 | }.refineToOrDie[IOException] 179 | 180 | def create: Task[ZFile] = 181 | ZIO.attemptBlocking(Files.createFile(path)).map(_ => this) 182 | 183 | def size: IO[IOException, Long] = 184 | ZIO.attemptBlocking(Files.size(path)).refineToOrDie[IOException] 185 | 186 | def isEmpty: IO[IOException, Boolean] = size.map(_ == 0) 187 | def nonEmpty: IO[IOException, Boolean] = size.map(_ > 0) 188 | 189 | def delete: IO[IOException, Unit] = ZIO.attemptBlocking { 190 | Files.deleteIfExists(path) 191 | }.unit.refineToOrDie[IOException] 192 | 193 | // read 194 | 195 | def readBytes: IO[IOException, Array[Byte]] = ZIO.attemptBlocking { 196 | Files.readAllBytes(path) 197 | }.refineToOrDie[IOException] 198 | 199 | def readString: IO[IOException, String] = for { 200 | bytes <- readBytes 201 | } yield bytes.map(_.toChar).mkString 202 | 203 | def readLines: IO[IOException, List[String]] = for { 204 | content <- readString 205 | } yield content.split("\n").toList 206 | 207 | // write 208 | 209 | def write(bytes: Array[Byte]): Task[Unit] = 210 | ZIO.attemptBlocking(Files.write(path, bytes)) 211 | 212 | def write(str: String): Task[Unit] = 213 | write(str.getBytes) 214 | 215 | def write(lines: Seq[String]): Task[Unit] = 216 | write(lines.mkString("\n").getBytes) 217 | 218 | // append 219 | 220 | def append(bytes: Array[Byte]): Task[Unit] = ZIO.attemptBlocking { 221 | Files.write(path, bytes, StandardOpenOption.APPEND) 222 | } 223 | 224 | def append(str: String): Task[Unit] = 225 | append(str.getBytes) 226 | 227 | def append(lines: Seq[String]): Task[Unit] = 228 | append(("\n" + lines.mkString("\n")).getBytes) 229 | 230 | // copy 231 | 232 | @deprecated("use copyTo", "2.0.2") 233 | def copy(target: ZFile): Task[Unit] = copyTo(target) 234 | 235 | @deprecated("use copyTo", "2.0.2") 236 | def copy(dest: ZDir): Task[Unit] = copyTo(dest.file(name)) 237 | 238 | def copyTo(target: ZFile): Task[Unit] = ZIO.attemptBlocking { 239 | Files.copy(path, target.path) 240 | }.unit 241 | 242 | def copyTo(dest: ZDir): Task[Unit] = copyTo(dest.file(name)) 243 | 244 | // rename 245 | 246 | def rename(target: ZFile): Task[ZFile] = ZIO.attemptBlocking { 247 | Files.move(path, target.path) 248 | }.map(_ => target) 249 | 250 | def rename(fileName: String): Task[ZFile] = 251 | rename(parent.file(fileName)) 252 | 253 | def moveTo(dest: ZDir): Task[ZFile] = 254 | rename(dest.file(name)) 255 | 256 | // mime 257 | 258 | def mimeType: IO[IOException, Option[String]] = 259 | ZIO.attemptBlocking( 260 | Option(Files.probeContentType(path)) 261 | ).refineToOrDie[IOException] 262 | 263 | // gzip 264 | 265 | def gzip: Task[ZFile] = 266 | gzip(parent.file(name + ".gz")) 267 | 268 | def gzip(out: ZFile): Task[ZFile] = 269 | ZStream 270 | .fromPath(path) 271 | .via(ZPipeline.gzip()) 272 | .run(ZSink.fromPath(out.path)) 273 | .map(_ => out) 274 | 275 | def ungzip: Task[ZFile] = 276 | ungzip(parent.file(name.substring(0, name.size - 3))) 277 | 278 | def ungzip(out: ZFile): Task[ZFile] = 279 | ZStream 280 | .fromPath(path) 281 | .via(ZPipeline.gunzip()) 282 | .run(ZSink.fromPath(out.path)) 283 | .map(_ => out) 284 | 285 | // zip 286 | 287 | def unzip: Task[ZDir] = unzip(parent) 288 | 289 | def unzip(dest: ZDir): Task[ZDir] = ZIO.attemptBlocking { 290 | Archive.unzip(path, dest.path) 291 | }.map(_ => dest) 292 | 293 | // untar 294 | 295 | def untar: Task[ZDir] = untar(parent) 296 | 297 | def untar(dest: ZDir): Task[ZDir] = ZIO.attemptBlocking { 298 | Archive.untar(path, dest.path, false) 299 | }.map(_ => dest) 300 | 301 | def untarGz: Task[ZDir] = untarGz(parent) 302 | 303 | def untarGz(dest: ZDir): Task[ZDir] = ZIO.attemptBlocking { 304 | Archive.untar(path, dest.path, true) 305 | }.map(_ => dest) 306 | 307 | // stream 308 | 309 | @deprecated("use copyTo", "2.0.2") 310 | def fillFrom(url: URL): Task[Long] = download(url.toString).map(_ => 0L) 311 | 312 | def download(url: String): IO[IOException, ZFile] = 313 | download(url, Map.empty[String, String]) 314 | 315 | def download(url: String, headers: Map[String, String]): IO[IOException, ZFile] = 316 | ZPath.urlStream(url, headers).run(asSink).map(_ => this).refineToOrDie[IOException] 317 | 318 | def upload(url: String): IO[IOException, String] = upload(url, Map.empty) 319 | 320 | def upload(url: String, headers: Map[String, String]): IO[IOException, String] = for { 321 | ct <- mimeType.map(_.getOrElse("application/octet-stream")) 322 | cs <- size.map(_.toString) 323 | ah <- ZIO.succeed(headers ++ Map("Content-Type" -> ct, "Content-Size" -> cs)) 324 | res <- streamBytes.run(ZPath.urlSink(url, ah)) 325 | } yield res 326 | 327 | def asSink: ZSink[Any, Throwable, Byte, Byte, Long] = 328 | ZSink.fromPath(path) 329 | 330 | def asStringSink: ZSink[Any, Throwable, String, Byte, Long] = 331 | asSink.contramapChunks[String](_.flatMap(_.getBytes)) 332 | 333 | def streamBytes: ZStream[Any, IOException, Byte] = 334 | ZStream.fromPath(path).refineToOrDie[IOException] 335 | 336 | def streamLines: ZStream[Any, IOException, String] = 337 | streamBytes 338 | .via(ZPipeline.utf8Decode) 339 | .via(ZPipeline.splitLines) 340 | } 341 | 342 | 343 | object ZDir { 344 | 345 | def fromPath(path: Path) = 346 | ZDir(path) 347 | 348 | def rel(parts: String*) = 349 | ZDir(Paths.get(ZPath.root, parts: _*)) 350 | 351 | def get(first: String, rest: String*) = 352 | ZDir(Paths.get(first, rest: _*)) 353 | 354 | def mkdirs(dirNames: Seq[String]): IO[IOException, List[ZDir]] = 355 | ZIO.attemptBlocking { 356 | val zDirs = dirNames.map(d => get(d)).toList 357 | zDirs.foreach(d => Files.createDirectories(d.path)) 358 | zDirs 359 | }.refineToOrDie[IOException] 360 | } 361 | 362 | 363 | case class ZDir(path: Path) extends ZPath { 364 | 365 | private def pickFiles(paths: List[ZPath]): List[ZFile] = 366 | paths.filter(_.isFile).map(_.asInstanceOf[ZFile]) 367 | 368 | private def pickDirs(paths: List[ZPath]): List[ZDir] = 369 | paths.filter(_.isDir).map(_.asInstanceOf[ZDir]) 370 | 371 | private def listDir(p: Path): List[Path] = 372 | Files.list(p).iterator.asScala.toList 373 | 374 | private def walkDir(p: Path): List[Path] = 375 | Files.walk(p).iterator.asScala.toList 376 | 377 | private val toZPath: Path => ZPath = { p => 378 | if (Files.isDirectory(p)) ZDir(p) else ZFile(p) 379 | } 380 | 381 | def isFile = false 382 | def isDir = true 383 | 384 | def relTo(other: ZDir) = ZDir(other.path.relativize(path)) 385 | 386 | def add(other: ZFile) = ZFile(path.resolve(other.path)) 387 | def add(other: ZDir) = ZDir(path.resolve(other.path)) 388 | def add(other: ZPath): ZPath = other match { 389 | case p: ZFile => add(p) 390 | case p: ZDir => add(p) 391 | } 392 | 393 | def file(fileName: String) = add(ZFile.get(fileName)) 394 | def dir(dirName: String) = add(ZDir.get(dirName)) 395 | 396 | def assert: IO[IOException, ZDir] = 397 | ZIO.attemptBlocking { 398 | if (!Files.exists(path)) 399 | throw new IOException(s"path does not exist: $path") 400 | if (!Files.isDirectory(path)) 401 | throw new IOException(s"path is not a file: $path") 402 | this 403 | }.refineToOrDie[IOException] 404 | 405 | def size: IO[IOException, Long] = ZIO.attemptBlocking { 406 | walkDir(path).foldLeft(0L) { (acc, p) => acc + Files.size(p) } 407 | }.refineToOrDie[IOException] 408 | 409 | def isEmpty: IO[IOException, Boolean] = list.map(_.isEmpty) 410 | def nonEmpty: IO[IOException, Boolean] = list.map(_.nonEmpty) 411 | 412 | def create: Task[ZDir] = 413 | ZIO.attemptBlocking(Files.createDirectories(path)).map(_ => this) 414 | 415 | def mkdir(dirName: String): Task[ZDir] = 416 | dir(dirName).create 417 | 418 | def mkdirs(dirNames: Seq[String]): IO[IOException, List[ZDir]] = (for { 419 | zDirs <- ZIO.attempt(dirNames.map(dir).toList) 420 | _ <- ZIO.attemptBlocking(zDirs.map(d => Files.createDirectories(d.path))) 421 | } yield zDirs).refineToOrDie[IOException] 422 | 423 | def rename(dest: ZDir): Task[ZDir] = 424 | ZIO.attemptBlocking(Files.move(path, dest.path)).map(_ => dest) 425 | 426 | def rename(dirName: String): Task[ZDir] = rename(parent.dir(dirName)) 427 | 428 | def moveTo(dest: ZDir): Task[ZDir] = rename(dest.dir(name)) 429 | 430 | def moveHere(paths: Seq[ZPath]): Task[Seq[ZPath]] = ZIO.attemptBlocking { 431 | paths.foreach(p => Files.move(p.path, path.resolve(p.name))) 432 | paths.toList 433 | }.refineToOrDie[IOException] 434 | 435 | // copy 436 | 437 | @deprecated("use copyTo", "2.0.2") 438 | def copy(other: ZDir): Task[Unit] = copyTo(other) 439 | 440 | def copyTo(other: ZDir): Task[Unit] = ZIO.attemptBlocking { 441 | def loop(source: ZPath, dest: ZDir): Unit = { 442 | source match { 443 | case sourceDir: ZDir => 444 | val nextDest = dest.dir(sourceDir.name) 445 | Files.createDirectory(nextDest.path) 446 | listDir(sourceDir.path).map(toZPath).foreach { p => 447 | loop(sourceDir.add(p), nextDest) 448 | } 449 | case sourceFile: ZFile => 450 | Files.copy(sourceFile.path, dest.file(sourceFile.name).path) 451 | } 452 | } 453 | loop(this, other) 454 | } 455 | 456 | // delete 457 | 458 | private def deleteAny(p: Path): Unit = { 459 | if (Files.isDirectory(p)) { 460 | listDir(p).foreach(deleteAny) 461 | Files.deleteIfExists(p) 462 | } else { 463 | Files.deleteIfExists(p) 464 | } 465 | } 466 | 467 | def delete: IO[IOException, Unit] = ZIO.attemptBlocking { 468 | if (Files.exists(path)) 469 | deleteAny(path) 470 | }.refineToOrDie[IOException] 471 | 472 | def empty: IO[IOException, ZDir] = ZIO.attemptBlocking { 473 | listDir(path).foreach(deleteAny) 474 | }.map(_ => this).refineToOrDie[IOException] 475 | 476 | // zip 477 | 478 | def zip: Task[ZFile] = zip(parent.file(name + ".zip")) 479 | 480 | def zip(out: ZFile): Task[ZFile] = ZIO.attemptBlocking { 481 | Archive.zip(path, out.path) 482 | }.map(_ => out) 483 | 484 | // tar 485 | 486 | def tar: Task[ZFile] = tar(parent) 487 | 488 | def tar(dest: ZDir): Task[ZFile] = for { 489 | tarFile <- ZIO.attempt(dest.file(name + ".tar")) 490 | _ <- ZIO.attemptBlocking(Archive.tar(path, tarFile.path, false)) 491 | } yield tarFile 492 | 493 | def tarGz: Task[ZFile] = tarGz(parent) 494 | 495 | def tarGz(dest: ZDir): Task[ZFile] = for { 496 | tarGzFile <- ZIO.attempt(dest.file(name + ".tar.gz")) 497 | _ <- ZIO.attemptBlocking(Archive.tar(path, tarGzFile.path, true)) 498 | } yield tarGzFile 499 | 500 | // list 501 | 502 | def list: IO[IOException, List[ZPath]] = ZIO.attemptBlocking { 503 | listDir(path).map(toZPath) 504 | }.refineToOrDie[IOException] 505 | 506 | def listFiles: IO[IOException, List[ZFile]] = 507 | list.map(pickFiles(_)) 508 | 509 | def listDirs: IO[IOException, List[ZDir]] = 510 | list.map(pickDirs(_)) 511 | 512 | // walk 513 | 514 | def walk: IO[IOException, List[ZPath]] = ZIO.attemptBlocking { 515 | walkDir(path).map(toZPath) 516 | }.refineToOrDie[IOException] 517 | 518 | def walkFiles: IO[IOException, List[ZFile]] = 519 | walk.map(pickFiles(_)) 520 | 521 | def walkDirs: IO[IOException, List[ZDir]] = 522 | walk.map(pickDirs(_)) 523 | 524 | def streamWalk: ZStream[Any, Throwable, ZPath] = 525 | ZStream.unfoldChunkZIO(new WalkIter(path))(_.next) 526 | 527 | def streamWalkFiles: ZStream[Any, Throwable, ZFile] = 528 | streamWalk.filter(_.isFile).map(_.asInstanceOf[ZFile]) 529 | 530 | def streamWalkDirs: ZStream[Any, Throwable, ZDir] = 531 | streamWalk.filter(_.isDir).map(_.asInstanceOf[ZDir]) 532 | 533 | private class WalkIter(path: Path) { 534 | 535 | var iterator: Option[Iterator[Path]] = None 536 | 537 | def iter = iterator match { 538 | case Some(iter) => ZIO.succeed(iter) 539 | case None => for { 540 | iter <- ZIO.attemptBlocking(Files.walk(path).iterator.asScala) 541 | _ <- ZIO.succeed { iterator = Some(iter) } 542 | } yield iter 543 | } 544 | 545 | def take(iter: Iterator[Path]) = ZIO.attemptBlocking { 546 | iter.take(100).toList.map(toZPath) 547 | } 548 | 549 | val toChunk: List[ZPath] => Option[(Chunk[ZPath], WalkIter)] = { 550 | case Nil => None 551 | case batch => Some((Chunk.fromIterable(batch), this)) 552 | } 553 | 554 | def next = iter.flatMap(take).map(toChunk) 555 | } 556 | } 557 | 558 | 559 | 560 | 561 | 562 | 563 | 564 | 565 | 566 | 567 | 568 | 569 | 570 | 571 | 572 | 573 | 574 | 575 | --------------------------------------------------------------------------------