-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathrules.scala
More file actions
91 lines (70 loc) · 2.89 KB
/
rules.scala
File metadata and controls
91 lines (70 loc) · 2.89 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
package gitcode.viewer.sync
import scala.concurrent.{ExecutionContext, Future}
import scala.concurrent.duration.*
import java.time.Instant
// ============ Domain Models ============
case class Repository(
id: String,
name: String,
url: String,
branch: String = "main",
lastSynced: Option[Instant] = None,
sizeBytes: Long = 0,
)
enum SyncResult:
case Success(repo: Repository, filesChanged: Int)
case Error(repo: Repository, message: String, cause: Option[Throwable] = None)
case Cancelled
enum SyncStatus:
case Idle, Syncing, Done, Failed
// ============ Repository Layer ============
trait RepoStore:
def getAll: Future[List[Repository]]
def getById(id: String): Future[Option[Repository]]
def upsert(repo: Repository): Future[Repository]
def delete(id: String): Future[Boolean]
class InMemoryRepoStore(using ExecutionContext) extends RepoStore:
private var store = Map.empty[String, Repository]
def getAll: Future[List[Repository]] = Future.successful(store.values.toList)
def getById(id: String): Future[Option[Repository]] = Future.successful(store.get(id))
def upsert(repo: Repository): Future[Repository] = Future.successful:
store = store + (repo.id -> repo)
repo
def delete(id: String): Future[Boolean] = Future.successful:
val existed = store.contains(id)
store = store - id
existed
// ============ Sync Service ============
class SyncService(store: RepoStore, maxConcurrent: Int = 3)(using ExecutionContext):
private var statusMap = Map.empty[String, SyncStatus]
def syncAll(): Future[List[SyncResult]] =
store.getAll.flatMap: repos =>
Future.sequence(repos.map(syncOne))
private def syncOne(repo: Repository): Future[SyncResult] =
updateStatus(repo.id, SyncStatus.Syncing)
Future:
Thread.sleep(500)
val updated = repo.copy(lastSynced = Some(Instant.now()))
store.upsert(updated)
updateStatus(repo.id, SyncStatus.Done)
SyncResult.Success(updated, scala.util.Random.nextInt(50) + 1)
.recover:
case e: InterruptedException =>
updateStatus(repo.id, SyncStatus.Idle)
SyncResult.Cancelled
case e: Exception =>
updateStatus(repo.id, SyncStatus.Failed)
SyncResult.Error(repo, e.getMessage, Some(e))
private def updateStatus(id: String, status: SyncStatus): Unit =
statusMap = statusMap + (id -> status)
object SyncService:
def formatBytes(bytes: Long): String = bytes match
case b if b < 1024 => s"$b B"
case b if b < 1024 * 1024 => s"${b / 1024} KB"
case b => f"${b / (1024.0 * 1024.0)}%.1f MB"
// ============ Extension Methods ============
extension (repo: Repository)
def displayName: String =
repo.url.split("/").lastOption.map(_.stripSuffix(".git")).filter(_.nonEmpty).getOrElse(repo.name)
extension (repos: List[Repository])
def totalSize: String = SyncService.formatBytes(repos.map(_.sizeBytes).sum)