Find similar packages here.
- `, pkg.Name, pkg.Name, pkg.Description, pkg.Maintainer, pkg.Version, pkg.URL, pkg.Name, pkg.Name, pkg.PackageName), + `, pkg.Name, pkg.Name, pkg.Description, pkg.Maintainer, pkg.Version, pkg.URL, pkg.Name, pkg.Name, consts.PACSCRIPT_FILE_EXTENSION, pkg.PackageName), } }, ) diff --git a/server/server/webserver.go b/server/server/webserver.go index 674e8462..1602491a 100644 --- a/server/server/webserver.go +++ b/server/server/webserver.go @@ -41,7 +41,7 @@ func Listen(port int) { if config.Production { path, err := filepath.Abs(config.PublicDir) if err != nil { - log.Fatal("failed to find client public dir at path '%s'. err: %v", config.PublicDir, err) + log.Fatal("failed to find client public dir at path '%s'. err: %+v", config.PublicDir, err) } Router().PathPrefix("/").Handler(spaHandler{staticPath: path}) @@ -57,18 +57,18 @@ func Listen(port int) { err := serverInstance.ListenAndServe() if errors.Is(err, http.ErrServerClosed) { - log.Info("Http server stopped") + log.Info("http server stopped") } else { - log.Fatal("Could not start TCP listener on port %v. Got error: %v\n", port, err) + log.Fatal("could not start TCP listener on port %v. Got error: %+v\n", port, err) } } func Shutdown() { if serverInstance == nil { - log.Info("Server instance is already down") + log.Info("server instance is already down") } ctx := context.Background() serverInstance.Shutdown(ctx) - log.Info("Gracefully shutting down the http server") + log.Info("gracefully shutting down the http server") } diff --git a/server/types/array/array.go b/server/types/array/array.go new file mode 100644 index 00000000..024e6793 --- /dev/null +++ b/server/types/array/array.go @@ -0,0 +1,256 @@ +package array + +import ( + "sort" + "sync/atomic" + + "github.com/joomcode/errorx" +) + +type ComparisonFunc[T any] func(a, b T) bool + +func SortBy[T any](arr []T, isLessThan func(T, T) bool) []T { + sort.SliceStable(arr, func(i, j int) bool { + return isLessThan(arr[i], arr[j]) + }) + + return arr +} + +func IndexOf[T any](arr []T, filter Filterer[T]) (int, error) { + for idx, it := range arr { + if filter(&Iterator[T]{idx, it, arr}) { + return idx, nil + } + } + + return -1, errorx.IllegalState.New("object does not exist in list") +} + +func Find[T any](arr []T, filter Filterer[T]) (T, error) { + idx, err := IndexOf(arr, filter) + if err != nil { + var out T + return out, err + } + + return arr[idx], nil +} + +func Equals[T any](arr []T, other []T, compare ComparisonFunc[T]) bool { + if (arr == nil) != (other == nil) { + return false + } + + if len(arr) != len(other) { + return false + } + + for idx, it := range arr { + if !compare(it, other[idx]) { + return false + } + } + + return true +} + +func Distinct[T any](arr []T, isEq ComparisonFunc[T]) []T { + out := make([]T, 0) + for _, item := range arr { + exists := Contains(out, func(it *Iterator[T]) bool { return isEq(it.Value, item) }) + if !exists { + out = append(out, item) + } + } + + return out +} + +func IsEmpty[T any](arr []T) bool { + return len(arr) == 0 +} + +func FindBy[T any](arr []T, predicate func(T) bool) (T, error) { + for _, it := range arr { + if predicate(it) { + return it, nil + } + } + + var out T + return out, errorx.IllegalState.New("object does not exist in list") +} + +func Contains[T any](arr []T, filter Filterer[T]) bool { + _, err := IndexOf(arr, filter) + return err == nil +} + +func ContainsPtr[T any](arr []T, item *T) bool { + _, err := IndexOf(arr, func(it *Iterator[T]) bool { + return &it.Value == item + }) + return err == nil +} + +func IsSorted[T any](arr []T, isLessThan func(T, T) bool) bool { + for i := 1; i < len(arr); i++ { + if !isLessThan(arr[i-1], arr[i]) { + return false + } + } + + return true +} + +func Clone[T any](arr []T) []T { + return append(make([]T, 0), arr...) +} + +func Filter[T any](arr []T, predicate func(*Iterator[T]) bool) []T { + out := make([]T, 0) + for idx, it := range arr { + if predicate(&Iterator[T]{idx, it, arr}) { + out = append(out, it) + } + } + + return out +} + +func FilterPtr[T any](arr []T, predicate func(*PtrIterator[T]) bool) []T { + out := make([]T, 0) + for idx, it := range arr { + if predicate(&PtrIterator[T]{idx, &it, arr}) { + out = append(out, it) + } + } + + return out +} + +func All[T any](arr []T, predicate func(T) bool) bool { + for _, it := range arr { + if !predicate(it) { + return false + } + } + + return true +} + +func Any[T any](arr []T, predicate func(T) bool) bool { + passes := false + for _, it := range arr { + if predicate(it) { + passes = true + } + } + + return passes +} + +func ToBufChan[T any](arr []T, ch chan T) chan T { + left := int32(len(arr)) + for _, item := range arr { + go func(item T) { + ch <- item + atomic.AddInt32(&left, -1) + if left == 0 { + close(ch) + } + }(item) + } + return ch +} + +func ToChan[T any](arr []T) (ch chan T) { + return ToBufChan(arr, ch) +} + +func Last[T any](arr []T) (T, error) { + if IsEmpty(arr) { + var out T + return out, errorx.IllegalState.New("array is empty") + } + + return arr[len(arr)-1], nil +} + +type Iterator[T any] struct { + Index int + Value T + Array []T +} + +type PtrIterator[T any] struct { + Index int + Value *T + Array []T +} + +/* Maps the given array in place. */ +func Map[T any](arr []T, mapper func(it *Iterator[T]) T) []T { + for idx, item := range arr { + value := mapper(&Iterator[T]{idx, item, arr}) + arr[idx] = value + } + + return arr +} + +func SwitchMap[T any, E any](arr []T, mapper func(it *Iterator[T]) E) []E { + out := make([]E, len(arr)) + for idx, item := range arr { + value := mapper(&Iterator[T]{idx, item, arr}) + out[idx] = value + } + + return out +} + +/* Maps the given array in place. */ +func MapPtr[T any](arr []T, mapper func(it *PtrIterator[T]) T) []T { + for idx, item := range arr { + value := mapper(&PtrIterator[T]{idx, &item, arr}) + arr[idx] = value + } + + return arr +} + +func SwitchMapPtr[T any, E any](arr []T, mapper func(it *PtrIterator[T]) E) []E { + out := make([]E, len(arr)) + for idx, item := range arr { + value := mapper(&PtrIterator[T]{idx, &item, arr}) + out[idx] = value + } + + return out +} + +func ReduceIndex[T any, E any](arr []T, reducer func(int, T, E) E, accumulator E) E { + out := accumulator + for idx, item := range arr { + out = reducer(idx, item, out) + } + + return out +} + +func Reduce[T any, E any](arr []T, reducer func(T, E) E, accumulator E) E { + return ReduceIndex(arr, func(i int, t T, e E) E { + return reducer(t, e) + }, accumulator) +} + +/* Reverse the given array in place. */ +func Reverse[T any](arr []T) []T { + for i := 0; i < len(arr)/2; i++ { + j := len(arr) - i - 1 + arr[i], arr[j] = arr[j], arr[i] + } + + return arr +} diff --git a/server/types/array/array_test.go b/server/types/array/array_test.go new file mode 100644 index 00000000..63b00fe3 --- /dev/null +++ b/server/types/array/array_test.go @@ -0,0 +1,114 @@ +package array_test + +import ( + "testing" + + "pacstall.dev/webserver/types/array" +) + +func Test_Array_SortBy(t *testing.T) { + data := []int{1, 2, 3, 4, 5} + array. + SortBy(data, array.Asc[int]()) + + if !array.IsSorted(data, array.Asc[int]()) { + t.Errorf("Expected array to be sorted") + } +} + +func Test_Array_IsSorted(t *testing.T) { + data := []int{1, 2, 3, 4, 5} + + if !array.IsSorted(data, array.Asc[int]()) { + t.Errorf("Expected array to be sorted") + } +} + +func Test_Array_IsSorted_Fail(t *testing.T) { + data := []int{1, 2, 6, 4, 5} + + if array.IsSorted(data, array.Asc[int]()) { + t.Errorf("Expected array to not be sorted") + } +} + +func Test_Array_IsSorted_Desc(t *testing.T) { + data := []int{5, 4, 3, 2, 1} + + if !array.IsSorted(data, array.Desc[int]()) { + t.Errorf("Expected array to be sorted") + } +} + +func Test_Array_IsSorted_Desc_Fail(t *testing.T) { + data := []int{5, 4, 5, 2, 1} + + if array.IsSorted(data, array.Desc[int]()) { + t.Errorf("Expected list to not be sorted") + } +} + +func Test_Array_Filter(t *testing.T) { + data := []int{5, 4, 7, 7, 2, 1} + data = array.Filter(data, array.Not[int](7)) + + if len(data) != 4 { + t.Errorf("Expected 4, got %d", len(data)) + } +} + +func Test_Array_Filter_Inclussive(t *testing.T) { + data := []int{5, 7, 7, 2, 3, 1} + + data = array.Filter(data, array.Is(7)) + + if len(data) != 2 { + t.Errorf("Expected 2, got %d", len(data)) + } +} + +func Test_Array_Contains(t *testing.T) { + found := array.Contains([]int{5, 4, 7, 7, 2, 1}, array.Is(2)) + if !found { + t.Errorf("Expected to find 2") + } +} + +func Test_Array_Contains_Fail(t *testing.T) { + found := array.Contains([]int{5, 4, 7, 7, 2, 1}, array.Is(10)) + + if found { + t.Errorf("Expected to not find 10") + } +} + +func Test_Find(t *testing.T) { + ten, err := array.Find([]int{5, 4, 7, 7, 2, 10}, array.Is(10)) + + if err != nil { + t.Errorf("Expected to find 10") + } + + if ten != 10 { + t.Errorf("Expected 10, got %d", ten) + } +} + +func Test_Find_Fail(t *testing.T) { + _, err := array.Find([]int{5, 4, 7, 7, 2, 10}, array.Is(11)) + + if err == nil { + t.Errorf("Expected to not find 11") + } +} + +func Test_Distinct(t *testing.T) { + items := []int{4, 7, 10, 4, 7, 10} + + expected := []int{4, 7, 10} + + actualDistinct := array.Distinct(items, array.Eq[int]()) + if !array.Equals(actualDistinct, expected, array.Eq[int]()) { + t.Errorf("Expected %v, got %v", expected, items) + } +} diff --git a/server/types/array/sort.go b/server/types/array/sort.go new file mode 100644 index 00000000..3b47e2a9 --- /dev/null +++ b/server/types/array/sort.go @@ -0,0 +1,37 @@ +package array + +type Mapper[T any, E any] func(T) E +type Filterer[T any] func(*Iterator[T]) bool +type Ordered interface { + ~int | ~int8 | ~int16 | ~int32 | ~int64 | ~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 | ~uintptr | ~float32 | ~float64 | ~string +} + +func Asc[T Ordered]() ComparisonFunc[T] { + return func(a, b T) bool { + return a < b + } +} + +func Eq[T Ordered]() ComparisonFunc[T] { + return func(a, b T) bool { + return a == b + } +} + +func Desc[T Ordered]() ComparisonFunc[T] { + return func(a, b T) bool { + return a > b + } +} + +func Is[T Ordered](value T) Filterer[T] { + return func(it *Iterator[T]) bool { + return it.Value == value + } +} + +func Not[T Ordered](value T) Filterer[T] { + return func(it *Iterator[T]) bool { + return value != it.Value + } +} diff --git a/server/types/list/list_util.go b/server/types/list/list_util.go deleted file mode 100644 index e7c025a8..00000000 --- a/server/types/list/list_util.go +++ /dev/null @@ -1,358 +0,0 @@ -package list - -import ( - "fmt" - "sort" - "sync/atomic" -) - -type List[T any] []T - -type Ordered interface { - ~int | ~int8 | ~int16 | ~int32 | ~int64 | ~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 | ~uintptr | ~float32 | ~float64 | ~string -} - -type sorter[T any] struct { - list []T - less func(T, T) bool -} - -func (s *sorter[T]) Swap(i, j int) { - s.list[i], s.list[j] = s.list[j], s.list[i] -} - -func (s *sorter[T]) Len() int { - return len(s.list) -} - -func (s *sorter[T]) Less(i, j int) bool { - return s.less(s.list[i], s.list[j]) -} - -type ComparableList[T Ordered] struct { - List[T] -} - -func New[T any]() List[T] { - return From(make([]T, 0)) -} - -func From[T any](items []T) List[T] { - return items -} - -type ComparisonFunc[T any] func(a, b T) bool - -func (list List[T]) IndexOf(filter Filterer[T]) (int, error) { - for idx, it := range list.ToSlice() { - if filter(it) { - return idx, nil - } - } - - return -1, fmt.Errorf("object does not exist in list") -} - -func shallowEqual[T any](a, b T) bool { - return &a == &b -} - -func (list List[T]) Equals(other List[T], compare ComparisonFunc[T]) bool { - if (list == nil) != (other == nil) { - return false - } - - if len(list) != len(other) { - return false - } - - for idx, it := range list.ToSlice() { - if !compare(it, other[idx]) { - return false - } - } - - return true -} - -func (list List[T]) Distinct(isEq ComparisonFunc[T]) (out List[T]) { - out = New[T]() - for _, item := range list.ToSlice() { - exists := out.Contains(func(it T) bool { return isEq(it, item) }) - if !exists { - out = append(out, item) - } - } - - return out -} - -func (list List[T]) Append(item T) List[T] { - clone := list.Clone() - clone = append(clone, item) - return clone -} - -func (list List[T]) Remove(item *T) List[T] { - clone := list.RemoveFunc(func(a T) bool { - return shallowEqual(a, *item) - }) - - return clone -} - -func (list ComparableList[T]) Remove(item T) List[T] { - clone := list.RemoveFunc(func(a T) bool { - return a == item - }) - - return clone -} - -func (list List[T]) RemoveFunc(matches func(T) bool) List[T] { - clone := list.FilterIndex(func(_ int, t T) bool { - return !matches(t) - }) - - return clone -} - -func (list List[T]) IsEmpty() bool { - return list.Len() == 0 -} - -func (list List[T]) Find(filter Filterer[T]) (T, error) { - idx, err := list.IndexOf(filter) - if err != nil { - var out T - return out, err - } - - return list[idx], nil -} - -func (list List[T]) Last() (T, error) { - if list.IsEmpty() { - var out T - return out, fmt.Errorf("list is empty") - } - - return list[list.Len()-1], nil -} - -func (list List[T]) MapIndex(mapper func(int, T) T) List[T] { - out := make(List[T], list.Len()) - for idx, item := range list { - value := mapper(idx, item) - out[idx] = value - } - - return out -} - -func (list List[T]) Map(mapper func(T) T) List[T] { - return list.MapIndex(func(i int, t T) T { - return mapper(t) - }) -} - -func (list List[T]) MapExt(mapper func(T, List[T]) T) List[T] { - clone := list.Clone() - return list.MapIndex(func(i int, t T) T { - return mapper(t, clone) - }) -} - -func (list List[T]) Apply(mapper func([]T) []T) List[T] { - return mapper(list.Clone()) -} - -func Apply[T any, E any](list List[T], mapper func([]T) []E) List[E] { - return mapper(list.Clone()) -} - -func MapIndex[T any, E any](list List[T], mapper func(int, T) E) List[E] { - out := make(List[E], list.Len()) - for idx, item := range list { - value := mapper(idx, item) - out[idx] = value - } - - return out -} - -func Map[T any, E any](list List[T], mapper func(int, T) E) List[E] { - return MapIndex(list, func(i int, t T) E { - return mapper(i, t) - }) -} - -func ReduceIndex[T any, E any](list List[T], reducer func(int, T, E) E, accumulator E) E { - out := accumulator - for idx, item := range list { - out = reducer(idx, item, out) - } - - return out -} - -func Reduce[T any, E any](list List[T], reducer func(T, E) E, accumulator E) E { - return ReduceIndex(list, func(i int, t T, e E) E { - return reducer(t, e) - }, accumulator) -} - -func (list List[T]) Reverse() List[T] { - clone := list.Clone() - for i := clone.Len() - 1; i >= 0; i-- { - clone = append(clone, clone[i]) - } - - return clone -} - -func (list List[T]) FindBy(predicate func(T) bool) (T, error) { - for _, it := range list.ToSlice() { - if predicate(it) { - return it, nil - } - } - - var out T - return out, fmt.Errorf("object does not exist in list") -} - -func (list List[T]) Contains(filter Filterer[T]) bool { - _, err := list.IndexOf(filter) - return err == nil -} - -func (list List[T]) ContainsPtr(item *T) bool { - _, err := list.IndexOf(func(t T) bool { - return &t == item - }) - return err == nil -} - -func (list ComparableList[T]) Contains(item T) bool { - _, err := list.IndexOf(Is(item)) - return err == nil -} - -func (list List[T]) Len() int { - return len(list.ToSlice()) -} - -func (list List[T]) ToSlice() []T { - return []T(list) -} - -func (list List[T]) SortBy(isLessThan func(T, T) bool) List[T] { - a := list.Clone().ToSlice()[:] - sort.SliceStable(a, func(i, j int) bool { - return isLessThan(a[i], a[j]) - }) - - return a -} - -func quicksort[T any](a []T, isLessThan func(T, T) bool) List[T] { - if len(a) < 2 { - return a - } - - left, right := 0, len(a)-1 - - // Pick a pivot - pivotIndex := len(a) / 2 - - // Move the pivot to the right - a[pivotIndex], a[right] = a[right], a[pivotIndex] - - // Pile elements smaller than the pivot on the left - for i := range a { - if isLessThan(a[i], a[right]) { - a[i], a[left] = a[left], a[i] - left++ - } - } - - // Place the pivot after the last smaller element - a[left], a[right] = a[right], a[left] - - // Go down the rabbit hole - quicksort(a[:left], isLessThan) - quicksort(a[left+1:], isLessThan) - - return a -} - -func (list List[T]) IsSorted(isLessThan func(T, T) bool) bool { - for i := 1; i < list.Len(); i++ { - if !isLessThan(list[i-1], list[i]) { - return false - } - } - - return true -} - -func (list List[T]) Clone() List[T] { - return append(List[T](make([]T, 0)), list...) -} - -func (list List[T]) FilterIndex(predicate func(int, T) bool) List[T] { - out := make([]T, 0) - for idx, it := range list.ToSlice() { - if predicate(idx, it) { - out = append(out, it) - } - } - - return out -} - -func (list List[T]) Filter(predicate func(T) bool) List[T] { - return list.FilterIndex(func(i int, t T) bool { - return predicate(t) - }) -} - -func (list List[T]) All(predicate func(T) bool) bool { - for _, it := range list.ToSlice() { - if !predicate(it) { - return false - } - } - - return true -} - -func (list List[T]) Any(predicate func(T) bool) bool { - passes := false - for _, it := range list.ToSlice() { - if predicate(it) { - passes = true - } - } - - return passes -} - -func (list List[T]) ToBufChan(ch chan T) chan T { - left := int32(list.Len()) - for _, item := range list.ToSlice() { - go func(item T) { - ch <- item - atomic.AddInt32(&left, -1) - if left == 0 { - close(ch) - } - }(item) - } - return ch -} - -func (list List[T]) ToChan() (ch chan T) { - return list.ToBufChan(ch) -} diff --git a/server/types/list/list_util_test.go b/server/types/list/list_util_test.go deleted file mode 100644 index f15d5543..00000000 --- a/server/types/list/list_util_test.go +++ /dev/null @@ -1,221 +0,0 @@ -package list_test - -import ( - "testing" - - "pacstall.dev/webserver/types/list" -) - -func Test_List_Append_Remove(t *testing.T) { - data := list.New[int](). - Append(1). - Append(2). - Append(3). - Append(4). - Append(5). - Append(6). - RemoveFunc(list.Is(6)) - - if data.Len() != 5 { - t.Errorf("Expected 5, got %d", data.Len()) - } - - if data[0] != 1 { - t.Errorf("Expected 1, got %d", data[0]) - } - - if data[1] != 2 { - t.Errorf("Expected 2, got %d", data[1]) - } - - if data[2] != 3 { - t.Errorf("Expected 3, got %d", data[2]) - } - - if data[3] != 4 { - t.Errorf("Expected 4, got %d", data[3]) - } - - if data[4] != 5 { - t.Errorf("Expected 5, got %d", data[4]) - } -} - -func Test_List_SortBy(t *testing.T) { - data := list.New[int](). - Append(5). - Append(3). - Append(6). - Append(2). - Append(1). - SortBy(list.Asc[int]()) - - if !data.IsSorted(list.Asc[int]()) { - t.Errorf("Expected list to be sorted") - } -} - -func Test_List_IsSorted(t *testing.T) { - data := list.New[int](). - Append(1). - Append(2). - Append(3). - Append(4). - Append(5) - - if !data.IsSorted(list.Asc[int]()) { - t.Errorf("Expected list to be sorted") - } -} - -func Test_List_IsSorted_Fail(t *testing.T) { - data := list.New[int](). - Append(1). - Append(2). - Append(6). - Append(4). - Append(5) - - if data.IsSorted(list.Asc[int]()) { - t.Errorf("Expected list to be sorted") - } -} - -func Test_List_IsSorted_Desc(t *testing.T) { - data := list.New[int](). - Append(5). - Append(4). - Append(3). - Append(2). - Append(1) - - if !data.IsSorted(list.Desc[int]()) { - t.Errorf("Expected list to be sorted") - } -} - -func Test_List_IsSorted_Desc_Fail(t *testing.T) { - data := list.New[int](). - Append(5). - Append(4). - Append(7). - Append(2). - Append(1) - - if data.IsSorted(list.Desc[int]()) { - t.Errorf("Expected list to be sorted") - } -} - -func Test_List_Filter(t *testing.T) { - data := list.New[int](). - Append(5). - Append(4). - Append(7). - Append(7). - Append(2). - Append(1). - Filter(list.Not(7)) - - if data.Len() != 4 { - t.Errorf("Expected 4, got %d", data.Len()) - } -} - -func Test_List_Filter_Inclussive(t *testing.T) { - data := list.New[int](). - Append(5). - Append(4). - Append(7). - Append(7). - Append(2). - Append(1). - Filter(list.Is(7)) - - if data.Len() != 2 { - t.Errorf("Expected 2, got %d", data.Len()) - } -} - -func Test_List_Contains(t *testing.T) { - found := list.New[int](). - Append(5). - Append(4). - Append(7). - Append(7). - Append(2). - Append(1). - Contains(list.Is(2)) - - if !found { - t.Errorf("Expected to find 2") - } -} - -func Test_List_Contains_Fail(t *testing.T) { - found := list.New[int](). - Append(5). - Append(4). - Append(7). - Append(7). - Append(2). - Append(1). - Contains(list.Is(10)) - - if found { - t.Errorf("Expected to not find 10") - } -} - -func Test_Find(t *testing.T) { - ten, err := list.New[int](). - Append(5). - Append(4). - Append(7). - Append(7). - Append(2). - Append(10). - Find(list.Is(10)) - - if err != nil { - t.Errorf("Expected to find 10") - } - - if ten != 10 { - t.Errorf("Expected 10, got %d", ten) - } -} - -func Test_Find_Fail(t *testing.T) { - _, err := list.New[int](). - Append(5). - Append(4). - Append(7). - Append(7). - Append(2). - Append(10). - Find(list.Is(11)) - - if err == nil { - t.Errorf("Expected to not find 11") - } -} - -func Test_Distinct(t *testing.T) { - items := list.New[int](). - Append(4). - Append(7). - Append(7). - Append(7). - Append(10). - Append(10) - - expected := list.New[int](). - Append(4). - Append(7). - Append(10) - - if !items.Distinct(list.Eq[int]()).Equals(expected, list.Eq[int]()) { - t.Errorf("Expected %v, got %v", expected, items) - } -} diff --git a/server/types/list/sort.go b/server/types/list/sort.go deleted file mode 100644 index 4d6959ee..00000000 --- a/server/types/list/sort.go +++ /dev/null @@ -1,60 +0,0 @@ -package list - -// ~int | ~int8 | ~int16 | ~int32 | ~int64 | ~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 | ~uintptr | ~float32 | ~float64 | ~string - -type Mapper[T any, E any] func(T) E -type Filterer[T any] func(T) bool - -func Asc[T Ordered]() ComparisonFunc[T] { - return func(a, b T) bool { - return a < b - } -} - -func Eq[T Ordered]() ComparisonFunc[T] { - return func(a, b T) bool { - return a == b - } -} - -func Desc[T Ordered]() ComparisonFunc[T] { - return func(a, b T) bool { - return a > b - } -} - -func AscBy[T any, E Ordered](mapper Mapper[T, E]) ComparisonFunc[T] { - return func(a, b T) bool { - return mapper(a) < mapper(b) - } -} - -func DescBy[T any, E Ordered](mapper Mapper[T, E]) ComparisonFunc[T] { - return func(a, b T) bool { - return mapper(a) > mapper(b) - } -} - -func Is[T Ordered](value T) Filterer[T] { - return func(a T) bool { - return a == value - } -} - -func Not[T Ordered](value T) Filterer[T] { - return func(a T) bool { - return a != value - } -} - -func IsBy[T any, E Ordered](value E, mapper Mapper[T, E]) Filterer[T] { - return func(a T) bool { - return mapper(a) == value - } -} - -func NotBy[T any, E Ordered](value E, mapper Mapper[T, E]) Filterer[T] { - return func(a T) bool { - return mapper(a) != value - } -} diff --git a/server/types/pac/pacstore/store.go b/server/types/pac/pacstore/store.go index 63d63774..c2947ea1 100644 --- a/server/types/pac/pacstore/store.go +++ b/server/types/pac/pacstore/store.go @@ -3,42 +3,34 @@ package pacstore import ( "time" - "pacstall.dev/webserver/types/list" + "pacstall.dev/webserver/types/array" "pacstall.dev/webserver/types/pac" ) -type PacscriptList struct { - list.List[*pac.Script] -} - var lastModified time.Time -var loadedPacscripts PacscriptList +var loadedPacscripts []*pac.Script -func (l PacscriptList) FindByName(name string) (*pac.Script, error) { - return l.FindBy(func(pi *pac.Script) bool { - return pi.Name == name +func FindByName(name string) (*pac.Script, error) { + return array.FindBy(loadedPacscripts, func(p *pac.Script) bool { + return p.Name == name }) } -func (l PacscriptList) FindByMaintainer(maintainer string) (*pac.Script, error) { - return l.FindBy(func(pi *pac.Script) bool { - return pi.Maintainer == maintainer +func FindByMaintainer(maintainer string) (*pac.Script, error) { + return array.FindBy(loadedPacscripts, func(p *pac.Script) bool { + return p.Maintainer == maintainer }) } -func GetAll() PacscriptList { - return PacscriptList{ - loadedPacscripts.Clone().ToSlice(), - } +func GetAll() []*pac.Script { + return array.Clone(loadedPacscripts) } func LastModified() time.Time { return lastModified } -func Update(scripts list.List[*pac.Script]) { +func Update(scripts []*pac.Script) { lastModified = time.Now() - loadedPacscripts = PacscriptList{ - scripts.Clone(), - } + loadedPacscripts = scripts } diff --git a/server/types/pac/parser/git/lib.go b/server/types/pac/parser/git/lib.go index 757f4490..56d2df55 100644 --- a/server/types/pac/parser/git/lib.go +++ b/server/types/pac/parser/git/lib.go @@ -3,6 +3,8 @@ package git import ( "os" "os/exec" + + "github.com/joomcode/errorx" ) func hardResetAndPull(path string) error { @@ -13,7 +15,7 @@ func hardResetAndPull(path string) error { } // https://stackoverflow.com/a/41081908/13449010 - cmd = exec.Command("git", "fetch", "--depth=1") + cmd = exec.Command("git", "fetch") cmd.Dir = path if err := cmd.Run(); err != nil { return err @@ -35,9 +37,9 @@ func hardResetAndPull(path string) error { } func clonePrograms(path, url string) error { - cmd := exec.Command("git", "clone", "--depth=1", url, path) + cmd := exec.Command("git", "clone", url, path) if err := cmd.Run(); err != nil { - return err + return errorx.Decorate(err, "failed to run git clone command") } return nil @@ -49,11 +51,11 @@ func RefreshPrograms(path, url string) error { } if err := os.RemoveAll(path); err != nil { - return err + return errorx.Decorate(err, "failed to remove directory '%v'", path) } if err := clonePrograms(path, url); err != nil { - return err + return errorx.Decorate(err, "failed to clone repository '%v'", url) } return nil diff --git a/server/types/pac/parser/last_updated.go b/server/types/pac/parser/last_updated.go new file mode 100644 index 00000000..026a3ea9 --- /dev/null +++ b/server/types/pac/parser/last_updated.go @@ -0,0 +1,104 @@ +package parser + +import ( + "fmt" + "os" + "path" + "strconv" + "strings" + "time" + + "github.com/joomcode/errorx" + "pacstall.dev/webserver/config" + "pacstall.dev/webserver/consts" + "pacstall.dev/webserver/types/array" + "pacstall.dev/webserver/types/pac" + "pacstall.dev/webserver/types/pac/parser/pacsh" +) + +type packageLastUpdatedTuple struct { + packageName string + lastUpdated time.Time +} + +func getPackageLastUpdatedTuples() ([]packageLastUpdatedTuple, error) { + wordingDirectoryAbsolute, err := os.Getwd() + if err != nil { + return nil, errorx.Decorate(err, "failed to get absolute path to wording directory") + } + + programsPath := path.Join(wordingDirectoryAbsolute, config.GitClonePath) + script := fmt.Sprintf(` + cd %v + for i in ./packages/*/*.%s; do echo $i; git log -1 --pretty=\"%%at\" $i; done + `, programsPath, consts.PACSCRIPT_FILE_EXTENSION) + + outputBytes, err := pacsh.ExecBash(programsPath, "last_updated.sh", []byte(script)) + if err != nil { + return nil, errorx.Decorate(err, "failed to get last updated git output") + } + + output := string(outputBytes) + lines := strings.Split(output, "\n") + lines = lines[:len(lines)-1] // Remove last empty line + tuples := make([]packageLastUpdatedTuple, 0) + + for i := 0; i < len(lines)-1; i += 2 { + packagePath := lines[i] + lastUpdatedString := lines[i+1] // Unix time + + // Remove quotes + lastUpdatedString = lastUpdatedString[1 : len(lastUpdatedString) - 1] + + packageNameWithExtension := path.Base(packagePath) + packageName := strings.TrimSuffix(packageNameWithExtension, "."+consts.PACSCRIPT_FILE_EXTENSION) + + if packageName == "" || strings.HasPrefix(packageName, "-") { + return nil, errorx.IllegalState.New("failed to parse package name from package path '%v'", packagePath) + } + + lastUpdatedUnixTime, err := strconv.ParseInt(lastUpdatedString, 10, 64) + if err != nil { + return nil, errorx.Decorate(err, "failed to parse '%v' as int64", lastUpdatedString) + } + + lastUpdated := time.Unix(lastUpdatedUnixTime, 0).UTC() + + if lastUpdated.Year() < 2000 { + return nil, errorx.IllegalState.New("failed to parse last updated time for package '%v'. Given date is %v", packagePath, lastUpdatedString) + } + + tuples = append(tuples, packageLastUpdatedTuple{ + packageName: packageName, + lastUpdated: lastUpdated, + }) + } + + return tuples, nil +} + +func setLastUpdatedAt(packages []*pac.Script) error { + lastUpdatedTuples, err := getPackageLastUpdatedTuples() + if err != nil { + return errorx.Decorate(err, "failed to get package last updated tuples") + } + + packages = array.Clone(packages) + packages = array.SortBy(packages, func(s1, s2 *pac.Script) bool { + return s1.Name < s2.Name + }) + + lastUpdatedTuples = array.SortBy(lastUpdatedTuples, func(t1, t2 packageLastUpdatedTuple) bool { + return t1.packageName < t2.packageName + }) + + if len(lastUpdatedTuples) != len(packages) { + return errorx.AssertionFailed.New("expected %v package last updated tuples but got %v", len(packages), len(lastUpdatedTuples)) + } + + for i := 0; i < len(packages); i++ { + packages[i].LastUpdatedAt = lastUpdatedTuples[i].lastUpdated + } + + return nil +} diff --git a/server/types/pac/parser/pacscript.go b/server/types/pac/parser/pacscript.go index 7bae5671..2c96dff6 100644 --- a/server/types/pac/parser/pacscript.go +++ b/server/types/pac/parser/pacscript.go @@ -4,7 +4,7 @@ import ( "fmt" "strings" - "pacstall.dev/webserver/types/list" + "pacstall.dev/webserver/types/array" "pacstall.dev/webserver/types/pac" "pacstall.dev/webserver/types/pac/parser/pacsh" ) @@ -62,17 +62,16 @@ fi return []byte(script) } -func computeRequiredBy(script pac.Script, scripts list.List[*pac.Script]) *pac.Script { - pickBeforeColon := func(line string) string { - return strings.Split(line, ": ")[0] +func computeRequiredBy(script *pac.Script, scripts []*pac.Script) { + pickBeforeColon := func(it *array.Iterator[string]) string { + return strings.Split(it.Value, ": ")[0] } - script.RequiredBy = list.Map( - scripts.Filter(func(s *pac.Script) bool { - return list.From(s.PacstallDependencies).Map(pickBeforeColon).Contains(list.Is(script.Name)) - }), func(_ int, s *pac.Script) string { - return s.Name - }) - - return &script + script.RequiredBy = make([]string, 0) + for _, otherScript := range scripts { + otherScriptDependencies := array.Map(otherScript.PacstallDependencies, pickBeforeColon) + if array.Contains(otherScriptDependencies, array.Is(script.Name)) { + script.RequiredBy = append(script.RequiredBy, otherScript.Name) + } + } } diff --git a/server/types/pac/parser/pacsh/exec_sh.go b/server/types/pac/parser/pacsh/exec_sh.go index 893d4670..8731c9c0 100644 --- a/server/types/pac/parser/pacsh/exec_sh.go +++ b/server/types/pac/parser/pacsh/exec_sh.go @@ -3,16 +3,17 @@ package pacsh import ( "os" + "github.com/joomcode/errorx" "pacstall.dev/webserver/log" ) var removeFile = os.Remove var ExecBash = execBash -func execBash(cwd, filename string, pacscript []byte) (stdout []byte, err error) { - tmpPath, err := CreateTempExecutable(cwd, filename, pacscript) +func execBash(cwd, filename string, content []byte) (stdout []byte, err error) { + tmpPath, err := CreateTempExecutable(cwd, filename, content) if err != nil { - return + return nil, errorx.Decorate(err, "failed to create temp executable") } defer removeFile(tmpPath) @@ -20,7 +21,7 @@ func execBash(cwd, filename string, pacscript []byte) (stdout []byte, err error) if err != nil { bytes, _ := os.ReadFile(tmpPath) log.Debug("Failed to execute '%v'. %v\n%v", tmpPath, err, string(bytes)) - return + return nil, errorx.Decorate(err, "failed to execute '%v'", tmpPath) } return stdout, nil diff --git a/server/types/pac/parser/pacsh/parse_pac_output.go b/server/types/pac/parser/pacsh/parse_pac_output.go index 9cdaf7d1..47e09e01 100644 --- a/server/types/pac/parser/pacsh/parse_pac_output.go +++ b/server/types/pac/parser/pacsh/parse_pac_output.go @@ -1,10 +1,10 @@ package pacsh import ( - "fmt" "strings" - "pacstall.dev/webserver/types/list" + "github.com/joomcode/errorx" + "pacstall.dev/webserver/types/array" "pacstall.dev/webserver/types/pac" ) @@ -34,15 +34,20 @@ const ( ) func parseSubcategory(category string) []string { - return list.From(strings.Split(category, "+ +++")).Map(func(s string) string { - return strings.TrimSpace(s) - }).Filter(list.Not("")) + subcategories := strings.Split(category, "+ +++") + for i, subcategory := range subcategories { + subcategories[i] = strings.TrimSpace(subcategory) + } + + return array.Filter(subcategories, func(it *array.Iterator[string]) bool { + return len(it.Value) > 0 + }) } func parseOutput(data []byte) (out pac.Script, err error) { content := string(data) - categories := list.From(strings.Split(content, "++++")).Map(func(s string) string { return strings.TrimSpace(s) }).ToSlice()[1:] + categories := array.Map(strings.Split(content, "++++"), func(it *array.Iterator[string]) string { return strings.TrimSpace(it.Value) })[1:] name := categories[nameIdx] packageName := categories[pkgnameIdx] maintainer := categories[maintainerIdx] @@ -70,7 +75,7 @@ func parseOutput(data []byte) (out pac.Script, err error) { if strings.HasSuffix(name, "-git") { version = "git" } else { - return out, fmt.Errorf("version is empty") + return out, errorx.IllegalArgument.New("expected version to be non-empty but got: %v", version) } } diff --git a/server/types/pac/parser/pacsh/pretty-name.go b/server/types/pac/parser/pacsh/pretty-name.go index 54edd27b..216df132 100644 --- a/server/types/pac/parser/pacsh/pretty-name.go +++ b/server/types/pac/parser/pacsh/pretty-name.go @@ -3,7 +3,6 @@ package pacsh import ( "strings" - "pacstall.dev/webserver/types/list" "pacstall.dev/webserver/types/pac" ) @@ -31,12 +30,14 @@ func getPrettyName(p pac.Script) string { } func titleCase(s string) string { - out := list.Reduce(strings.Split(s, "-"), func(word string, acc string) string { - if acc != "" { - acc += " " + title := "" + for _, word := range strings.Split(s, "-") { + if title != "" { + title += " " } - return acc + strings.ToUpper(word[:1]) + strings.ToLower(word[1:]) - }, "") - return out + title += strings.ToUpper(word[:1]) + strings.ToLower(word[1:]) + } + + return title } diff --git a/server/types/pac/parser/pacsh/temp_dir.go b/server/types/pac/parser/pacsh/temp_dir.go index 1a8f81d6..23ebc399 100644 --- a/server/types/pac/parser/pacsh/temp_dir.go +++ b/server/types/pac/parser/pacsh/temp_dir.go @@ -4,7 +4,7 @@ import ( "io/fs" "os" - "pacstall.dev/webserver/log" + "github.com/joomcode/errorx" ) var CreateTempDirectory = createTempDirectory @@ -16,14 +16,12 @@ var makeDir = os.Mkdir func createTempDirectory(path string) error { if _, err := statFile(path); os.IsNotExist(err) { if err = makeDir(path, fs.FileMode(int(0777))); err != nil { - log.Error("Failed to create temp dir '%v'\n%v", path, err) - return err + return errorx.Decorate(err, "failed to create temp dir '%v'", path) } } else { if err := removeAll(path); err != nil { - log.Error("Failed to remove existing temp dir '%v'", path) - return err + return errorx.Decorate(err, "failed to remove existing temp dir '%v'", path) } return createTempDirectory(path) diff --git a/server/types/pac/parser/pacsh/temp_dir_test.go b/server/types/pac/parser/pacsh/temp_dir_test.go index 647d5bdd..ce00df49 100644 --- a/server/types/pac/parser/pacsh/temp_dir_test.go +++ b/server/types/pac/parser/pacsh/temp_dir_test.go @@ -6,7 +6,7 @@ import ( "testing" "time" - "pacstall.dev/webserver/types/list" + "pacstall.dev/webserver/types/array" ) func cleanup() { @@ -62,7 +62,7 @@ func Test_CreateTempDirectory_NoExisting(t *testing.T) { } statFileCalled += 1 - name, _ := list.From(strings.Split(path, "/")).Last() + name, _ := array.Last(strings.Split(path, "/")) return testFileInfo{ name: name, size: 0, @@ -85,19 +85,19 @@ func Test_CreateTempDirectory_NoExisting(t *testing.T) { err := CreateTempDirectory("/tmp") if err != nil { - t.Errorf("Expected no error, got %v", err) + t.Errorf("expected no error, got %v", err) } if removeDirCalled != 1 { - t.Error("Expected removeAll to be called 1 time but was called", removeDirCalled) + t.Error("expected removeAll to be called 1 time but was called", removeDirCalled) } if makeDirCalled != 1 { - t.Error("Expected makeDir to be called 1 time but was called", makeDirCalled) + t.Error("expected makeDir to be called 1 time but was called", makeDirCalled) } if statFileCalled != 2 { - t.Error("Expected statFile to be called 2 times but was called", statFileCalled) + t.Error("expected statFile to be called 2 times but was called", statFileCalled) } } @@ -115,7 +115,7 @@ func Test_CreateTempDirectory_AlreadyExisting(t *testing.T) { } statFileCalled += 1 - name, _ := list.From(strings.Split(path, "/")).Last() + name, _ := array.Last(strings.Split(path, "/")) return testFileInfo{ name: name, size: 0, @@ -138,18 +138,18 @@ func Test_CreateTempDirectory_AlreadyExisting(t *testing.T) { err := CreateTempDirectory("/tmp") if err != nil { - t.Errorf("Expected no error, got %v", err) + t.Errorf("expected no error, got %v", err) } if removeDirCalled != 0 { - t.Error("Expected removeAll to be called 0 times but was called", removeDirCalled) + t.Error("expected removeAll to be called 0 times but was called", removeDirCalled) } if makeDirCalled != 1 { - t.Error("Expected makeDir to be called 1 time but was called", makeDirCalled) + t.Error("expected makeDir to be called 1 time but was called", makeDirCalled) } if statFileCalled != 1 { - t.Error("Expected statFile to be called 2 times but was called", statFileCalled) + t.Error("expected statFile to be called 2 times but was called", statFileCalled) } } diff --git a/server/types/pac/parser/pacsh/temp_exec.go b/server/types/pac/parser/pacsh/temp_exec.go index 0d186f64..b87028e6 100644 --- a/server/types/pac/parser/pacsh/temp_exec.go +++ b/server/types/pac/parser/pacsh/temp_exec.go @@ -6,6 +6,7 @@ import ( "os/exec" "path" + "github.com/joomcode/errorx" "pacstall.dev/webserver/log" ) @@ -19,8 +20,7 @@ func createTempExecutable(dirPath, fileName string, content []byte) (string, err tmpFile, err := createFile(joinPaths(dirPath, fileName)) if err != nil { - log.Error("Failed to create temporary file '%v' in dir '%v'", fileName, dirPath) - return "", err + return "", errorx.Decorate(err, "failed to create temporary file '%v' in dir '%v'", fileName, dirPath) } defer tmpFile.Close() tmpPath := tmpFile.Name() @@ -29,18 +29,16 @@ func createTempExecutable(dirPath, fileName string, content []byte) (string, err cmd := execCommand("chmod", "+rwx", fileName) cmd.Dir = dirPath if err := cmd.Run(); err != nil { - log.Error("Failed to chmod temporary file '%v' in dir '%v'", fileName, dirPath) + log.Error("%+v", errorx.Decorate(err, "failed to chmod temporary file '%v' in dir '%v'", fileName, dirPath)) } }() if _, err = tmpFile.Write([]byte(content)); err != nil { - log.Error("Failed to write to file '%v'\n%v", tmpPath, err) - return "", err + return "", errorx.Decorate(err, "failed to write to file '%v'", tmpPath) } if err := tmpFile.Chmod(fs.FileMode(int(0777))); err != nil { - log.Error("Failed to chmod file '%v'\n%v", tmpPath, err) - return "", err + return "", errorx.Decorate(err, "failed to chmod file '%v'", tmpPath) } return tmpPath, nil diff --git a/server/types/pac/parser/parallelism/batch/run.go b/server/types/pac/parser/parallelism/batch/run.go index 834e9a54..95adb852 100644 --- a/server/types/pac/parser/parallelism/batch/run.go +++ b/server/types/pac/parser/parallelism/batch/run.go @@ -3,6 +3,8 @@ package batch import ( "sync/atomic" "time" + + "pacstall.dev/webserver/log" ) func Run[T any, E any](batchSize int, items []T, fn func(T) (E, error)) <-chan E { @@ -18,6 +20,8 @@ func Run[T any, E any](batchSize int, items []T, fn func(T) (E, error)) <-chan E if err == nil { out <- result + } else { + log.Error("batch error: %+v", err) } atomic.AddInt32(&left, -1) diff --git a/server/types/pac/parser/parallelism/batch/run_test.go b/server/types/pac/parser/parallelism/batch/run_test.go index 198e07d5..7268e397 100644 --- a/server/types/pac/parser/parallelism/batch/run_test.go +++ b/server/types/pac/parser/parallelism/batch/run_test.go @@ -4,7 +4,7 @@ import ( "testing" "time" - "pacstall.dev/webserver/types/list" + "pacstall.dev/webserver/types/array" "pacstall.dev/webserver/types/pac/parser/parallelism/batch" "pacstall.dev/webserver/types/pac/parser/parallelism/channels" ) @@ -22,7 +22,7 @@ func Test_Batch_Run(t *testing.T) { return item, nil }) - if channels.ToList(out).SortBy(list.Asc[int]()).Equals(items, list.Eq[int]()) == false { - t.Error("Expected results to be sorted") + if array.Equals(array.SortBy(channels.ToSlice(out), array.Asc[int]()), items, array.Eq[int]()) == false { + t.Error("expected results to be sorted") } } diff --git a/server/types/pac/parser/parallelism/channels/to_slice.go b/server/types/pac/parser/parallelism/channels/to_slice.go index 4e5a1f25..1f469dc7 100644 --- a/server/types/pac/parser/parallelism/channels/to_slice.go +++ b/server/types/pac/parser/parallelism/channels/to_slice.go @@ -1,7 +1,5 @@ package channels -import "pacstall.dev/webserver/types/list" - func ToSlice[T any](in <-chan T) []T { out := make([]T, 0) for item := range in { @@ -10,7 +8,3 @@ func ToSlice[T any](in <-chan T) []T { return out } - -func ToList[T any](in <-chan T) list.List[T] { - return list.From(ToSlice(in)) -} diff --git a/server/types/pac/parser/parse.go b/server/types/pac/parser/parse.go index d7e83cbe..18028437 100644 --- a/server/types/pac/parser/parse.go +++ b/server/types/pac/parser/parse.go @@ -6,11 +6,13 @@ import ( "path" "strings" + "github.com/joomcode/errorx" "pacstall.dev/webserver/config" + "pacstall.dev/webserver/consts" "pacstall.dev/webserver/log" "pacstall.dev/webserver/repology" "pacstall.dev/webserver/types" - "pacstall.dev/webserver/types/list" + "pacstall.dev/webserver/types/array" "pacstall.dev/webserver/types/pac" "pacstall.dev/webserver/types/pac/pacstore" "pacstall.dev/webserver/types/pac/parser/git" @@ -23,28 +25,38 @@ const PACKAGE_LIST_FILE_NAME = "./packagelist" func ParseAll() error { if err := git.RefreshPrograms(config.GitClonePath, config.GitURL); err != nil { - return fmt.Errorf("could not update repository 'pacstall-programs'. %v", err) + return errorx.Decorate(err, "could not update repository 'pacstall-programs'") } pkgList, err := readKnownPacscriptNames() if err != nil { - return fmt.Errorf("failed to parse packagelist. %v", err) + return errorx.Decorate(err, "failed to parse packagelist") } - loadedPacscripts := list.From(parsePacscriptFiles(pkgList)).MapExt(func(p *pac.Script, scripts list.List[*pac.Script]) *pac.Script { - return computeRequiredBy(*p, scripts) - }).SortBy(func(s1, s2 *pac.Script) bool { + loadedPacscripts, err := parsePacscriptFiles(pkgList) + if err != nil { + return errorx.Decorate(err, "failed to parse pacscripts") + } + + for _, script := range loadedPacscripts { + computeRequiredBy(script, loadedPacscripts) + } + + array.SortBy(loadedPacscripts, func(s1, s2 *pac.Script) bool { return s1.Name < s2.Name }) + if err := setLastUpdatedAt(loadedPacscripts); err != nil { + return errorx.Decorate(err, "failed to set last updated at") + } + pacstore.Update(loadedPacscripts) - log.Info("Successfully parsed %v (%v / %v) packages", types.Percent(float64(len(loadedPacscripts))/float64(pkgList.Len())), loadedPacscripts.Len(), pkgList.Len()) - log.Notify("Successfully parsed %v (%v / %v) packages", types.Percent(float64(len(loadedPacscripts))/float64(pkgList.Len())), loadedPacscripts.Len(), pkgList.Len()) + log.Info("successfully parsed %v (%v / %v) packages", types.Percent(float64(len(loadedPacscripts))/float64(len(pkgList))), len(loadedPacscripts), len(pkgList)) return nil } -func readKnownPacscriptNames() (list.List[string], error) { +func readKnownPacscriptNames() ([]string, error) { pkglistPath := path.Join(config.GitClonePath, PACKAGE_LIST_FILE_NAME) bytes, err := os.ReadFile(pkglistPath) if err != nil { @@ -59,39 +71,37 @@ func readKnownPacscriptNames() (list.List[string], error) { return names, nil } -func parsePacscriptFiles(names []string) []*pac.Script { +func parsePacscriptFiles(names []string) ([]*pac.Script, error) { if err := pacsh.CreateTempDirectory(config.TempDir); err != nil { - log.Error("Failed to create temporary directory. %v", err) - return nil + return nil, errorx.Decorate(err, "failed to create temporary directory") } - log.Info("Parsing pacscripts...") + log.Info("parsing pacscripts...") outChan := batch.Run(int(config.MaxOpenFiles), names, func(pacName string) (*pac.Script, error) { out, err := ParsePacscriptFile(config.GitClonePath, pacName) if err != nil { - log.Warn("Failed to parse %v. err: %v", pacName, err) + log.Warn("failed to parse %v. err: %v", pacName, err) } if config.Repology.Enabled { if err := repology.Sync(&out); err != nil { - log.Debug("Failed to sync %v with repology. Error: %v", pacName, err) + log.Debug("failed to sync %v with repology. Error: %v", pacName, err) } } return &out, err }) - return channels.ToSlice(outChan) + return channels.ToSlice(outChan), nil } func readPacscriptFile(rootDir, name string) (scriptBytes []byte, fileName string, err error) { - fileName = fmt.Sprintf("%v.pacscript", name) + fileName = fmt.Sprintf("%s.%s", name, consts.PACSCRIPT_FILE_EXTENSION) scriptPath := path.Join(rootDir, "packages", name, fileName) scriptBytes, err = os.ReadFile(scriptPath) if err != nil { - log.Error("Failed to read package pacsh '%v'\n%v", scriptPath, err) - return + return nil, "", errorx.Decorate(err, "failed to read file '%v'", scriptPath) } return scriptBytes, fileName, nil @@ -100,19 +110,19 @@ func readPacscriptFile(rootDir, name string) (scriptBytes []byte, fileName strin func ParsePacscriptFile(programsDirPath, name string) (pac.Script, error) { pacshell, filename, err := readPacscriptFile(programsDirPath, name) if err != nil { - return pac.Script{}, err + return pac.Script{}, errorx.Decorate(err, "failed to read pacscript '%v'", name) } pacshell = buildCustomFormatScript(pacshell) stdout, err := pacsh.ExecBash(config.TempDir, filename, pacshell) if err != nil { - return pac.Script{}, err + return pac.Script{}, errorx.Decorate(err, "failed to execute pacscript '%v'", name) } pacscript, err := pacsh.ParsePacOutput(stdout) if err != nil { - return pac.Script{}, fmt.Errorf("failed to parse pacscript %v. err: %v", name, err) + return pac.Script{}, errorx.Decorate(err, "failed to parse pacscript '%v'", name) } return pacscript, nil diff --git a/server/types/pac/parser/scheduler.go b/server/types/pac/parser/scheduler.go index 9700692d..9edd7925 100644 --- a/server/types/pac/parser/scheduler.go +++ b/server/types/pac/parser/scheduler.go @@ -7,14 +7,21 @@ import ( ) func ScheduleRefresh(every time.Duration) { - go func() { - for { - err := ParseAll() - if err != nil { - log.Error("Failed to parse pacscripts: %v", err) - } + go refresh(every) +} + +func refresh(every time.Duration) { + for { + err := ParseAll() + if err != nil { + log.Error("parse error: %+v", err) - time.Sleep(every) + retryIn := time.Second * 30 + log.Info("retrying in %v", retryIn) + time.Sleep(retryIn) + continue } - }() + + time.Sleep(every) + } } diff --git a/server/types/pac/parser/search.go b/server/types/pac/parser/search.go index b06dab52..179091af 100644 --- a/server/types/pac/parser/search.go +++ b/server/types/pac/parser/search.go @@ -3,7 +3,7 @@ package parser import ( "strings" - "pacstall.dev/webserver/types/list" + "pacstall.dev/webserver/types/array" "pacstall.dev/webserver/types/pac" ) @@ -56,38 +56,38 @@ func SortPackages(packages []*pac.Script, sortType, sortBy string) []*pac.Script return packages } - out := list.From(packages) + out := array.Clone(packages) switch sortBy { case "name": if strings.Compare(sortType, "asc") == 0 { - out = out.SortBy(func(a, b *pac.Script) bool { + out = array.SortBy(out, func(a, b *pac.Script) bool { return strings.Compare(a.Name, b.Name) < 0 }) } else { - out = out.SortBy(func(a, b *pac.Script) bool { + out = array.SortBy(out, func(a, b *pac.Script) bool { return strings.Compare(a.Name, b.Name) > 0 }) } case "maintainer": if strings.Compare(sortType, "asc") == 0 { - out = out.SortBy(func(a, b *pac.Script) bool { + out = array.SortBy(out, func(a, b *pac.Script) bool { return strings.Compare(a.Maintainer, b.Maintainer) < 0 }) } else { - out = out.SortBy(func(a, b *pac.Script) bool { + out = array.SortBy(out, func(a, b *pac.Script) bool { return strings.Compare(a.Maintainer, b.Maintainer) > 0 }) } case "version": if strings.Compare(sortType, "asc") == 0 { - out = out.SortBy(func(a, b *pac.Script) bool { + out = array.SortBy(out, func(a, b *pac.Script) bool { return strings.Compare(a.Version, b.Version) < 0 }) } else { - out = out.SortBy(func(a, b *pac.Script) bool { + out = array.SortBy(out, func(a, b *pac.Script) bool { return strings.Compare(a.Version, b.Version) > 0 }) } diff --git a/server/types/pac/script.go b/server/types/pac/script.go index 7e233014..50f93d75 100644 --- a/server/types/pac/script.go +++ b/server/types/pac/script.go @@ -1,5 +1,7 @@ package pac +import "time" + type updateStatus struct { Unknown UpdateStatusValue Latest UpdateStatusValue @@ -19,25 +21,26 @@ var UpdateStatus = updateStatus{ type UpdateStatusValue = int type Script struct { - Name string `json:"name"` - PrettyName string `json:"prettyName"` - Version string `json:"version"` - LatestVersion *string `json:"latestVersion"` - PackageName string `json:"packageName"` - Maintainer string `json:"maintainer"` - Description string `json:"description"` - URL string `json:"url"` - RuntimeDependencies []string `json:"runtimeDependencies"` - BuildDependencies []string `json:"buildDependencies"` - OptionalDependencies []string `json:"optionalDependencies"` - Breaks []string `json:"breaks"` - Gives string `json:"gives"` - Replace []string `json:"replace"` - Hash *string `json:"hash"` - PPA []string `json:"ppa"` - PacstallDependencies []string `json:"pacstallDependencies"` - Patch []string `json:"patch"` - Repology []string `json:"repology"` - RequiredBy []string `json:"requiredBy"` - UpdateStatus int `json:"updateStatus"` // enum UpdateStatus + Name string `json:"name"` + PrettyName string `json:"prettyName"` + Version string `json:"version"` + LatestVersion *string `json:"latestVersion"` + PackageName string `json:"packageName"` + Maintainer string `json:"maintainer"` + Description string `json:"description"` + URL string `json:"url"` + RuntimeDependencies []string `json:"runtimeDependencies"` + BuildDependencies []string `json:"buildDependencies"` + OptionalDependencies []string `json:"optionalDependencies"` + Breaks []string `json:"breaks"` + Gives string `json:"gives"` + Replace []string `json:"replace"` + Hash *string `json:"hash"` + PPA []string `json:"ppa"` + PacstallDependencies []string `json:"pacstallDependencies"` + Patch []string `json:"patch"` + Repology []string `json:"repology"` + RequiredBy []string `json:"requiredBy"` + LastUpdatedAt time.Time `json:"lastUpdatedAt"` + UpdateStatus int `json:"updateStatus"` // enum UpdateStatus }