Skip to content

Commit 16bad73

Browse files
committed
Change config path
Closes #116.
1 parent 54e9afd commit 16bad73

File tree

3 files changed

+41
-10
lines changed

3 files changed

+41
-10
lines changed

README.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ This will create a launcher at `./target/universal/stage/bin/spotify-next`.
3131
## Usage
3232

3333
The application requires some configuration (e.g. the client ID for the Spotify Web API).
34-
It's stored in a file at `~/.spotify-next.json`.
34+
It's stored in a file at `$XDG_CONFIG_HOME/spotify-next/config.json` or `$HOME/.config/spotify-next/config.json` (whichever works first).
3535
When you first run the application, or if that file is deleted, the application will ask and attempt to create one.
3636

3737
The configuration defines the port for the embedded HTTP server used for authentication. The server will only start when the login flow is triggered, and stop afterwards.

src/main/scala/com/kubukoz/next/ConfigLoader.scala

+4-3
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import cats.effect.std.Console
1414
import fs2.io.file.Files
1515
import cats.FlatMap
1616
import cats.MonadThrow
17+
import fs2.Pipe
1718

1819
trait ConfigLoader[F[_]] {
1920
def saveConfig(config: Config): F[Unit]
@@ -58,9 +59,9 @@ object ConfigLoader {
5859
def default[F[_]: Files: MonadThrow](configPath: Path)(using fs2.Compiler[F, F]): ConfigLoader[F] =
5960
new ConfigLoader[F] {
6061

61-
private val createOrOverwriteFile =
62-
Files[F]
63-
.writeAll(configPath, List(StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING))
62+
private val createOrOverwriteFile: Pipe[F, Byte, Nothing] = bytes =>
63+
fs2.Stream.exec(Files[F].createDirectories(configPath.getParent).void) ++
64+
bytes.through(Files[F].writeAll(configPath, List(StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING)))
6465

6566
def saveConfig(config: Config): F[Unit] =
6667
fs2

src/main/scala/com/kubukoz/next/Program.scala

+36-6
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package com.kubukoz.next
22

33
import cats.effect.Concurrent
4+
import cats.effect.Sync
45
import cats.effect.MonadCancelThrow
56
import cats.effect.Resource
67
import cats.effect.kernel.Async
@@ -20,15 +21,44 @@ import org.http4s.client.middleware.RequestLogger
2021
import org.http4s.client.middleware.ResponseLogger
2122
import java.lang.System
2223
import java.nio.file.Paths
24+
import java.nio.file.Path
25+
import cats.data.OptionT
26+
import cats.MonadThrow
2327

2428
object Program {
25-
val configPath = Paths.get(System.getProperty("user.home")).resolve(".spotify-next.json")
2629

27-
def makeLoader[F[_]: Files: Ref.Make: UserOutput: Console: fs2.Compiler.Target]: F[ConfigLoader[F]] =
28-
ConfigLoader
29-
.cached[F]
30-
.compose(ConfigLoader.withCreateFileIfMissing[F](configPath))
31-
.apply(ConfigLoader.default[F](configPath))
30+
trait System[F[_]] {
31+
def getenv(name: String): F[Option[String]]
32+
}
33+
34+
object System {
35+
def apply[F[_]](using F: System[F]): System[F] = F
36+
37+
given [F[_]: Sync]: System[F] = name => Sync[F].delay(java.lang.System.getenv(name)).map(Option(_))
38+
}
39+
40+
private def configPath[F[_]: System: MonadThrow]: F[Path] =
41+
OptionT(System[F].getenv("XDG_CONFIG_HOME"))
42+
.map(Paths.get(_))
43+
.getOrElseF(
44+
System[F]
45+
.getenv("HOME")
46+
.flatMap(_.liftTo[F](new Throwable("HOME not defined, I don't even")))
47+
.map(Paths.get(_))
48+
.map(_.resolve(".config"))
49+
)
50+
.map(
51+
_.resolve("spotify-next")
52+
.resolve("config.json")
53+
)
54+
55+
def makeLoader[F[_]: Files: System: Ref.Make: UserOutput: Console: fs2.Compiler.Target]: F[ConfigLoader[F]] =
56+
configPath[F].flatMap { p =>
57+
ConfigLoader
58+
.cached[F]
59+
.compose(ConfigLoader.withCreateFileIfMissing[F](p))
60+
.apply(ConfigLoader.default[F](p))
61+
}
3262

3363
def makeBasicClient[F[_]: Async]: Resource[F, Client[F]] =
3464
Resource

0 commit comments

Comments
 (0)