Merge branch 'feature/restore-by-url' into develop

This commit is contained in:
Sergey Linnik
2025-01-27 11:47:45 +03:00
25 changed files with 237 additions and 52 deletions

View File

@ -1,5 +1,6 @@
# Change Log
- restore by url
- ku skin language
- nodejs: support vsdx in diagram editor
- nodejs: support pages, numbers, key formats

View File

@ -308,9 +308,11 @@
var onRequestRestore = function (event) {
var fileName = "<%= Model.FileName %>";
var version = event.data.version;
var url = event.data.url;
var data = {
fileName: fileName,
version: version
version: version,
url: url
};
let xhr = new XMLHttpRequest();

View File

@ -705,6 +705,7 @@ namespace OnlineEditorsExampleMVC
var fileName = (string)body["fileName"];
var version = (int)body["version"];
var url = body.ContainsKey("url") ? (string)body["url"] : null;
var key = ServiceConverter.GenerateRevisionId(DocManagerHelper.CurUserHostAddress()
+ "/" + fileName + "/"
@ -726,9 +727,19 @@ namespace OnlineEditorsExampleMVC
{
File.Copy(changesPath, Path.Combine(currentVersionDir, "changes.json"));
}
File.Copy(Path.Combine(verDir, "prev" + ext), DocManagerHelper.StoragePath(fileName, null), true);
if (url != null)
{
var req = (HttpWebRequest)WebRequest.Create(url);
req.Method = "GET";
var stream = req.GetResponse().GetResponseStream();
var memoryStream = new MemoryStream();
stream.CopyTo(memoryStream);
File.WriteAllBytes(DocManagerHelper.StoragePath(fileName, null), memoryStream.ToArray());
}
else
{
File.Copy(Path.Combine(verDir, "prev" + ext), DocManagerHelper.StoragePath(fileName, null), true);
}
var fileInfo = new FileInfo(DocManagerHelper.StoragePath(fileName, null));
fileInfo.LastWriteTimeUtc = DateTime.UtcNow;

View File

@ -381,9 +381,11 @@
config.events['onRequestRestore'] = function (event) {
var fileName = "<%= FileName %>";
var version = event.data.version;
var url = event.data.url;
var data = {
fileName: fileName,
version: version
version: version,
url: url
};
let xhr = new XMLHttpRequest();

View File

@ -489,6 +489,7 @@ namespace OnlineEditorsExample
var fileName = (string)body["fileName"];
var version = (int)body["version"];
var url = body.ContainsKey("url") ? (string)body["url"] : null;
var lastVersionUri = _Default.FileUri(fileName, true);
var key = ServiceConverter.GenerateRevisionId(_Default.CurUserHostAddress(null)
@ -512,7 +513,19 @@ namespace OnlineEditorsExample
File.Copy(changesPath, Path.Combine(currentVersionDir, "changes.json"));
}
File.Copy(Path.Combine(verDir, "prev" + ext), _Default.StoragePath(fileName, null), true);
if (url != null)
{
var req = (HttpWebRequest)WebRequest.Create(url);
req.Method = "GET";
var stream = req.GetResponse().GetResponseStream();
var memoryStream = new MemoryStream();
stream.CopyTo(memoryStream);
File.WriteAllBytes(_Default.StoragePath(fileName, null), memoryStream.ToArray());
}
else
{
File.Copy(Path.Combine(verDir, "prev" + ext), _Default.StoragePath(fileName, null), true);
}
var fileInfo = new FileInfo(_Default.StoragePath(fileName, null));
fileInfo.LastWriteTimeUtc = DateTime.UtcNow;

View File

@ -21,9 +21,13 @@ import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
"os"
"path"
"time"
"github.com/ONLYOFFICE/document-server-integration/server/models"
"github.com/ONLYOFFICE/document-server-integration/server/shared"
"github.com/ONLYOFFICE/document-server-integration/utils"
)
@ -49,6 +53,12 @@ func (srv *DefaultServerEndpointsHandler) Restore(w http.ResponseWriter, r *http
}
version := fmt.Sprintf("%v", body["version"])
url := ""
v, e := body["url"]
if s, o := v.(string); o && e {
url = s
}
key, err := srv.GenerateFileHash(fileName)
if err != nil {
result["error"] = err.Error()
@ -97,7 +107,20 @@ func (srv *DefaultServerEndpointsHandler) Restore(w http.ResponseWriter, r *http
return
}
verFile, _ := srv.Managers.StorageManager.ReadFile(versionPath)
var verFile []byte
if url != "" {
res, err := http.Get(url)
if err != nil {
result["error"] = err.Error()
shared.SendResponse(w, result)
return
}
defer res.Body.Close()
verFile, _ = io.ReadAll(res.Body)
} else {
verFile, _ = srv.Managers.StorageManager.ReadFile(versionPath)
}
err = srv.Managers.StorageManager.CreateFile(bytes.NewBuffer(verFile), filePath)
if err != nil {
result["error"] = err.Error()
@ -105,6 +128,51 @@ func (srv *DefaultServerEndpointsHandler) Restore(w http.ResponseWriter, r *http
return
}
fileContent, err := os.Open(path.Join(historyPath, fileName+".json"))
if err != nil {
result["error"] = err.Error()
shared.SendResponse(w, result)
return
}
byteResult, _ := io.ReadAll(fileContent)
var history models.History
err = json.Unmarshal(byteResult, &history)
if err != nil {
result["error"] = err.Error()
shared.SendResponse(w, result)
return
}
fileContent.Close()
changes := history.Changes[len(history.Changes)-1]
hist := models.History{
ServerVersion: srv.config.Version,
Changes: []models.Changes{
{
Created: time.Now().UTC().Format("2006-02-1 15:04:05"),
User: models.User{
Id: changes.User.Id,
Username: changes.User.Username,
},
},
},
}
meta, _ := json.MarshalIndent(hist, " ", "")
err = srv.StorageManager.MoveFile(path.Join(historyPath, fileName+".json"), path.Join(newVersionPath, "changes.json"))
if err != nil {
result["error"] = err.Error()
shared.SendResponse(w, result)
return
}
err = srv.StorageManager.CreateFile(bytes.NewReader(meta), path.Join(historyPath, fileName+".json"))
if err != nil {
srv.logger.Errorf("meta creation error: %s", err.Error())
}
result["success"] = true
shared.SendResponse(w, result)
}

View File

@ -123,9 +123,11 @@
var onRequestRestore = function (event) { // the user is trying to restore file version
const version = event.data.version;
const url = event.data.url;
const fileName = config.document.title;
const restoreData = {
version: version,
url: url,
fileName: fileName,
};
let xhr = new XMLHttpRequest();

View File

@ -86,6 +86,7 @@ import java.io.IOException;
import java.io.InputStream;
import java.net.InetAddress;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
@ -678,16 +679,25 @@ public class FileController {
Path bumpedFile = Paths.get(bumpedVersionStringDirectory, previousBasename);
Files.move(sourcePathFile, bumpedFile);
String recoveryVersionStringDirectory = historyManager.versionDir(
historyDirectory,
body.getVersion(),
true
);
Path recoveryPathFile = Paths.get(recoveryVersionStringDirectory, previousBasename);
String recoveryStringFile = recoveryPathFile.toString();
FileInputStream recoveryStream = new FileInputStream(recoveryStringFile);
storageMutator.createFile(sourcePathFile, recoveryStream);
recoveryStream.close();
if (body.getUrl() != null) {
java.net.HttpURLConnection connection = (java.net.HttpURLConnection) new URL(body.getUrl())
.openConnection();
InputStream stream = connection.getInputStream();
storageMutator.createFile(sourcePathFile, stream);
stream.close();
connection.disconnect();
} else {
String recoveryVersionStringDirectory = historyManager.versionDir(
historyDirectory,
body.getVersion(),
true
);
Path recoveryPathFile = Paths.get(recoveryVersionStringDirectory, previousBasename);
String recoveryStringFile = recoveryPathFile.toString();
FileInputStream recoveryStream = new FileInputStream(recoveryStringFile);
storageMutator.createFile(sourcePathFile, recoveryStream);
recoveryStream.close();
}
JSONObject responseBody = new JSONObject();
responseBody.put("error", null);

View File

@ -38,7 +38,7 @@ import org.springframework.stereotype.Component;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
@ -116,8 +116,9 @@ public class DefaultHistoryManager implements HistoryManager {
if (i > 1) { //check if the version number is greater than 1
// if so, get the path to the changes.json file
InputStream changesSteam = new FileInputStream(
versionDir(histDir, i - 1, true) + File.separator + "changes.json");
InputStreamReader changesSteam = new InputStreamReader(new FileInputStream(
versionDir(histDir, i - 1, true) + File.separator + "changes.json"),
"UTF-8");
History changes = objectMapper.readValue(changesSteam, History.class);

View File

@ -30,4 +30,5 @@ import lombok.Setter;
public class Restore {
private String fileName;
private Integer version;
private String url;
}

View File

@ -298,7 +298,8 @@
const query = new URLSearchParams(window.location.search)
const payload = {
fileName: query.get('fileName'),
version: event.data.version
version: event.data.version,
url: event.data.url
}
const request = new XMLHttpRequest()
request.open('PUT', 'restore')

View File

@ -817,6 +817,7 @@ public class IndexServlet extends HttpServlet {
String sourceBasename = (String) body.get("fileName");
Integer version = ((Long) body.get("version")).intValue();
String url = (String) body.get("url");
String userID = (String) body.get("userId");
String sourceStringFile = DocumentManager.storagePath(sourceBasename, null);
@ -872,12 +873,20 @@ public class IndexServlet extends HttpServlet {
Path bumpedFile = Paths.get(bumpedVersionStringDirectory, previousBasename);
Files.move(sourcePathFile, bumpedFile);
String recoveryVersionStringDirectory = DocumentManager.versionDir(historyDirectory, version);
Path recoveryPathFile = Paths.get(recoveryVersionStringDirectory, previousBasename);
String recoveryStringFile = recoveryPathFile.toString();
FileInputStream recoveryStream = new FileInputStream(recoveryStringFile);
DocumentManager.createFile(sourcePathFile, recoveryStream);
recoveryStream.close();
if (url != null) {
java.net.HttpURLConnection connection = (java.net.HttpURLConnection) new URL(url).openConnection();
InputStream stream = connection.getInputStream();
DocumentManager.createFile(sourcePathFile, stream);
stream.close();
connection.disconnect();
} else {
String recoveryVersionStringDirectory = DocumentManager.versionDir(historyDirectory, version);
Path recoveryPathFile = Paths.get(recoveryVersionStringDirectory, previousBasename);
String recoveryStringFile = recoveryPathFile.toString();
FileInputStream recoveryStream = new FileInputStream(recoveryStringFile);
DocumentManager.createFile(sourcePathFile, recoveryStream);
recoveryStream.close();
}
JSONObject responseBody = new JSONObject();
responseBody.put("error", null);

View File

@ -271,6 +271,7 @@
const payload = {
fileName: query.get('fileName'),
version: event.data.version,
url: event.data.url,
userId: config.editorConfig.user.id
}
const request = new XMLHttpRequest()

View File

@ -651,32 +651,47 @@ app.post('/reference', (req, res) => { // define a handler for renaming file
result(data);
});
app.put('/restore', (req, res) => { // define a handler for restore file version
const { fileName } = req.body;
app.put('/restore', async (req, res) => { // define a handler for restore file version
const { fileName, version, url } = req.body;
const result = {};
if (fileName) {
req.DocManager = new DocManager(req, res);
const userAddress = req.DocManager.curUserHostAddress();
const key = req.DocManager.getKey(fileName);
const { version } = req.body;
const filePath = req.DocManager.storagePath(fileName, userAddress);
const historyPath = req.DocManager.historyPath(fileName, userAddress);
const newVersion = req.DocManager.countVersion(historyPath) + 1;
const versionPath = path.join(`${historyPath}`, `${version}`, `prev${fileUtility.getFileExtension(fileName)}`);
const newVersionPath = path.join(`${historyPath}`, `${newVersion}`);
if (fileSystem.existsSync(versionPath)) {
req.DocManager.createDirectory(newVersionPath);
req.DocManager.copyFile(
filePath,
path.join(`${newVersionPath}`, `prev${fileUtility.getFileExtension(fileName)}`),
);
fileSystem.writeFileSync(path.join(`${newVersionPath}`, 'key.txt'), key);
req.DocManager.copyFile(versionPath, filePath);
result.success = true;
if (url) {
const { status, data } = await urllib.request(url, { method: 'GET' });
if (status === 200) {
req.DocManager.createDirectory(newVersionPath);
req.DocManager.copyFile(
filePath,
path.join(`${newVersionPath}`, `prev${fileUtility.getFileExtension(fileName)}`),
);
fileSystem.writeFileSync(path.join(`${newVersionPath}`, 'key.txt'), key);
fileSystem.writeFileSync(filePath, data);
result.success = true;
} else {
result.success = false;
result.error = `Document editing service returned status: ${status}`;
}
} else {
result.success = false;
result.error = 'Version path does not exists';
const versionPath = path.join(`${historyPath}`, `${version}`, `prev${fileUtility.getFileExtension(fileName)}`);
if (fileSystem.existsSync(versionPath)) {
req.DocManager.createDirectory(newVersionPath);
req.DocManager.copyFile(
filePath,
path.join(`${newVersionPath}`, `prev${fileUtility.getFileExtension(fileName)}`),
);
fileSystem.writeFileSync(path.join(`${newVersionPath}`, 'key.txt'), key);
req.DocManager.copyFile(versionPath, filePath);
result.success = true;
} else {
result.success = false;
result.error = 'Version path does not exists';
}
}
} else {
result.success = false;

View File

@ -109,10 +109,12 @@
var onRequestRestore = function (event) { // the user is trying to restore file version
const version = event.data.version;
const url = event.data.url;
const fileName = "<%- file.name %>" || null;
const directUrl = "<%- file.directUrl %>" || null;
const restoreData = {
version: version,
url: url,
fileName: fileName,
};
let xhr = new XMLHttpRequest();

View File

@ -14,6 +14,7 @@ class VersionController extends Controller
$request->validate([
'filename' => 'required|string',
'version' => 'required|int',
'url' => 'nullable|string',
'fileType' => 'required|string',
'userId' => 'required|string',
]);
@ -24,6 +25,7 @@ class VersionController extends Controller
userDirectory: $request->ip(),
fileType: $request->fileType,
version: $request->version,
url: $request->url,
userId: $request->userId,
));

View File

@ -8,6 +8,7 @@ use App\Models\VersionInfo;
use App\Repositories\FileRepository;
use App\Repositories\UserRepository;
use App\Repositories\VersionRepository;
use App\Services\ServerConfig;
use Illuminate\Support\Str;
class ChangeDocumentVersionCommand
@ -16,6 +17,7 @@ class ChangeDocumentVersionCommand
private FileRepository $fileRepository,
private VersionRepository $versionRepository,
private UserRepository $userRepository,
private ServerConfig $serverConfig,
) {}
public function __invoke(ChangeDocumentVersionRequest $request): void
@ -28,7 +30,19 @@ class ChangeDocumentVersionCommand
$currentVersion = $this->versionRepository->current($filePath);
$versionFile = $this->versionRepository->file($filePath, $request->version);
copy($versionFile['path'], $absFilePath);
if ($request->url) {
$data = file_get_contents(
str_replace(
$this->serverConfig->get('url.public'),
$this->serverConfig->get('url.private'),
$request->url),
false,
stream_context_create(['http' => ['timeout' => 5]])
);
file_put_contents($absFilePath, $data, LOCK_EX);
} else {
copy($versionFile['path'], $absFilePath);
}
$versionInfo = VersionInfo::create(
Str::uuid(),

View File

@ -9,6 +9,7 @@ class ChangeDocumentVersionRequest
public string $userDirectory,
public string $fileType,
public string $version,
public ?string $url,
public string $userId,
) {}
}

View File

@ -342,6 +342,7 @@
filename: query.get('fileID'),
fileType: event.data.fileType,
version: event.data.version,
url: event.data.url,
userId: query.get('user') || config.editorConfig.user.id
}
const request = new XMLHttpRequest()

View File

@ -629,6 +629,7 @@ function restore()
$sourceBasename = $body->fileName;
$version = $body->version;
$url = $body->url;
$userID = $body->userId;
$sourceFile = getStoragePath($sourceBasename);
@ -673,12 +674,20 @@ function restore()
$bumpedStringFile = $bumpedFile->string();
copy($sourceFile, $bumpedStringFile);
$recoveryVersionStringDirectory = getVersionDir($historyDirectory, $version);
$recoveryVersionDirectory = new Path($recoveryVersionStringDirectory);
$recoveryFile = $recoveryVersionDirectory->joinPath($previousBasename);
$recoveryStringFile = $recoveryFile->string();
copy($recoveryStringFile, $sourceFile);
if ($url) {
$data = file_get_contents(
$url,
false,
stream_context_create(["http" => ["timeout" => 5]])
);
file_put_contents($sourceFile, $data, LOCK_EX);
} else {
$recoveryVersionStringDirectory = getVersionDir($historyDirectory, $version);
$recoveryVersionDirectory = new Path($recoveryVersionStringDirectory);
$recoveryFile = $recoveryVersionDirectory->joinPath($previousBasename);
$recoveryStringFile = $recoveryFile->string();
copy($recoveryStringFile, $sourceFile);
}
return [
'error' => null,
'success' => true

View File

@ -323,6 +323,7 @@
const payload = {
fileName: query.get('fileID'),
version: event.data.version,
url: event.data.url,
userId: query.get('user') || config.editorConfig.user.id
}
const request = new XMLHttpRequest()

View File

@ -641,6 +641,7 @@ def restore(request: HttpRequest) -> HttpResponse:
body = json.loads(request.body)
source_basename: str = body['fileName']
version: int = body['version']
url = body['url'] if 'url' in body else None
user_id: str = body.get('userId')
source_extension = Path(source_basename).suffix
@ -674,7 +675,11 @@ def restore(request: HttpRequest) -> HttpResponse:
Path(bumped_key_file).write_text(bumped_key, 'utf-8')
Path(bumped_changes_file).write_text(bumped_changes_content, 'utf-8')
copy(source_file, bumped_file)
copy(recovery_file, source_file)
if url is not None:
data = requests.get(url)
Path(source_file).write_bytes(data.content)
else:
copy(recovery_file, source_file)
return HttpResponse()
except Exception as error:

View File

@ -277,6 +277,7 @@
const payload = {
fileName: query.get('filename'),
version: event.data.version,
url: event.data.url,
userId: config.editorConfig.user.id
}
const request = new XMLHttpRequest()

View File

@ -531,6 +531,7 @@ class HomeController < ApplicationController
source_basename = body['fileName']
version = body['version']
url = body['url']
user_id = body['userId']
source_extension = Pathname(source_basename).extname
@ -580,7 +581,17 @@ class HomeController < ApplicationController
bumped_file = bumped_version_directory.join(previous_basename)
FileUtils.cp(source_file, bumped_file)
FileUtils.cp(recovery_file, source_file)
if url.nil?
FileUtils.cp(recovery_file, source_file)
else
uri = URI.parse(url)
http = Net::HTTP.new(uri.host, uri.port)
DocumentHelper.verify_ssl(url, http)
req = Net::HTTP::Get.new(uri.request_uri)
res = http.request(req)
data = res.body
File.binwrite(source_file, data)
end
render(
json: {

View File

@ -300,6 +300,7 @@
const payload = {
fileName: query.get('fileName'),
version: event.data.version,
url: event.data.url,
userId: query.get('userId')
}
const request = new XMLHttpRequest()