Git Product home page Git Product logo

fastrsyncnet's People

Contributors

bretkoppel avatar darosenberg avatar grzegorzblok avatar kzu avatar paulstovell avatar plootie avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

fastrsyncnet's Issues

Can I use it from powershell?

Goal is copy only delta file from powershell remoting and apply it on remote PC's basic file to create a new file.

Delta size is very big

Hi

I am using FastRsyncNet . Its working perfectly.

But I have noticed one problem that is the delta file size is very big almost same as as my main file size for example Screenshot_2016-05-18-19-09-05_com.example.premat.workpack_framed.png =1218 KB and delta size is :1218 KB

Can You please help me with the "big delta file size issue"

.

The implementation code is attached below

I am using it like this;

string sourcefile = @"D:\TestSync\Screenshot_2016-05-18-19-09-05_com.example.premat.workpack_framed.png";
string signaturefile = @"D:\TestSync\signature";
string Destinationfile = @"\SERVER2008\Share\TestSync1\Screenshot_2016-05-18-19-09-05_com.example.premat.workpack_framed.png";
string Deltafile = @"\SERVER2008\Share\TestSync1\delta";
IProgress< ProgressReport> ProgressReport= null ;

        var signatureBuilder = new SignatureBuilder();
        using (var basisStream = new FileStream(Destinationfile, FileMode.Open, FileAccess.Read, FileShare.Read))
        using (var signatureStream = new FileStream(signaturefile, FileMode.Create, FileAccess.Write, FileShare.Read))
        {
            signatureBuilder.Build(basisStream, new SignatureWriter(signatureStream));
        }


        //var delta = new DeltaBuilder();
        //signatureBuilder.ProgressReport = new ConsoleProgressReporter();
        //using (var newFileStream = new FileStream(newFilePath, FileMode.Open, FileAccess.Read, FileShare.Read))
        //using (var signatureStream = new FileStream(signatureFilePath, FileMode.Open, FileAccess.Read, FileShare.Read))
        //using (var deltaStream = new FileStream(deltaFilePath, FileMode.Create, FileAccess.Write, FileShare.Read))
        //{
        //    delta.BuildDelta(newFileStream, new SignatureReader(signatureStream, delta.ProgressReporter), new AggregateCopyOperationsDecorator(new BinaryDeltaWriter(deltaStream)));
        //}
        var delta = new DeltaBuilder();
        
        using (var newFileStream = new FileStream(sourcefile, FileMode.Open, FileAccess.Read, FileShare.Read))
        using (var signatureStream = new FileStream(signaturefile, FileMode.Open, FileAccess.Read, FileShare.Read))
        using (var deltaStream = new FileStream(Deltafile, FileMode.Create, FileAccess.Write, FileShare.Read))
        {
            delta.BuildDelta(newFileStream, new SignatureReader(signatureStream, delta.ProgressReport), new AggregateCopyOperationsDecorator(new BinaryDeltaWriter(deltaStream)));
        }

        var deltaapp = new DeltaApplier
        {
            SkipHashCheck = true
        };
        using (var basisStream = new FileStream(Destinationfile, FileMode.Create, FileAccess.ReadWrite, FileShare.Read))
        using (var deltaStream = new FileStream(Deltafile, FileMode.Open, FileAccess.Read, FileShare.Read))
        using (var newFileStream = new FileStream(sourcefile, FileMode.Open, FileAccess.Read, FileShare.Read))
        {
            deltaapp.Apply(newFileStream, new BinaryDeltaReader(deltaStream, ProgressReport), basisStream);
        }

NuGet version 1.1.2

NuGet has a 1.1.2 version, but the GitHub repo only has 1.0.1 and 1.1.1.

Is this just a case of missing tags, or is there more to it?

Add xxHash3 support

Looks like System.IO.Hashing will support xxHash3 - .Net Standard 2, .Net Framework 4.6.2, and .Net 6/7/8.

Keep getting exception on delta patching

Verification of the patched file failed. The MD5 hash of the patch result file, and the file that was used as input for the delta, do not match. This can happen if the basis file changed since the signatures were calculated.

Here is the setup:
Local Machine creates a signature for every file and stores them in temp folder. (The signatures have random names)
Client uploads signature to server with the name of the remote file to check against.
Server creates a delta file from the signature and updated server copy of the file.

Client downloads that delta file and does patch. I get the exception on all 5 files that need updated.

I built a Unity game and compiled it twice one with a minor UI difference for testing.

Complete Code:

class Program
    {

        static ConsoleProgressReporter Progress = new ConsoleProgressReporter();


        static string GameLocation = Path.Combine(Path.Combine(@"C:\Users\baile\Downloads", "GameData"));

        static string TempStore = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "Diffs", "Local");

        static HttpClient Client = new HttpClient() 
        {
            BaseAddress = new Uri("http://localhost:5000/api/")
        };

        static async Task Main(string[] args)
        {
            var filesToSig = Directory.EnumerateFiles(Path.Combine(@"C:\Users\baile\Downloads", "GameData"), "*", SearchOption.AllDirectories);
            List<Container> Updates = new List<Container>();

            foreach (var file in filesToSig)
            {
                var sInfo = new FileInfo(file);
                var sigInfo = await GenerateSignature(sInfo);
                Updates.Add(new Container() 
                {
                    Source = sInfo,
                    Signature = sigInfo,
                    Delta = await GetDeltaFromServer(sInfo, sigInfo)
                });
            }


            Console.Clear();
            var updateQuery = Updates.Where(x => x.Delta != null);
            Console.WriteLine($"Updating {updateQuery.Count()} files");

            foreach (var update in updateQuery)
            {
                var result = await UpdateFile(update.Delta, update.Source, update.Signature);
            }
        }

        private static async Task<bool> UpdateFile(FileInfo delta, FileInfo source, FileInfo sig)
        {
            var updatedInfo = new FileInfo(Path.Combine(TempStore, "Updates", source.FullName.Substring(GameLocation.Length + 1)));
            Directory.CreateDirectory(Path.GetDirectoryName(updatedInfo.FullName));
            await RSync.PatchFile(updatedInfo.FullName, sig.FullName, delta.FullName, false, Progress);

            updatedInfo.Refresh();

            return false;
        }

        private static async Task<FileInfo> GetDeltaFromServer(FileInfo source, FileInfo sig)
        {
            try
            {
                var deltaInfo = new FileInfo(Path.Combine(TempStore, "Deltas", Path.GetFileName(source.Name) + ".octodelta"));
                Directory.CreateDirectory(Path.GetDirectoryName(deltaInfo.FullName));
                using (var fs = sig.OpenRead())
                {
                    var content = new MultipartFormDataContent();
                    content.Add(new StreamContent(fs), "FormFile" ,fs.Name);
                    content.Add(new StringContent(source.FullName.Substring(GameLocation.Length+1)), "FileName");
                    content.Add(new StringContent(source.Sha1()), "Sha1");
                    var result = await Client.PostAsync("diff", content);

                    if (result.IsSuccessStatusCode)
                    {
                        using (var saveDelta = new FileStream(deltaInfo.FullName, FileMode.Create, FileAccess.ReadWrite, FileShare.Read))
                        {
                            using (var deltaStream = await result.Content.ReadAsStreamAsync())
                            {
                                await deltaStream.CopyToAsync(saveDelta);
                            }
                        }
                    }
                    else
                    {
                        return null;
                    }

                }
                deltaInfo.Refresh();
                return deltaInfo;
            }
            catch (Exception er)
            {
                return null;
            }
        }

        private static async Task<FileInfo> GenerateSignature(FileInfo sourceFile)
        {
            var sigInfo = new FileInfo(Path.Combine(TempStore, "Signatures", Path.GetFileName(sourceFile.FullName) + ".octosig"));
            Directory.CreateDirectory(Path.GetDirectoryName(sigInfo.FullName));
            await RSync.CalculateSignature(sourceFile.FullName, sigInfo.FullName, Progress);

            sigInfo.Refresh();
            return sigInfo;
        }
    }

    class Container
    {
        public FileInfo Delta { get; set; }
        public FileInfo Source { get; set; }
        public FileInfo Signature { get; set; }
    }
public static class RSync
    {
        public static async Task CalculateSignature(string filePath, string signaturePath, IProgress<ProgressReport> Progress = null)
        {
            var signatureBuilder = new SignatureBuilder() { 
                ProgressReport = Progress
            };

            using (var basisStream = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.Read))
            using (var signatureStream = new FileStream(signaturePath, FileMode.Create, FileAccess.Write, FileShare.Read))
            {
                await signatureBuilder.BuildAsync(basisStream, new SignatureWriter(signatureStream));
            }
        }

        public static async Task CalculateDelta(string updatedFilePath, string signaturePath, string deltaPath, IProgress<ProgressReport> Progress = null)
        {
            var delta = new DeltaBuilder();
            delta.ProgressReport = Progress;
            using (var newFileStream = new FileStream(updatedFilePath, FileMode.Open, FileAccess.Read, FileShare.Read))
            using (var signatureStream = new FileStream(signaturePath, FileMode.Open, FileAccess.Read, FileShare.Read))
            using (var deltaStream = new FileStream(deltaPath, FileMode.Create, FileAccess.Write, FileShare.Read))
            {
                await delta.BuildDeltaAsync(newFileStream, new SignatureReader(signatureStream, delta.ProgressReport), new AggregateCopyOperationsDecorator(new BinaryDeltaWriter(deltaStream)));
            }
        }

        public static async Task PatchFile(string updatedFile, string basisFile, string deltaPath, bool SkipHashCheck = false, IProgress<ProgressReport> Progress = null)
        {
            try
            {
                var delta = new DeltaApplier
                {
                    SkipHashCheck = SkipHashCheck
                };

                using (var basisStream = new FileStream(basisFile, FileMode.Open, FileAccess.Read, FileShare.Read))
                using (var deltaStream = new FileStream(deltaPath, FileMode.Open, FileAccess.Read, FileShare.Read))
                using (var newFileStream = new FileStream(updatedFile, FileMode.Create, FileAccess.ReadWrite, FileShare.Read))
                {
                    await delta.ApplyAsync(basisStream, new BinaryDeltaReader(deltaStream, Progress), newFileStream);
                }
            }
            catch (Exception er)
            {

            }
        }
    }
[Route("api/[controller]")]
    [ApiController]
    public class DiffController : ControllerBase
    {

        private string TempStore = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "Diffs", "Server");
        private string GameLocation = Path.Combine(Path.Combine(@"C:\Users\baile\Downloads", "GameData_Updated"));

        [HttpPost]
        public async Task<IActionResult> GenerateDiff([FromForm] DeltaRequest request)
        {
            var result = await GenerateDiffFile(request.FormFile, request.FileName, request.Sha1);

            if (result != null)
                return new FileStreamResult(result.OpenRead(), "application/octet-stream");
            else return new BadRequestResult();
        }


        private async Task<FileInfo> GenerateDiffFile(IFormFile file, string fileName, string sha)
        {

            try
            {
                var signatureFile = new FileInfo(Path.Combine(TempStore, Path.GetFileNameWithoutExtension(Path.GetRandomFileName()) + ".octosig"));
                var updatedFileName = new FileInfo(Path.Combine(GameLocation, fileName));

                if (updatedFileName.Sha1() == sha)
                    return null;

                Directory.CreateDirectory(Path.GetDirectoryName(signatureFile.FullName));

                using (var fs = new FileStream(signatureFile.FullName, FileMode.Create, FileAccess.Write, FileShare.Read))
                using (var reader = file.OpenReadStream())
                {
                    await reader.CopyToAsync(fs);
                }

                var deltaInfo = new FileInfo(Path.Combine(TempStore, Path.GetFileNameWithoutExtension(Path.GetRandomFileName()) + ".octodelta"));

                Directory.CreateDirectory(Path.GetDirectoryName(deltaInfo.FullName));

                await RSync.CalculateDelta(updatedFileName.FullName, signatureFile.FullName, deltaInfo.FullName);

                deltaInfo.Refresh();
                return deltaInfo;
            }
            catch (Exception)
            {
                return null;
            }
        }
    }

New version of Adler32?

First of all, thanks for a really great library!

I just noticed that Octodiff has recently integrated a new version of Adler32 that, according to the commit, is more standard. They have also marked the old implementation as obsolete.

Should this commit perhaps me cherry-picked into FastRsyncNet as well?

Add way to abort delta generation on matching hash

I notice that FastRsync performs a hash of the entire file whenever it creates a signature or builds a delta, however even if these hashes match each other in both algorithm and value the delta builder will still run over the entire stream.

It would be amazing to have some sort of way to either perform the metadata creation and delta generation separately to allow comparison or have the delta builder skip going over the stream if the hashes match

Applying delta incrementally directly to original blob in Azure blob storage?

We are building a wrapping library around FastRsyncNet that will orchestrate an outer bi-directional sync process, snapshotting of target file during the rsync transfer, along with some timestamp-based change detection to determine whether even a signature comparison in either direction is really necessary (as even generating and transmitting a signature are relatively expensive operations for our scenario). We will make this library open-source for anyone who might find this functionality useful.

The library will abstract the underlying file storage, and provide implementations for file system and Azure Blob Storage. Our intended application of the library will primarily use Azure Blob Storage. In your readme.md you provide an example of generating a signature from, and storing it in, blob storage. That's all good and very straightforward - it's all streams anyway.

Our scenario will also require applying a delta directly to the original blob inside of blob storage. This is because our files will get quite large (several hundred MB) and reading the entire base file from blob storage and writing the entire target blob back to blob storage every time we apply a delta (which typically will only change a few hundred bytes) would be problematic from perf and scalability standpoints, since reading and writing against blob storage is always done over the network. It would also incur significant cost since blob storage is metered by number of access operations.

As far as I can tell from the FastRsyncNet API and implementation, the updated target file is always written in its entirety, to a separate stream. In your assessment, is there any way we can overcome this and end up with an implementation that, using whatever blob storage APIs are available (perhaps not streams), applies a delta more granularly by making incremental modifications to the original blob?

One obvious issue is that the DeltaApplier reads the base stream and writes the target stream simultaneously, so if they represent the same blob things will get real messy real fast. :) But this can easily be overcome by leveraging the blob storage snapshot capability to snapshot the target blob, let the reader read from the snapshot and the writer write to the original blob, and then delete the snapshot after the delta application has finished.

Many thanks in advance for your advice.

complex files are not syncing Properly

I have one more question, I have noticed one thing that is if I using a complex( a png file of 1 mb or a jpg file) file the patching is not working properly. I mean to say that its not creating the new file.

Can You please tell me why is this happening.
If you want I will share you the delta, signature and the image files so that we will able to overcome the issue and make the thing perfect

Hope to hear back from you soon

Generate signature while generating delta

Just say I backup daily from a source to a server, it would be good to have an option to generate the delta with the new signature on the "new file"... This may increase efficiency as there will only be one pass ... and next time it comes to back up the file it knows the last signature already.

If you need more clarification let me know :).
Thanks

Add stream API for signature and delta generation

After going through the design phase of our project, we've realized a somewhat challenging limitation in the way the API surface of FastRsyncNet is designed.

Specifically, the APIs provided for generating signatures and deltas are based on builder/writer classes. This requires that, as a caller, you are able to provide a stream to which the signature or delta should be written, and once you start the operation, you'd better be ready to handle the signature/delta being written to that stream in its entirety.

We are writing a component made for a scenario in which we want to allow clients to ask for a signature/delta and get a stream back, like so:

Stream GetSignature();
Stream GetDelta(Stream signature);

In order to provide such an API from our component, we would basically need to first instantiate a MemoryStream, then ask FastRsyncNet to write the signature to that stream, and finally hand that stream back as a return value to the caller. Obviously this has some serious scalability implications, as we would be buffering the entire signature/delta in memory, which is a server scenario is practically suicide.

To avoid this our design would be forced into some type of awkward "inversion" where we always force the caller to provide their own stream instead:

void WriteSignatureToStream(Stream outputSignatureStream);
void WriteDeltaToStream(Stream signatureStream, Stream outputDeltaStream);

But this would not be practical given our scenario. For our scenario, it would be incredibly useful if FastRsyncNet provided a stream implementation for the generation of signatures/deltas in addition to the builder/writer design:

  • SignatureStream as an alternative to SignatureBuilder and SignatureWriter
  • BinaryDeltaStream as an alternative to DeltaBuilder and BinaryDeltaWriter

These stream implementations would we wrappers around the input file streams (which would be provided as constructor parameters). And these streams would of course be non-seekable piping streams where, for each block of bytes read from the stream, the appropriate number of bytes should be read/buffered from the underlying input stream and transformed into the corresponding signature/delta bytes "on the fly", possibly with an input buffer of reasonable size.

This would allow our component/service to return such a stream to the caller, thus providing the desired API without the aforementioned memory consumption and scalability problems.

Possible to update the patch basis file in-place?

I was wondering if it would be at all possible to modify the basis file directly in place, rather than to create a new file.. the reason that I ask is that the remote file system that I am backing is aware of changes vs new file writes for its backup.

As for things failing mid-process, the software I am writing is aware if of failures from its last backup and will re-push the entire file that failed when the connection is restored.

Great work, Thanks

Any tips on how to deal with Unity resource files compressed with LZ4?

Hello, first of all, thanks for this great tool.

I would like to use it on a game project I'm working on. Specifically, to provide a method to patch changed Unity resource files which are compressed with LZ4. I tested with 2 resource files with 32 KB difference. The output delta file produced by the tool was 51 MB. I tried with lower signature chunk size(minimum) and the output size reduced to 27 MB. It is a great improvement but are there any other settings I could tweak to lower the size of the delta file?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.