Documentation
¶
Overview ¶
Package nextcloud is a client library for NextCloud. It only supports files via Webdav for the moment.
Index ¶
- Constants
- Variables
- func CancelMigration(ctx context.Context, inst *instance.Instance, migrationID string, ...) error
- func DeleteMigrationSource(ctx context.Context, inst *instance.Instance, migrationID string) error
- func EnsureAccount(inst *instance.Instance, ncURL, login, password, userID string) (string, error)
- func FetchUserIDWithCredentials(ctx context.Context, nextcloudURL, username, password string) (string, error)
- func FindNextcloudAccount(inst *instance.Instance) (*couchdb.JSONDoc, error)
- type File
- func (f *File) Clone() couchdb.Doc
- func (f *File) DocType() string
- func (f *File) ID() string
- func (f *File) Included() []jsonapi.Object
- func (f *File) Links() *jsonapi.LinksList
- func (f *File) Relationships() jsonapi.RelationshipMap
- func (f *File) Rev() string
- func (f *File) SetID(id string)
- func (f *File) SetRev(id string)
- type Migration
- func (m *Migration) Clone() couchdb.Doc
- func (m *Migration) DocType() string
- func (m *Migration) ID() string
- func (m *Migration) Included() []jsonapi.Object
- func (m *Migration) IsTerminal() bool
- func (m *Migration) Links() *jsonapi.LinksList
- func (m *Migration) MarkFailed(inst *instance.Instance, cause error) error
- func (m *Migration) Relationships() jsonapi.RelationshipMap
- func (m *Migration) Rev() string
- func (m *Migration) SetID(id string)
- func (m *Migration) SetRev(rev string)
- type MigrationError
- type MigrationProgress
- type NextCloud
- func (nc *NextCloud) Copy(oldPath, newPath string) error
- func (nc *NextCloud) Delete(path string) error
- func (nc *NextCloud) DeleteTrash(path string) error
- func (nc *NextCloud) Download(path string) (*webdav.Download, error)
- func (nc *NextCloud) Downstream(path, dirID string, kind OperationKind, cozyMetadata *vfs.FilesCozyMetadata, ...) (*vfs.FileDoc, error)
- func (nc *NextCloud) EmptyTrash() error
- func (nc *NextCloud) ListFiles(path string) ([]jsonapi.Object, error)
- func (nc *NextCloud) ListTrashed(path string) ([]jsonapi.Object, error)
- func (nc *NextCloud) Mkdir(path string) error
- func (nc *NextCloud) Move(oldPath, newPath string) error
- func (nc *NextCloud) Restore(path string) error
- func (nc *NextCloud) Size(path string) (uint64, error)
- func (nc *NextCloud) Upload(path, mime string, contentLength int64, body io.Reader) error
- func (nc *NextCloud) Upstream(path, from string, kind OperationKind) error
- type OCSPayload
- type OperationKind
- type SkippedFile
- type TriggerMigrationRequest
Constants ¶
const ( MigrationStatusPending = "pending" MigrationStatusRunning = "running" MigrationStatusCompleted = "completed" MigrationStatusFailed = "failed" MigrationStatusCanceled = "canceled" )
const DefaultMigrationTargetDir = "/Nextcloud"
Variables ¶
var ( // ErrAccountNotFound is used when the no account can be found with the // given ID. ErrAccountNotFound = errors.New("account not found") // ErrInvalidAccount is used when the account cannot be used to connect to // NextCloud. ErrInvalidAccount = errors.New("invalid NextCloud account") )
var ErrMigrationAlreadyTerminal = errors.New("nextcloud migration already in a terminal state")
ErrMigrationAlreadyTerminal is returned when cancel is requested on a migration that has already reached completed, failed, or canceled. Callers map it to a 409. The migration service's cancel_requested fallback would swallow a stray message, but publishing when there is nothing to stop wastes broker work and pollutes metrics.
ErrMigrationBrokerUnavailable is returned when the RabbitMQ publish fails after the tracking document has been created. The tracking document is marked failed before this error is returned, so retries are not blocked by a stuck pending doc.
var ErrMigrationConflict = errors.New("a nextcloud migration is already in progress")
var ErrMigrationNotDeletable = errors.New("nextcloud migration source can only be deleted after a completed migration")
ErrMigrationNotDeletable gates delete-source to the completed status. Failed and canceled migrations may still hold files on Nextcloud that never reached Cozy, and removing the folder would lose them.
var ErrMigrationNotFound = errors.New("nextcloud migration not found")
ErrMigrationNotFound is returned when the tracking document referenced by a cancel request does not exist. Callers map it to a 404 rather than publishing into the void.
var ErrMigrationSourceAlreadyDeleted = errors.New("nextcloud migration source already deleted")
ErrMigrationSourceAlreadyDeleted is surfaced rather than swallowed so duplicate clicks from the UI show up as bugs instead of silent no-ops.
var ErrNextcloudAccountMissing = errors.New("no nextcloud account configured for this instance")
ErrNextcloudAccountMissing means the single nextcloud account was removed out-of-band between the migration and this cleanup call.
var ErrNextcloudUnreachable = errors.New("nextcloud unreachable")
ErrNextcloudUnreachable wraps any error surfaced while probing the Nextcloud server other than an explicit auth rejection (401/403). The caller should translate it to a 502 Bad Gateway for the HTTP client.
Functions ¶
func CancelMigration ¶
func CancelMigration( ctx context.Context, inst *instance.Instance, migrationID string, rmq rabbitmq.Service, ) error
CancelMigration validates the request and publishes a cancel command. It intentionally does NOT mutate the tracking document: the terminal state transition is owned by the migration service so there is a single writer for it.
The diagnostic logger is pulled from ctx via logger.FromContext so the caller can attach its request-scoped fields (migration_id, etc.) once with logger.WithContext rather than threading an extra parameter.
Error contract, in priority order, so callers can map via errors.Is:
- ErrMigrationNotFound: no tracking doc with this id on the instance.
- ErrMigrationAlreadyTerminal: the migration has already reached a terminal state (completed, failed, or canceled).
- ErrMigrationBrokerUnavailable: the RabbitMQ publish failed. Unlike trigger, the tracking doc is NOT marked failed: a cancel publish failure does not invalidate a migration that is already running. The user retries, or the migration finishes normally.
- any other error: treat as an internal server failure.
func DeleteMigrationSource ¶
func DeleteMigrationSource( ctx context.Context, inst *instance.Instance, migrationID string, ) error
DeleteMigrationSource removes the Nextcloud content ingested by the given migration, then empties the user's trashbin so the migrated bytes stop counting against quota.
SourcePath == "/" enumerates the WebDAV home and deletes each top-level child: Nextcloud refuses DELETE on the home itself with 403, so a single call would fail. An empty SourcePath is treated as "/" to match legacy docs predating source_path persistence.
Error contract (priority order, for errors.Is mapping):
- ErrMigrationNotFound, ErrMigrationNotDeletable, ErrMigrationSourceAlreadyDeleted, ErrNextcloudAccountMissing: state gates.
- webdav.ErrInvalidAuth: stored credentials rejected (401/403).
- ErrNextcloudUnreachable: any other Nextcloud-side failure.
func EnsureAccount ¶
EnsureAccount upserts the single Nextcloud account for the instance and returns its id. If an account already exists, its auth block and webdav_user_id are rewritten with the given values regardless of what they were before — this is the keyed-by-type policy that keeps the Settings UI free of orphans at the cost of multi-account support. The password is encrypted at rest before persistence.
func FetchUserIDWithCredentials ¶
func FetchUserIDWithCredentials(ctx context.Context, nextcloudURL, username, password string) (string, error)
FetchUserIDWithCredentials probes the OCS cloud/user endpoint and returns the user ID, or webdav.ErrInvalidAuth if the credentials are rejected. The logger used for diagnostics is pulled from ctx via logger.FromContext, so callers should attach a request-scoped logger with logger.WithContext before calling.
https://docs.nextcloud.com/server/latest/developer_manual/client_apis/OCS/ocs-api-overview.html
func FindNextcloudAccount ¶
FindNextcloudAccount returns the unique Nextcloud account for the given instance, or (nil, nil) if none exists yet. By design there is at most one account with `account_type: "nextcloud"` per instance: the migration trigger endpoint overwrites the existing account on every call so a retry with a corrected password or a different login does not leave orphaned docs the Settings UI cannot surface.
If multiple legacy nextcloud accounts exist (left over from when the konnector flow kept one account per (url, login) pair), the function returns the first one scanned. Newly-triggered migrations will update that single doc; the rest stay in the database untouched until a real cleanup is wired up. A linear scan is cheaper than maintaining a Mango index because the per-instance account count is tiny.
Types ¶
type File ¶
type File struct {
DocID string `json:"id,omitempty"`
Type string `json:"type"`
Name string `json:"name"`
Path string `json:"path"`
Size uint64 `json:"size,omitempty"`
Mime string `json:"mime,omitempty"`
Class string `json:"class,omitempty"`
UpdatedAt string `json:"updated_at,omitempty"`
ETag string `json:"etag,omitempty"`
RestorePath string `json:"restore_path,omitempty"`
// contains filtered or unexported fields
}
func (*File) Relationships ¶
func (f *File) Relationships() jsonapi.RelationshipMap
type Migration ¶
type Migration struct {
DocID string `json:"_id,omitempty"`
DocRev string `json:"_rev,omitempty"`
Status string `json:"status"`
TargetDir string `json:"target_dir"`
SourcePath string `json:"source_path,omitempty"`
Progress MigrationProgress `json:"progress"`
Errors []MigrationError `json:"errors"`
Skipped []SkippedFile `json:"skipped"`
StartedAt *time.Time `json:"started_at"`
FinishedAt *time.Time `json:"finished_at"`
CancelRequested bool `json:"cancel_requested,omitempty"`
CanceledAt *time.Time `json:"canceled_at,omitempty"`
SourceDeletedAt *time.Time `json:"source_deleted_at,omitempty"`
}
Migration is the io.cozy.nextcloud.migrations tracking document.
The schema (especially the nested Progress object) is the contract with twake-migration-nextcloud. Flat counters would crash the service's progress reducer because it spreads doc.progress and adds to its fields.
CancelRequested and CanceledAt are written by the migration service; the Stack round-trips them without modification. SourcePath and SourceDeletedAt are written only by the Stack (trigger and delete-source respectively); SourceDeletedAt is never cleared once stamped.
func FindActiveMigration ¶
FindActiveMigration returns the first pending or running migration, or (nil, nil) if none. A missing doctype database or index is treated as "no active migration" so the first call on a fresh instance succeeds.
func NewPendingMigration ¶
NewPendingMigration returns a fresh Migration document in the pending state. Errors and Skipped are explicit empty slices so the JSON serialization produces "[]" rather than "null": the migration service consumes them as arrays and would crash on null. sourcePath must be the normalized Nextcloud-side path from the trigger request.
func TriggerMigration ¶
func TriggerMigration( ctx context.Context, inst *instance.Instance, req TriggerMigrationRequest, rmq rabbitmq.Service, log logger.Logger, ) (*Migration, error)
TriggerMigration is the single entry point for "start a Nextcloud migration for this instance with these credentials". It probes the remote host, serializes itself against concurrent triggers via a per-instance lock, upserts the io.cozy.accounts document, creates a pending tracking document, and publishes the migration command to RabbitMQ. On success it returns the created tracking document.
Error contract, in priority order, so callers can map them to HTTP status codes via errors.Is:
- ErrMigrationConflict: a pending or running migration already exists for this instance.
- webdav.ErrInvalidAuth: the Nextcloud host rejected the supplied credentials with 401/403.
- ErrNextcloudUnreachable: the credentials probe failed for any other reason (DNS, TLS, unexpected status, decode error).
- ErrMigrationBrokerUnavailable: the RabbitMQ publish failed and the tracking document was marked failed.
- any other error: treat as an internal server failure.
func (*Migration) IsTerminal ¶
IsTerminal reports whether the migration has reached a state that the Stack must not try to cancel (completed, failed, or canceled).
func (*Migration) MarkFailed ¶
func (*Migration) Relationships ¶
func (m *Migration) Relationships() jsonapi.RelationshipMap
type MigrationError ¶
type MigrationProgress ¶
type NextCloud ¶
type NextCloud struct {
// contains filtered or unexported fields
}
func (*NextCloud) DeleteTrash ¶
func (*NextCloud) Downstream ¶
func (nc *NextCloud) Downstream(path, dirID string, kind OperationKind, cozyMetadata *vfs.FilesCozyMetadata, failOnConflict bool) (*vfs.FileDoc, error)
func (*NextCloud) EmptyTrash ¶
func (*NextCloud) ListTrashed ¶
func (*NextCloud) Size ¶
Size returns the recursive byte total of the resource at path, as reported by Nextcloud's cached `oc:size` property. Works on the account root (pass an empty string or "/") and on any sub-folder. Equivalent to a single Depth:0 PROPFIND on the server, not a tree walk, so the cost is constant regardless of how many files the folder contains.
type OCSPayload ¶
type OCSPayload struct {
OCS struct {
Data struct {
UserID string `json:"id"`
} `json:"data"`
} `json:"ocs"`
}
type OperationKind ¶
type OperationKind int
const ( MoveOperation OperationKind = iota CopyOperation )
type SkippedFile ¶
type TriggerMigrationRequest ¶
type TriggerMigrationRequest struct {
NextcloudURL string
NextcloudLogin string
NextcloudAppPassword string
SourcePath string
// TargetDir is the absolute Cozy path under which the migration service
// writes imported files. Empty means "use DefaultMigrationTargetDir".
TargetDir string
}
TriggerMigrationRequest carries the user-supplied inputs needed to start a bulk Nextcloud-to-Cozy migration. Field semantics match the HTTP request body on POST /remote/nextcloud/migration.