Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion frontend/package.json.md5
Original file line number Diff line number Diff line change
@@ -1 +1 @@
5fbf12469d224a93954efecb5886e8a6
be0c7dc3573b2470ab6a8ec4c48845b6
83 changes: 1 addition & 82 deletions frontend/wailsjs/runtime/runtime.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -246,85 +246,4 @@ export function OnFileDropOff() :void
export function CanResolveFilePaths(): boolean;

// Resolves file paths for an array of files
export function ResolveFilePaths(files: File[]): void

// Notification types
export interface NotificationOptions {
id: string;
title: string;
subtitle?: string; // macOS and Linux only
body?: string;
categoryId?: string;
data?: { [key: string]: any };
}

export interface NotificationAction {
id?: string;
title?: string;
destructive?: boolean; // macOS-specific
}

export interface NotificationCategory {
id?: string;
actions?: NotificationAction[];
hasReplyField?: boolean;
replyPlaceholder?: string;
replyButtonTitle?: string;
}

// [InitializeNotifications](https://wails.io/docs/reference/runtime/notification#initializenotifications)
// Initializes the notification service for the application.
// This must be called before sending any notifications.
export function InitializeNotifications(): Promise<void>;

// [CleanupNotifications](https://wails.io/docs/reference/runtime/notification#cleanupnotifications)
// Cleans up notification resources and releases any held connections.
export function CleanupNotifications(): Promise<void>;

// [IsNotificationAvailable](https://wails.io/docs/reference/runtime/notification#isnotificationavailable)
// Checks if notifications are available on the current platform.
export function IsNotificationAvailable(): Promise<boolean>;

// [RequestNotificationAuthorization](https://wails.io/docs/reference/runtime/notification#requestnotificationauthorization)
// Requests notification authorization from the user (macOS only).
export function RequestNotificationAuthorization(): Promise<boolean>;

// [CheckNotificationAuthorization](https://wails.io/docs/reference/runtime/notification#checknotificationauthorization)
// Checks the current notification authorization status (macOS only).
export function CheckNotificationAuthorization(): Promise<boolean>;

// [SendNotification](https://wails.io/docs/reference/runtime/notification#sendnotification)
// Sends a basic notification with the given options.
export function SendNotification(options: NotificationOptions): Promise<void>;

// [SendNotificationWithActions](https://wails.io/docs/reference/runtime/notification#sendnotificationwithactions)
// Sends a notification with action buttons. Requires a registered category.
export function SendNotificationWithActions(options: NotificationOptions): Promise<void>;

// [RegisterNotificationCategory](https://wails.io/docs/reference/runtime/notification#registernotificationcategory)
// Registers a notification category that can be used with SendNotificationWithActions.
export function RegisterNotificationCategory(category: NotificationCategory): Promise<void>;

// [RemoveNotificationCategory](https://wails.io/docs/reference/runtime/notification#removenotificationcategory)
// Removes a previously registered notification category.
export function RemoveNotificationCategory(categoryId: string): Promise<void>;

// [RemoveAllPendingNotifications](https://wails.io/docs/reference/runtime/notification#removeallpendingnotifications)
// Removes all pending notifications from the notification center.
export function RemoveAllPendingNotifications(): Promise<void>;

// [RemovePendingNotification](https://wails.io/docs/reference/runtime/notification#removependingnotification)
// Removes a specific pending notification by its identifier.
export function RemovePendingNotification(identifier: string): Promise<void>;

// [RemoveAllDeliveredNotifications](https://wails.io/docs/reference/runtime/notification#removealldeliverednotifications)
// Removes all delivered notifications from the notification center.
export function RemoveAllDeliveredNotifications(): Promise<void>;

// [RemoveDeliveredNotification](https://wails.io/docs/reference/runtime/notification#removedeliverednotification)
// Removes a specific delivered notification by its identifier.
export function RemoveDeliveredNotification(identifier: string): Promise<void>;

// [RemoveNotification](https://wails.io/docs/reference/runtime/notification#removenotification)
// Removes a notification by its identifier (cross-platform convenience function).
export function RemoveNotification(identifier: string): Promise<void>;
export function ResolveFilePaths(files: File[]): void
60 changes: 0 additions & 60 deletions frontend/wailsjs/runtime/runtime.js
Original file line number Diff line number Diff line change
Expand Up @@ -48,10 +48,6 @@ export function EventsOff(eventName, ...additionalEventNames) {
return window.runtime.EventsOff(eventName, ...additionalEventNames);
}

export function EventsOffAll() {
return window.runtime.EventsOffAll();
}

export function EventsOnce(eventName, callback) {
return EventsOnMultiple(eventName, callback, 1);
}
Expand Down Expand Up @@ -239,60 +235,4 @@ export function CanResolveFilePaths() {

export function ResolveFilePaths(files) {
return window.runtime.ResolveFilePaths(files);
}

export function InitializeNotifications() {
return window.runtime.InitializeNotifications();
}

export function CleanupNotifications() {
return window.runtime.CleanupNotifications();
}

export function IsNotificationAvailable() {
return window.runtime.IsNotificationAvailable();
}

export function RequestNotificationAuthorization() {
return window.runtime.RequestNotificationAuthorization();
}

export function CheckNotificationAuthorization() {
return window.runtime.CheckNotificationAuthorization();
}

export function SendNotification(options) {
return window.runtime.SendNotification(options);
}

export function SendNotificationWithActions(options) {
return window.runtime.SendNotificationWithActions(options);
}

export function RegisterNotificationCategory(category) {
return window.runtime.RegisterNotificationCategory(category);
}

export function RemoveNotificationCategory(categoryId) {
return window.runtime.RemoveNotificationCategory(categoryId);
}

export function RemoveAllPendingNotifications() {
return window.runtime.RemoveAllPendingNotifications();
}

export function RemovePendingNotification(identifier) {
return window.runtime.RemovePendingNotification(identifier);
}

export function RemoveAllDeliveredNotifications() {
return window.runtime.RemoveAllDeliveredNotifications();
}

export function RemoveDeliveredNotification(identifier) {
return window.runtime.RemoveDeliveredNotification(identifier);
}

export function RemoveNotification(identifier) {
return window.runtime.RemoveNotification(identifier);
}
4 changes: 1 addition & 3 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,10 @@ require (
github.com/google/uuid v1.6.0
github.com/minio/minio-go/v7 v7.0.98
github.com/stretchr/testify v1.11.1
github.com/wailsapp/wails/v2 v2.15.0
github.com/wailsapp/wails/v2 v2.10.1
)

require (
git.sr.ht/~jackmordaunt/go-toast/v2 v2.0.3 // indirect
github.com/APTrust/preservation-services v0.0.0-20260303193921-0cefb01f4e00 // indirect
github.com/bep/debounce v1.2.1 // indirect
github.com/blang/semver v3.5.1+incompatible // indirect
Expand All @@ -46,7 +45,6 @@ require (
github.com/goccy/go-json v0.10.5 // indirect
github.com/godbus/dbus/v5 v5.1.0 // indirect
github.com/golang/snappy v1.0.0 // indirect
github.com/gorilla/websocket v1.5.3 // indirect
github.com/jchv/go-winloader v0.0.0-20210711035445-715c2860da7e // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/julienschmidt/httprouter v1.3.0 // indirect
Expand Down
8 changes: 2 additions & 6 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
git.sr.ht/~jackmordaunt/go-toast/v2 v2.0.3 h1:N3IGoHHp9pb6mj1cbXbuaSXV/UMKwmbKLf53nQmtqMA=
git.sr.ht/~jackmordaunt/go-toast/v2 v2.0.3/go.mod h1:QtOLZGz8olr4qH2vWK0QH0w0O4T9fEIjMuWpKUsH7nc=
github.com/APTrust/dart-runner v1.0.5 h1:19J7p8uxUeQvc+SZU5Yx9/NoC2TF3vW5IhpwM1AafbE=
github.com/APTrust/dart-runner v1.0.5/go.mod h1:nTTycLK5i09QSBv+nlrbHLJKdilzucrD4414X1aLlPA=
github.com/APTrust/preservation-services v0.0.0-20260303193921-0cefb01f4e00 h1:IlQVCaLQuY/NTdxZ8dYAy9XTe9sBuO4tNe3vtSKbRPM=
Expand Down Expand Up @@ -74,8 +72,6 @@ github.com/google/pprof v0.0.0-20240409012703-83162a5b38cd h1:gbpYu9NMq8jhDVbvlG
github.com/google/pprof v0.0.0-20240409012703-83162a5b38cd/go.mod h1:kf6iHlnVGwgKolg33glAes7Yg/8iWP8ukqeldJSO7jw=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/jchv/go-winloader v0.0.0-20210711035445-715c2860da7e h1:Q3+PugElBCf4PFpxhErSzU3/PY5sFL5Z6rfv4AbGAck=
Expand Down Expand Up @@ -226,8 +222,8 @@ github.com/wailsapp/go-webview2 v1.0.22 h1:YT61F5lj+GGaat5OB96Aa3b4QA+mybD0Ggq6N
github.com/wailsapp/go-webview2 v1.0.22/go.mod h1:qJmWAmAmaniuKGZPWwne+uor3AHMB5PFhqiK0Bbj8kc=
github.com/wailsapp/mimetype v1.4.1 h1:pQN9ycO7uo4vsUUuPeHEYoUkLVkaRntMnHJxVwYhwHs=
github.com/wailsapp/mimetype v1.4.1/go.mod h1:9aV5k31bBOv5z6u+QP8TltzvNGJPmNJD4XlAL3U+j3o=
github.com/wailsapp/wails/v2 v2.15.0 h1:u7cHK+UesZOlYxyJxfYLteaCPhws6UsZoDdqUejuX6Q=
github.com/wailsapp/wails/v2 v2.15.0/go.mod h1:scxrgwfsv6yR6fE6cCF+Flfl+JeU+SR87T9x4kILJ6M=
github.com/wailsapp/wails/v2 v2.10.1 h1:QWHvWMXII2nI/nXz77gpPG8P3ehl6zKe+u4su5BWIns=
github.com/wailsapp/wails/v2 v2.10.1/go.mod h1:zrebnFV6MQf9kx8HI4iAv63vsR5v67oS7GTEZ7Pz1TY=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
Expand Down
49 changes: 47 additions & 2 deletions scripts/run.sh
Original file line number Diff line number Diff line change
Expand Up @@ -123,11 +123,34 @@ start_sftp() {
local sftp_dir="$PROJECT_ROOT/testdata/sftp"
local image
image=$(sftp_image_name)
# Resolve host file paths and print diagnostics. On Windows (Git Bash / MSYS),
# Docker may need a Windows-style path for -v mounts; attempt conversion
# using `cygpath` when available.
local host_key_pub="$sftp_dir/sftp_user_key.pub"
local host_users_conf="$sftp_dir/users.conf"

case "$(uname -s)" in
MINGW*|MSYS*|CYGWIN*)
if command -v cygpath >/dev/null 2>&1; then
# Convert to Windows path and normalize backslashes to slashes
host_key_pub=$(cygpath -w "$host_key_pub" | sed 's#\\\\#/#g')
host_users_conf=$(cygpath -w "$host_users_conf" | sed 's#\\\\#/#g')
fi
;;
esac

echo "Using SFTP config options from $sftp_dir"
echo " host_key_pub: $host_key_pub"
echo " host_users_conf: $host_users_conf"
ls -l "$sftp_dir" > /dev/null 2>&1 || true
if [ ! -f "$host_users_conf" ]; then
echo "ERROR: users.conf not found at $host_users_conf"
fi

docker rm -f dart-sftp > /dev/null 2>&1 || true
DOCKER_SFTP_ID=$(docker run --name dart-sftp --rm \
-v "$sftp_dir/sftp_user_key.pub:/home/key_user/.ssh/keys/sftp_user_key.pub:ro" \
-v "$sftp_dir/users.conf:/etc/sftp/users.conf:ro" \
-v "$host_key_pub:/home/key_user/.ssh/keys/sftp_user_key.pub:ro" \
-v "$host_users_conf:/etc/sftp/users.conf:ro" \
-p 2222:22 -d "$image")
local exit_code=$?
DOCKER_SFTP_ID=$(echo "$DOCKER_SFTP_ID" | tr -d '\n')
Expand Down Expand Up @@ -187,6 +210,28 @@ run_tests() {
start_minio
start_sftp
go clean -testcache

# Set GOTOOLCHAIN based on the project's go.mod `go` directive. This
# reads the `go` version (e.g. "1.20" or "1.25") and converts it to a
# three-part version if necessary (e.g. "1.20.0"), then prefixes with
# "go" to produce the toolchain name used by `go` toolchains like
# "go1.25.0+auto". This fixes an issue with covdata not being found in
# Go 1.25+ when using the `go test -coverprofile` option.
local go_mod_file="$PROJECT_ROOT/go.mod"
if [ -f "$go_mod_file" ]; then
local go_ver
go_ver=$(awk '/^go[ \t]+/ {print $2; exit}' "$go_mod_file")
if [ -n "$go_ver" ]; then
# Ensure we have three components (major.minor.patch)
local parts
parts=$(echo "$go_ver" | awk -F. '{print NF}')
if [ "$parts" -eq 2 ]; then
go_ver="${go_ver}.0"
fi
export GOTOOLCHAIN="go${go_ver}+auto"
echo "Using GOTOOLCHAIN=$GOTOOLCHAIN"
fi
fi
go test -race -p 1 ./... -coverprofile c.out
local exit_code=$?
if [ $exit_code -eq 0 ]; then
Expand Down
24 changes: 23 additions & 1 deletion server/controllers/job_packaging_controller.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package controllers

import (
"errors"
"fmt"
"net/http"
"os"
Expand Down Expand Up @@ -134,7 +135,28 @@ func JobSavePackaging(c *gin.Context) {
// If direction == "previous", just save and go back.
err := core.ObjSaveWithoutValidation(job)
if err != nil {
AbortWithErrorHTML(c, http.StatusInternalServerError, err)
errorToDisplay := err
// Fixes https://trello.com/c/4akB0rL9.
// There is a reason we prevent duplicate job names, but unless we
// explain it clearly to users, it looks like a bug. This fix makes
// the explanation more clear. Someday, messages like this should go
// into separate HTML templates and/or should be made easier to
// internationalize.
if err.Error() == constants.ErrUniqueConstraint.Error() {
message := fmt.Sprintf(
`DART cannot save this job because there is an existing job
with the same name. DART job names must be unique because it's
too confusing to users to have multiple bags with the same name
and different contents. If you want to add files to the existing
bag, add them to the older job named "%s". If you want to
create a new bag, please rename this job to something other
than "%s".`, job.Name(), job.Name())
errorToDisplay = errors.New(message)
core.Dart.Log.Errorf(
`Caught unique constraint error on job name when attempting to save
job "%s". Reporting more instructive error message to user.`, job.Name())
}
AbortWithErrorHTML(c, http.StatusInternalServerError, errorToDisplay)
return
}
c.Redirect(http.StatusFound, nextPage)
Expand Down
39 changes: 39 additions & 0 deletions server/controllers/job_packaging_controller_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"github.com/APTrust/dart-runner/constants"
"github.com/APTrust/dart-runner/core"
"github.com/APTrust/dart-runner/util"
"github.com/google/uuid"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
Expand Down Expand Up @@ -122,3 +123,41 @@ func TestJobSavePackagingWithNilPackageOp(t *testing.T) {
assert.Equal(t, constants.PackageFormatBagIt, reloadedJob.PackageOp.PackageFormat)
assert.Equal(t, "output.tar", reloadedJob.PackageOp.PackageName)
}

func TestJobSavePackagingDuplicateName(t *testing.T) {
defer core.ClearDartTable()

// Existing job with the target name
job1 := loadTestJob(t)
job1.PackageOp.PackageName = "duplicate-name.tar"
require.NoError(t, core.ObjSave(job1))

// Job we will attempt to save with the duplicate name
job2 := loadTestJob(t)
// Ensure a different UUID so DB treats it as a separate object
id, err := uuid.NewV7()
require.Nil(t, err, "uuid generator blew its own mind")
job2.ID = id.String()
job2.PackageOp.PackageName = "unique-name.tar"
require.NoError(t, core.ObjSave(job2))

values := url.Values{}
values.Set("direction", "previous")
values.Set("BagItSerialization", "application/tar")
values.Set("OutputPath", "/path/to/output.tar")
values.Set("PackageFormat", constants.PackageFormatBagIt)
// Try to change job2's name to the existing job's name
values.Set("PackageName", "duplicate-name.tar")

settings := PostTestSettings{
EndpointUrl: fmt.Sprintf("/jobs/packaging/%s", job2.ID),
Params: values,
ExpectedResponseCode: http.StatusInternalServerError,
ExpectedContent: []string{
"DART cannot save this job because there is an existing job",
"DART job names must be unique",
"duplicate-name.tar",
},
}
DoSimplePostTest(t, settings)
}