mirror of
https://github.com/thomiceli/opengist.git
synced 2026-06-23 04:10:18 +00:00
28736d6b66
Signed-off-by: Thomas Miceli <tho.miceli@gmail.com>
211 lines
5.4 KiB
Go
211 lines
5.4 KiB
Go
package actions
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"sync/atomic"
|
|
"time"
|
|
|
|
"github.com/rs/zerolog/log"
|
|
"github.com/thomiceli/opengist/internal/config"
|
|
"github.com/thomiceli/opengist/internal/db"
|
|
"github.com/thomiceli/opengist/internal/git"
|
|
"github.com/thomiceli/opengist/internal/index"
|
|
)
|
|
|
|
const (
|
|
SyncReposFromFS = iota
|
|
SyncReposFromDB
|
|
GitGcRepos
|
|
SyncGistPreviews
|
|
ResetHooks
|
|
IndexGists
|
|
SyncGistLanguages
|
|
DeleteExpiredGists
|
|
|
|
numActions // keep last — sizes the `running` array
|
|
)
|
|
|
|
// running tracks which actions are in progress in this instance, one slot per
|
|
// action type. It dedupes concurrent runs (e.g. a double-clicked admin button)
|
|
// and backs IsRunning for the admin panel; cross-instance single-flighting is
|
|
// handled separately by the DB action lock.
|
|
var running [numActions]atomic.Bool
|
|
|
|
const lockLease = time.Hour
|
|
|
|
type action struct {
|
|
run func()
|
|
spec string
|
|
}
|
|
|
|
var registry = map[int]action{
|
|
SyncReposFromFS: {run: syncReposFromFS},
|
|
SyncReposFromDB: {run: syncReposFromDB},
|
|
GitGcRepos: {run: gitGcRepos},
|
|
SyncGistPreviews: {run: syncGistPreviews},
|
|
ResetHooks: {run: resetHooks},
|
|
IndexGists: {run: indexGists},
|
|
SyncGistLanguages: {run: syncGistLanguages},
|
|
DeleteExpiredGists: {run: deleteExpiredGists, spec: "@every 1m"},
|
|
}
|
|
|
|
func IsRunning(actionType int) bool {
|
|
return actionType >= 0 && actionType < numActions && running[actionType].Load()
|
|
}
|
|
|
|
func RunOnce(actionType int) {
|
|
a, ok := registry[actionType]
|
|
if !ok {
|
|
log.Error().Msgf("Unknown action type %d", actionType)
|
|
return
|
|
}
|
|
|
|
if !running[actionType].CompareAndSwap(false, true) {
|
|
return // already running in this instance
|
|
}
|
|
defer running[actionType].Store(false)
|
|
|
|
// Single-flight the action across instances sharing the database so only
|
|
// one replica runs it at a time, whether triggered by the scheduler or
|
|
// manually.
|
|
acquired, err := db.AcquireLock(actionType, lockLease)
|
|
if err != nil {
|
|
log.Error().Err(err).Msgf("Could not acquire lock for action %d", actionType)
|
|
return
|
|
}
|
|
if !acquired {
|
|
return
|
|
}
|
|
defer func() {
|
|
if err := db.ReleaseLock(actionType); err != nil {
|
|
log.Error().Err(err).Msgf("Could not release lock for action %d", actionType)
|
|
}
|
|
}()
|
|
|
|
log.Info().Msgf("Starting running action %d", actionType)
|
|
a.run()
|
|
log.Info().Msgf("Finished running action %d", actionType)
|
|
}
|
|
|
|
func syncReposFromFS() {
|
|
log.Info().Msg("Syncing repositories from filesystem...")
|
|
gists, err := db.GetAllGistsRows()
|
|
if err != nil {
|
|
log.Error().Err(err).Msg("Cannot get gists")
|
|
return
|
|
}
|
|
for _, gist := range gists {
|
|
// if repository does not exist, delete gist from database
|
|
if _, err := os.Stat(git.RepositoryPath(gist.User.Username, gist.Uuid)); err != nil && !os.IsExist(err) {
|
|
if err2 := gist.Delete(); err2 != nil {
|
|
log.Error().Err(err2).Msgf("Cannot delete gist %d", gist.ID)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func syncReposFromDB() {
|
|
log.Info().Msg("Syncing repositories from database...")
|
|
entries, err := filepath.Glob(filepath.Join(config.GetHomeDir(), "repos", "*", "*"))
|
|
if err != nil {
|
|
log.Error().Err(err).Msg("Cannot read repos directories")
|
|
return
|
|
}
|
|
|
|
for _, e := range entries {
|
|
path := strings.Split(e, string(os.PathSeparator))
|
|
gist, _ := db.GetGist(path[len(path)-2], path[len(path)-1])
|
|
|
|
if gist.ID == 0 {
|
|
if err := git.DeleteRepository(path[len(path)-2], path[len(path)-1]); err != nil {
|
|
log.Error().Err(err).Msgf("Cannot delete repository %s/%s", path[len(path)-2], path[len(path)-1])
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func gitGcRepos() {
|
|
log.Info().Msg("Garbage collecting all repositories...")
|
|
if err := git.GcRepos(); err != nil {
|
|
log.Error().Err(err).Msg("Error garbage collecting repositories")
|
|
}
|
|
}
|
|
|
|
func syncGistPreviews() {
|
|
log.Info().Msg("Syncing all Gist previews...")
|
|
|
|
gists, err := db.GetAllGistsRows()
|
|
if err != nil {
|
|
log.Error().Err(err).Msg("Cannot get gists")
|
|
return
|
|
}
|
|
for _, gist := range gists {
|
|
fmt.Println("Syncing preview for gist", gist.ID)
|
|
if err = gist.UpdatePreviewAndCount(false); err != nil {
|
|
log.Error().Err(err).Msgf("Cannot update preview and count for gist %d", gist.ID)
|
|
}
|
|
}
|
|
}
|
|
|
|
func resetHooks() {
|
|
log.Info().Msg("Resetting Git server hooks for all repositories...")
|
|
if err := git.ResetHooks(); err != nil {
|
|
log.Error().Err(err).Msg("Error resetting hooks for repositories")
|
|
}
|
|
}
|
|
|
|
func indexGists() {
|
|
log.Info().Msg("Rebuilding index from scratch...")
|
|
if err := index.ResetIndex(); err != nil {
|
|
log.Error().Err(err).Msg("Cannot reset index")
|
|
return
|
|
}
|
|
|
|
gists, err := db.GetAllGistsRows()
|
|
if err != nil {
|
|
log.Error().Err(err).Msg("Cannot get gists")
|
|
return
|
|
}
|
|
|
|
for _, gist := range gists {
|
|
log.Info().Msgf("Indexing gist %d", gist.ID)
|
|
indexedGist, err := gist.ToIndexedGist()
|
|
if err != nil {
|
|
log.Error().Err(err).Msgf("Cannot convert gist %d to indexed gist", gist.ID)
|
|
continue
|
|
}
|
|
if err = index.AddInIndex(indexedGist); err != nil {
|
|
log.Error().Err(err).Msgf("Cannot index gist %d", gist.ID)
|
|
}
|
|
}
|
|
}
|
|
|
|
func syncGistLanguages() {
|
|
log.Info().Msg("Syncing all Gist languages...")
|
|
gists, err := db.GetAllGistsRows()
|
|
if err != nil {
|
|
log.Error().Err(err).Msg("Cannot get gists")
|
|
return
|
|
}
|
|
|
|
for _, gist := range gists {
|
|
log.Info().Msgf("Syncing languages for gist %d", gist.ID)
|
|
gist.UpdateLanguages()
|
|
}
|
|
}
|
|
|
|
func deleteExpiredGists() {
|
|
gists, err := db.DeleteExpiredGists()
|
|
if err != nil {
|
|
log.Error().Err(err).Msg("Cannot delete expired gists")
|
|
return
|
|
}
|
|
|
|
if len(gists) > 0 {
|
|
log.Info().Msgf("Deleted %d expired gist(s)", len(gists))
|
|
}
|
|
}
|