diff --git a/services/collaboration/pkg/connector/fileconnector.go b/services/collaboration/pkg/connector/fileconnector.go index c3f5551880..74617def72 100644 --- a/services/collaboration/pkg/connector/fileconnector.go +++ b/services/collaboration/pkg/connector/fileconnector.go @@ -49,6 +49,10 @@ type PutRelativeResponse struct { HostEdit string } +type RenameResponse struct { + Name string +} + // FileConnectorService is the interface to implement the "Files" // endpoint. Basically lock operations on the file plus the CheckFileInfo. // All operations need a context containing a WOPI context and, optionally, @@ -99,8 +103,8 @@ type FileConnectorService interface { // RenameFile will rename the provided file in the context to the requested // filename. The filename must be UTF8-encoded. // In case of conflict, this method will return the actual lockId in - // the file. - RenameFile(ctx context.Context, lockID, target string) (string, error) + // the file as second return value. + RenameFile(ctx context.Context, lockID, target string) (*RenameResponse, string, error) } // FileConnector implements the "File" endpoint. @@ -626,6 +630,11 @@ func (f *FileConnector) PutRelativeFileSuggested(ctx context.Context, ccs Conten } } + // adjust the wopi file reference to use only the resource id without path + if err := f.adjustWopiReference(ctx, &wopiContext, newLogger); err != nil { + return nil, err + } + wopiSrcURL, err := f.generateWOPISrc(ctx, wopiContext, newLogger) if err != nil { logger.Error().Err(err).Msg("PutRelativeFileSuggested: error generating the WOPISrc parameter") @@ -638,7 +647,9 @@ func (f *FileConnector) PutRelativeFileSuggested(ctx context.Context, ccs Conten Url: wopiSrcURL.String(), } - logger.Debug().Msg("PutRelativeFileSuggested: success") + logger.Debug(). + Str("FinalReference", wopiContext.FileReference.String()). + Msg("PutRelativeFileSuggested: success") return result, nil } @@ -716,6 +727,9 @@ func (f *FileConnector) PutRelativeFileRelative(ctx context.Context, ccs Content } if conError.HttpCodeOut == 409 { + if err := f.adjustWopiReference(ctx, &wopiContext, newLogger); err != nil { + return nil, nil, err + } // if conflict generate a different name and retry. // this should happen only once wopiSrcURL, err2 := f.generateWOPISrc(ctx, wopiContext, newLogger) @@ -753,6 +767,10 @@ func (f *FileConnector) PutRelativeFileRelative(ctx context.Context, ccs Content } } + if err := f.adjustWopiReference(ctx, &wopiContext, newLogger); err != nil { + return nil, nil, err + } + wopiSrcURL, err := f.generateWOPISrc(ctx, wopiContext, newLogger) if err != nil { newLogger.Error().Err(err).Msg("PutRelativeFileRelative: error generating the WOPISrc parameter") @@ -856,12 +874,14 @@ func (f *FileConnector) DeleteFile(ctx context.Context, lockID string) (string, // You can pass a pre-configured zerologger instance through the context that // will be used to log messages. // -// The method will return the actual lockId in case of conflict, otherwise -// the returned lockId will be empty. -func (f *FileConnector) RenameFile(ctx context.Context, lockID, target string) (string, error) { +// The method will return the final target name as first return value (target +// is just a suggestion, so it could have changed) and the actual lockId in +// case of conflict as second return value, otherwise the returned lockId will +// be empty. +func (f *FileConnector) RenameFile(ctx context.Context, lockID, target string) (*RenameResponse, string, error) { wopiContext, err := middleware.WopiContextFromCtx(ctx) if err != nil { - return "", err + return nil, "", err } logger := zerolog.Ctx(ctx).With(). @@ -875,15 +895,20 @@ func (f *FileConnector) RenameFile(ctx context.Context, lockID, target string) ( }) if err != nil { logger.Error().Err(err).Msg("RenameFile: stat failed") - return "", err + return nil, "", err } if oldStatRes.GetStatus().GetCode() != rpcv1beta1.Code_CODE_OK { - logger.Error(). - Str("StatusCode", oldStatRes.GetStatus().GetCode().String()). - Str("StatusMsg", oldStatRes.GetStatus().GetMessage()). - Msg("RenameFile: stat failed with unexpected status") - return "", NewConnectorError(500, oldStatRes.GetStatus().GetCode().String()+" "+oldStatRes.GetStatus().GetMessage()) + if oldStatRes.GetStatus().GetCode() == rpcv1beta1.Code_CODE_NOT_FOUND { + logger.Error().Msg("RenameFile: file not found") + return nil, "", NewConnectorError(404, oldStatRes.GetStatus().GetCode().String()+" "+oldStatRes.GetStatus().GetMessage()) + } else { + logger.Error(). + Str("StatusCode", oldStatRes.GetStatus().GetCode().String()). + Str("StatusMsg", oldStatRes.GetStatus().GetMessage()). + Msg("RenameFile: stat failed with unexpected status") + return nil, "", NewConnectorError(500, oldStatRes.GetStatus().GetCode().String()+" "+oldStatRes.GetStatus().GetMessage()) + } } // the target doesn't include the extension @@ -908,10 +933,10 @@ func (f *FileConnector) RenameFile(ctx context.Context, lockID, target string) ( }) if err != nil { newLogger.Error().Err(err).Msg("RenameFile: move failed") - return "", err + return nil, "", err } if moveRes.GetStatus().GetCode() != rpcv1beta1.Code_CODE_OK { - if moveRes.GetStatus().GetCode() == rpcv1beta1.Code_CODE_LOCKED { + if moveRes.GetStatus().GetCode() == rpcv1beta1.Code_CODE_LOCKED || moveRes.GetStatus().GetCode() == rpcv1beta1.Code_CODE_ABORTED { currentLockID := "" if oldStatRes.GetInfo().GetLock() != nil { currentLockID = oldStatRes.GetInfo().GetLock().GetLockId() @@ -921,7 +946,7 @@ func (f *FileConnector) RenameFile(ctx context.Context, lockID, target string) ( Str("StatusCode", moveRes.GetStatus().GetCode().String()). Str("StatusMsg", moveRes.GetStatus().GetMessage()). Msg("RenameFile: conflict") - return currentLockID, NewConnectorError(409, "file is locked") + return nil, currentLockID, NewConnectorError(409, "file is locked") } if moveRes.GetStatus().GetCode() == rpcv1beta1.Code_CODE_ALREADY_EXISTS { @@ -936,7 +961,7 @@ func (f *FileConnector) RenameFile(ctx context.Context, lockID, target string) ( Str("StatusMsg", moveRes.GetStatus().GetMessage()). Msg("RenameFile: move failed with unexpected status") - return "", NewConnectorError(500, moveRes.GetStatus().GetCode().String()+" "+moveRes.GetStatus().GetMessage()) + return nil, "", NewConnectorError(500, moveRes.GetStatus().GetCode().String()+" "+moveRes.GetStatus().GetMessage()) } } else { // if the put is successful, exit the loop and move on @@ -944,8 +969,11 @@ func (f *FileConnector) RenameFile(ctx context.Context, lockID, target string) ( logger = newLogger } } + logger.Debug().Msg("RenameFile: success") - return "", nil + return &RenameResponse{ + Name: strings.TrimSuffix(path.Base(finalTarget), path.Ext(finalTarget)), // return the final filename without extension + }, "", nil } @@ -1178,3 +1206,29 @@ func (f *FileConnector) generateWOPISrc(ctx context.Context, wopiContext middlew return wopiSrcURL, nil } + +func (f *FileConnector) adjustWopiReference(ctx context.Context, wopiContext *middleware.WopiContext, logger zerolog.Logger) error { + // using resourceid + path won't do for WOPI, we need just the resource if of the new file + // the wopicontext has resourceid + path, which is good enough for the stat request + newStatRes, err := f.gwc.Stat(ctx, &providerv1beta1.StatRequest{ + Ref: &wopiContext.FileReference, + }) + if err != nil { + logger.Error().Err(err).Msg("stat failed") + return err + } + + if newStatRes.GetStatus().GetCode() != rpcv1beta1.Code_CODE_OK { + logger.Error(). + Str("StatusCode", newStatRes.GetStatus().GetCode().String()). + Str("StatusMsg", newStatRes.GetStatus().GetMessage()). + Msg("stat failed with unexpected status") + return NewConnectorError(500, newStatRes.GetStatus().GetCode().String()+" "+newStatRes.GetStatus().GetMessage()) + } + // adjust the reference in the wopi context to use only the resourceid without the path + wopiContext.FileReference = providerv1beta1.Reference{ + ResourceId: newStatRes.GetInfo().GetId(), + } + + return nil +} diff --git a/services/collaboration/pkg/connector/httpadapter.go b/services/collaboration/pkg/connector/httpadapter.go index fdc6acdefd..e90756847c 100644 --- a/services/collaboration/pkg/connector/httpadapter.go +++ b/services/collaboration/pkg/connector/httpadapter.go @@ -364,7 +364,7 @@ func (h *HttpAdapter) RenameFile(w http.ResponseWriter, r *http.Request) { } fileCon := h.con.GetFileConnector() - newLockID, err := fileCon.RenameFile(r.Context(), lockID, utf8Target) + response, newLockID, err := fileCon.RenameFile(r.Context(), lockID, utf8Target) if err != nil { var conError *ConnectorError if errors.As(err, &conError) { @@ -377,6 +377,25 @@ func (h *HttpAdapter) RenameFile(w http.ResponseWriter, r *http.Request) { } return } - // If no error, a HTTP 200 should be sent automatically. - // X-WOPI-Lock header isn't needed on HTTP 200 + + // need to return a JSON response with the name if this is successful + logger := zerolog.Ctx(r.Context()) + + jsonFileInfo, err := json.Marshal(response) + if err != nil { + logger.Error().Err(err).Msg("RenameFile: failed to marshal response") + return + } + + w.Header().Set("Content-Length", strconv.Itoa(len(jsonFileInfo))) + w.WriteHeader(http.StatusOK) + bytes, err := w.Write(jsonFileInfo) + + if err != nil { + logger.Error(). + Err(err). + Int("TotalBytes", len(jsonFileInfo)). + Int("WrittenBytes", bytes). + Msg("RenameFile: failed to write contents in the HTTP response") + } }