Full code sample

Following is a full code example of synchronizing custom items between two folders. The sample displays the ISiteSyncSnapIn, the CustomTypeSiteSyncSnapIn.cs class, followed by the Global.asax class:

ISiteSyncSnapIn interface

C#
using System;
using System.Collections.Generic;
using System.Linq;
using Telerik.Sitefinity.SiteSync;

/// <summary>
/// Interface for a snap-in that will handle syncing of an items from a specified type(s)
/// </summary>
public interface ISiteSyncSnapIn
{
   /// <summary>
   /// The supported type for this snapin
   /// </summary>
   string SupportedType { get; set; }

   IQueryable<ISiteSyncLogEntry> GetPendingItems(ISiteSyncExportContext context);

   int GetExportItemsCount(ISiteSyncExportContext context, Guid? siteId = null);

   IEnumerable<ISiteSyncExportTransaction> Export(ISiteSyncExportContext context);

   void Import(ISiteSyncImportTransaction transaction);
}

CustomTypeSiteSyncSnapIn class

C#
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Telerik.Sitefinity.Abstractions;
using Telerik.Sitefinity.Configuration;
using Telerik.Sitefinity.Publishing;
using Telerik.Sitefinity.Services;
using Telerik.Sitefinity.SiteSync;
using Telerik.Sitefinity.SiteSync.Configuration;

namespace SitefinityWebApp
{
   public class CustomTypeSiteSyncSnapIn : Telerik.Sitefinity.SiteSync.ISiteSyncSnapIn
   {
       private readonly ISiteSyncContext context;

       private readonly FileSystemWatcher watcher;

       private readonly string folderPath;

       private ICommonItemLoader commonItemLoader;

       private const string FolderName = "Test";

       public CustomTypeSiteSyncSnapIn(ISiteSyncContext context)
       {
           //Initalization of the File System watcher
           this.watcher = new FileSystemWatcher();

           //Initialization of the folder path that is going to be watched
           this.folderPath = AppDomain.CurrentDomain.BaseDirectory + "App_Data\\Sitefinity\\" + FolderName;

           //Site Sync context
           this.context = context;

           //Subscribe to the watcher events
           this.Subscribe();
       }

       public string SupportedType
       {
           get;
           set;
       }

       public ICommonItemLoader CommonItemLoader
       {
           get
           {
               if (this.commonItemLoader == null)
                   this.commonItemLoader = ObjectFactory.Resolve<ICommonItemLoader>();

               return this.commonItemLoader;
           }
       }

       public IEnumerable<ISiteSyncExportTransaction> Export(ISiteSyncExportContext context)
       {
           var logEntries = this.GetExportItems(context);
           var exportTransactions = new List<ISiteSyncExportTransaction>(logEntries.Count());

           foreach (var logEntry in logEntries)
           {
               var exportTransaction = ObjectFactory.Resolve<ISiteSyncExportTransaction>();
               exportTransaction.Type = logEntry.TypeName;
               exportTransaction.LogEntry = (ISiteSyncLogEntry)logEntry.Clone();

               try
               {
                   var obj = new WrapperObject(null) { Language = logEntry.Language };

                   this.CommonItemLoader.SetCommonProperties(obj, "CustomItem", logEntry.Provider, logEntry.ItemAction, logEntry.Language);

                   obj.AddProperty("Title", logEntry.Title);
                   obj.AddProperty("TypeName", logEntry.TypeName);

                   if (logEntry.TypeName == "File")
                   {
                       obj.AddProperty("BlobStream", File.OpenRead(this.folderPath + "\\" + logEntry.Title));

                       //Because of known issue, if you send BlobStream you should
                       //send more than one item in the transaction where you are sending BlobStream.
                       //That's the reason of the new WrapperObject(null)
                       exportTransaction.Items = new List<WrapperObject>() { obj, new WrapperObject(null) };
                   }
                   else
                   {
                       exportTransaction.Items = new List<WrapperObject>() { obj };
                   }

                   exportTransaction.Headers.Add(SiteSyncHeader.SnapInType, this.SupportedType);
               }
               catch (Exception ex)
               {
                   exportTransaction.Exception = ex;
               }

               if (exportTransaction != null)
               {
                   exportTransactions.Add(exportTransaction);
               }
           }

           return exportTransactions;
       }

       public int GetExportItemsCount(ISiteSyncExportContext context, Guid? siteId = null)
       {
           //Get items to be exported
           var query = this.GetExportItems(context);

           //Filter the query by site id
           if (siteId.HasValue)
           {
               query = query.Where(i => i.Sites.Contains(siteId.Value));
           }

           return query.Count();
       }

       public IQueryable<ISiteSyncLogEntry> GetExportItems(ISiteSyncExportContext exportContext)
       {
           string typeName = this.SupportedType;
           var pendingItems = this.GetPendingItems(exportContext);

           var query = pendingItems;

           var syncingManager = ObjectFactory.Resolve<ISiteSyncExportFilterRegistry>();
           var filter = syncingManager.GetExportFilter(typeName);

           if (filter != null)
           {
               if (exportContext.TypeFilters != null && exportContext.TypeFilters.ContainsKey(typeName))
               {
                   query = filter.FilterLogEntries(query, exportContext.TypeFilters[typeName]);
               }
           }

           return query.OrderBy(i => i.Timestamp);
       }

       public IQueryable<ISiteSyncLogEntry> GetPendingItems(ISiteSyncExportContext context)
       {
           string serverId = context.ServerId;
           var originSiteIds = context.Sites;

           string failingStatus = SyncingStatus.Failing.ToString();

           //Get log entries for certain type
           var query = this.context.GetDataStore().GetLogEntries()
           .Where(e => e.ServerId == serverId &&
                              e.ModifiedSinceLastSync &&
                              (e.Status == null || e.Status != failingStatus)
                              && (e.TypeName == "Folder" || e.TypeName == "File"));

           if (originSiteIds.Any())
           {
               //Filter the log entries by the current site ids
               query = this.FilterBySites(query, originSiteIds);
           }

           return query;
       }

       public void Import(ISiteSyncImportTransaction transaction)
       {
           //Dettach from the Created event
           this.watcher.Created -= new FileSystemEventHandler(OnCreated);

           //Here we write the logic on the target, how we want to process the data on the target
           //In the case we are creating folder if the type is folder, and create 
           //file with the content from the source if the item is file
           foreach (var item in transaction.Items)
           {
               if (item.HasProperty("TypeName"))
               {
                   if (item.GetProperty<string>("TypeName") == "Folder")
                   {
                       var folderTitle = item.GetProperty<string>("Title");
                       var action = item.GetPropertyOrDefault<string>("ItemAction");
                       var pathString = System.IO.Path.Combine(this.folderPath, folderTitle);

                       if (action == "New")
                       {
                           System.IO.Directory.CreateDirectory(pathString);
                       }
                   }
                   else
                   {
                       var fileName = item.GetProperty<string>("Title");
                       var dataStream = item.GetPropertyOrDefault<Stream>("BlobStream");
                       var path = this.folderPath + "\\" + fileName;

                       //Method for upload the stream on the target
                       this.Upload(dataStream, 100, path);
                   }
               }
           }
       }

       private long Upload(Stream source, int bufferSize, string destinationPath)
       {
           var buffer = new byte[bufferSize];
           int bytesRead = buffer.Length;
           long totalSize = 0;

           using (Stream destinaion = File.Create(destinationPath))
           {
               while (true)
               {
                   bytesRead = source.Read(buffer, 0, buffer.Length);
                   if (bytesRead > 0)
                   {
                       destinaion.Write(buffer, 0, bytesRead);
                       totalSize += bytesRead;
                   }
                   else
                   {
                       break;
                   }
               }
           }

           return totalSize;
       }

       private void Subscribe()
       {
           //Watch all files
           this.watcher.Filter = "*.*";

           //crearte Test folder if doesn't exist
           Directory.CreateDirectory(this.folderPath);

           //Path of the directory to watch.
           this.watcher.Path = folderPath;

           //Value indicating whether subdirectories within the specified path should   be monitored.
           this.watcher.IncludeSubdirectories = true;

           //The watcher has another events as Changed, Renamed and Deleted but 
           //for this sample we are showing you only the Created event 
           this.watcher.Created += new FileSystemEventHandler(OnCreated);

           //Enable raising events
           this.watcher.EnableRaisingEvents = true;
       }

       private void OnCreated(object sender, FileSystemEventArgs e)
       {
           FileAttributes attr = File.GetAttributes(e.FullPath);

           //The Name of the item will be the item id, as long as we are not using database for our folders/files
           var itemId = e.Name;

           if (attr.HasFlag(FileAttributes.Directory))
           {
               this.LogEntries("Folder", itemId, e);
           }
           else
           {
               this.LogEntries("File", itemId, e);
           }
       }

       private void LogEntries(string type, string itemId, FileSystemEventArgs e)
       {
           var dataStore = this.context.GetDataStore();
           var targetServers = Config.Get<SiteSyncConfig>().ReceivingServers.Values;

           List<Guid> siteIds;

           siteIds = SystemManager.CurrentContext.GetSites().Select(d => d.Id).ToList();

           //For every target server, first we check if we have already logged log entry for this item 
           //If this is the first time the item is created, we should set the properties that are set below
           //The ItemAction can be Updated and Deleted also, corresponding of what we have done to the item
           //and we should set it corresponding of what we have done to the item
           foreach (var targetServer in targetServers)
           {
               var entry = dataStore.GetLogEntries()
                   .SingleOrDefault(l => l.ItemId == itemId
                       && l.ServerId == targetServer.ServerId);

               if (entry == null)
               {
                   entry = dataStore.CreateLogEntry();

                   entry.TypeName = type;
                   entry.ItemId = itemId;
                   entry.ServerId = targetServer.ServerId;
                   entry.ItemAction = "New";
               }

               entry.ModifiedSinceLastSync = true;
               entry.Sites = siteIds;
               entry.Title = e.Name;

               //If you have updated or deleted the item you should have some variable (action) that is set corresponding of what it was done to the item
               //entry.ItemAction = action;
               //Possible values are: Updated and Deleted

               entry.Timestamp = DateTime.UtcNow;
           }

           //Save the log entry
           dataStore.SaveChanges();
       }

       private IQueryable<ISiteSyncLogEntry> FilterBySites(IQueryable<ISiteSyncLogEntry> source, IEnumerable<Guid> guids)
       {
           return source.Where(s => this.context.GetDataStore().GetLogEntries()
           .Where(x => x.Sites.Any(l => guids.Contains(l)) && s.Id == x.Id).Any());
       }
   }
}

Global.asax class

C#
using System;
using Telerik.Sitefinity.Abstractions;
using Telerik.Sitefinity.Services;
using Telerik.Sitefinity.SiteSync;

namespace SitefinityWebApp
{
   public class Global : System.Web.HttpApplication
   {

       protected void Application_Start(object sender, EventArgs e)
       {
           Bootstrapper.Bootstrapped += Bootstrapper_Bootstrapped;
       }

       private void Bootstrapper_Bootstrapped(object sender, EventArgs e)
       {
           SystemManager.TypeRegistry.Register("CustomItem",
                                      new SitefinityType
                                      {
                                          PluralTitle = "my custom items",
                                          SingularTitle = "my custom item",
                                          Kind = SitefinityTypeKind.Type,
                                          ModuleName = "custom items module",
                                          Parent = null
                                      });

           var typeRegistry = ObjectFactory.Resolve<ISiteSyncTypeRegistry>();
           typeRegistry.Register("CustomItem", new string[] { });

           var siteSyncExportFilterRegistry = ObjectFactory.Resolve<ISiteSyncExportFilterRegistry>();
           siteSyncExportFilterRegistry.RegisterExportFilter("CustomItem", ObjectFactory.Resolve<ISiteSyncExportFilter>());

           var snapInRegistry = ObjectFactory.Resolve<ISiteSyncSnapInRegistry>();
           var context = ObjectFactory.Resolve<ISiteSyncContext>();
           snapInRegistry.Register("CustomItem", new CustomTypeSiteSyncSnapIn(context) { SupportedType = "CustomItem" });
       }
   }
}
Want to learn more?
Enhance your Sitefinity skills by enrolling in free training sessions. Become Sitefinity certified through Progress Education Community to strengthen your professional credentials.