Skip to content
This repository has been archived by the owner on Nov 10, 2024. It is now read-only.

Commit

Permalink
Merge pull request #77 from ckipp01/backup
Browse files Browse the repository at this point in the history
feat: add a backup command
  • Loading branch information
ckipp01 authored Dec 7, 2023
2 parents a51331c + b0e8244 commit 13623c0
Show file tree
Hide file tree
Showing 7 changed files with 116 additions and 20 deletions.
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -120,3 +120,9 @@ If you ever want to see what the values of your config are or can't find your
config, you can use `i` while inside of your main board view.

The other option for `boardOrder` is `date`.

### Backups

When in the main board view `b` will trigger a backup, which will zip up all
your contexts (which are just json files) and store them under `backup` in your
datadir.
1 change: 1 addition & 0 deletions skan/src/Config.scala
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ object Config:
private lazy val defaultDataDir = os.Path(dataDir) / "contexts"
val configFile = os.Path(configDir) / "config.json"
val archiveDir = os.Path(dataDir) / "archive"
val backupDir = os.Path(dataDir) / "backup"

private def fromJson(json: String) =
upickle.default.read[Config](json)
Expand Down
56 changes: 55 additions & 1 deletion skan/src/ContextState.scala
Original file line number Diff line number Diff line change
@@ -1,6 +1,19 @@
package skan

import os.Path
import os.Source
import skan.ui.Message

import java.io.File
import java.io.FileInputStream
import java.io.FileOutputStream
import java.time.Instant
import java.util.zip.ZipEntry
import java.util.zip.ZipOutputStream
import scala.util.Using
import scala.util.Failure
import scala.util.Success
import tui.Color

/** The top level state of the application containing all of the various
* contexts. For most operations that have to do with the selected board this
Expand All @@ -14,7 +27,8 @@ import java.time.Instant
*/
case class ContextState(
boards: Map[String, BoardState],
activeContext: String
activeContext: String,
message: Option[Message] = None
):
/** All the board names in a sorted fashion.
*/
Expand All @@ -30,6 +44,46 @@ case class ContextState(
val json = ContextState.toJson(board.items)
os.write.over(config.dataDir / s"${name}.json", json)

def backup(config: Config) =
save(config)
os.makeDir.all(Config.backupDir)
val ouputPath = Config.backupDir / s"${Instant.now()}-backup.zip"
val output = new File(ouputPath.toString)

val result = Using.Manager { use =>
val fos = use(new FileOutputStream(output))
val zos = use(new ZipOutputStream(fos))
val files = os.walk(config.dataDir)
files.foreach { file =>
val entry = new ZipEntry(file.last)
zos.putNextEntry(entry)
val fis = use(new FileInputStream(file.toIO))
val buffer = new Array[Byte](1024)
var length = fis.read(buffer)
while length > 0 do
zos.write(buffer, 0, length)
length = fis.read(buffer)
zos.closeEntry()
}
}

result match
case Failure(exception) =>
this.copy(message =
Some(
Message(
s"Unable to perform backup -- ${exception.getMessage()}",
Color.Red
)
)
)
case Success(value) =>
this.copy(message =
Some(Message("Successfully backed up!", Color.Blue))
)

end backup

/** Select the previous item in the current active board.
*/
def previous(): Unit =
Expand Down
12 changes: 12 additions & 0 deletions skan/src/Message.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package skan.ui

/** A message to be shown to the user. This will typically be done in the head
* and is meant to update the user about something that either went well or
* didn't.
*
* @param message
* The message to show to the user.
* @param borderColor
* The color the message should be displayed with.
*/
final case class Message(message: String, borderColor: tui.Color)
18 changes: 15 additions & 3 deletions skan/src/project.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
//> using jvm 19
//> using dep com.olvind.tui::tui:0.0.7
//> using dep com.lihaoyi::upickle:3.1.3
//> using dep com.lihaoyi::os-lib:0.9.1
//> using dep com.lihaoyi::os-lib:0.9.2
//> using dep dev.dirs:directories:26
//> using test.dep org.scalameta::munit::0.7.29
//> using options -deprecation -feature -explain -Wunused:all
Expand All @@ -22,8 +22,18 @@ import skan.ui.*
val config = Config.load()
val contextState = ContextState.fromConfig(config)

def runBoard(state: ContextState): Unit =
terminal.draw(frame => Board.render(frame, state, config))
def runBoard(_state: ContextState): Unit =
terminal.draw(frame => Board.render(frame, _state, config))

/** After draw we reset the message since we've already shown it once. We
* could be fancy and try to remove it after a certain amount of time if
* there is no action, but honestly most people will do something that will
* cause runBoard to happen again anyways.
*/
val state = _state.message match
case None => _state
case Some(value) => _state.copy(message = None)

jni.read() match
case key: Event.Key =>
key.keyEvent().code() match
Expand All @@ -46,6 +56,8 @@ import skan.ui.*
runContextMenu(state, ListWidget.State(selected = Some(0)))
case char: KeyCode.Char if char.c() == 'i' =>
runInfo(state)
case char: KeyCode.Char if char.c() == 'b' =>
runBoard(state.backup(config))
case _: KeyCode.Enter =>
state.progress()
runBoard(state)
Expand Down
7 changes: 3 additions & 4 deletions skan/src/ui/Board.scala
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
package skan.ui

import skan.*

import tui.*
import tui.widgets.tabs.TabsWidget
import tui.widgets.BlockWidget
import tui.widgets.ListWidget
import tui.widgets.ParagraphWidget
import tui.widgets.ParagraphWidget.Wrap
import tui.widgets.tabs.TabsWidget

object Board:
def render(
Expand All @@ -20,7 +19,7 @@ object Board:
val verticalChunk = Layout(
direction = Direction.Vertical,
constraints = Array(
Constraint.Length(2),
Constraint.Length(2 + contextState.message.map(_ => 1).getOrElse(0)),
Constraint.Length(3),
Constraint.Percentage(75),
Constraint.Length(2)
Expand All @@ -36,7 +35,7 @@ object Board:
)
).split(verticalChunk(2))

Header.render(frame, verticalChunk(0))
Header.render(frame, verticalChunk(0), contextState.message)

val contexts = contextState.sortedKeys.map: context =>
Spans(Array(Span.nostyle(context)))
Expand Down
36 changes: 24 additions & 12 deletions skan/src/ui/Header.scala
Original file line number Diff line number Diff line change
Expand Up @@ -12,21 +12,33 @@ object Header:
* @param rect
* The rect you want it rendered in.
*/
def render(frame: Frame, rect: Rect) =
def render(frame: Frame, rect: Rect, message: Option[Message] = None) =
val defaults = Vector(
Spans.styled(
s"skan",
Style(addModifier = Modifier.BOLD)
),
Spans.styled(
s"v${BuildInfo.projectVersion.getOrElse("NO-VERSION")}",
Style(fg = Some(Color.White), addModifier = Modifier.DIM)
.addModifier(Modifier.ITALIC)
)
)

val spans = message match
case None => defaults
case Some(value) =>
defaults :+ Spans.styled(
value.message,
Style(fg = Some(value.borderColor))
)

val header =
ParagraphWidget(
text = Text.fromSpans(
Spans.styled(
s"skan",
Style(addModifier = Modifier.BOLD)
),
Spans.styled(
s"v${BuildInfo.projectVersion.getOrElse("NO-VERSION")}",
Style(fg = Some(Color.White), addModifier = Modifier.DIM)
.addModifier(Modifier.ITALIC)
)
),
text = Text.fromSpans(spans*),
alignment = Alignment.Center
)

frame.renderWidget(header, rect)
end render
end Header

0 comments on commit 13623c0

Please sign in to comment.