ECO Example: Backup Utility
ECO Example: Backup Utility - by Wayne Niddery
Abstract: Created as an ECO learning vehicle for myself, it acts as a practical example demonstrating various abilities of ECO and that, even for such a modest project, ECO is an advantage and should never be considered “overkill”.
As part of learning to use ECO, I decided to create something I need anyway. As an independent developer I work on different projects over time and often at the same time. To protect myself and my clients, I like to back up my work to an offsite location. Now there are certainly lots of existing backup products of various capabilities, and it’s even possible to use WinZip and batch files. But I decided I wanted something easy to use and specific for my needs, and it would be a good exercise. In developing it, I also answered for myself a frequent question I hear: Isn’t ECO overkill for small projects? My answer is no. Despite this being a very modest project, I saved a great deal of time by not having to define relationships in code, manage lists of objects, and design and manage some kind of storage mechanism. So first let’s define the requirements. Note: for the ability to zip up the selected files I used open source library called SharpZipLib available from http://www.icsharpcode.net/OpenSource/SharpZipLib/. This library is distributed under a modified GPL license that allows the library to be used by executables without requiring the executable to be GPL. Let’s start. Create a new ECO Winform Application project. Save the project with the name ECOBackup. We will need persistence in order to store the project definitions we create with our utility, so we will define that first. Due to the nature of this project, simple XML storage will be ideal. In the Project Manager, find the EcoBackupEcoSpace and open it. In the tool palette, under the Enterprise Core Objects node, there is a PersistenceMapperXML component that can be dropped on our ECO Space. Keyboard shortcut: Focus the tool palette with Ctrl-P, then just type “pe” and the palette will display the ECO persistence components. Scroll down to the PersistenceMapperXML and hit Enter. In the Object Inspector for the PersistenceMapperXML, we need to set a file name. This can just be ECOBackup.xml. You can give it a complete path if desired. At this point take a moment to Save All and compile the project. Then open Winform1 from the Project Manager. In the set of components at the bottom will be one called rhRoot. This is a reference handle used to provide access to the model from the form. In the Object Inspector for rhRoot you will see an EcoSpaceType property and you should now be able to click on the dropdown arrow and select the EcoSpace. As mentioned above, this is a tiny model. There are only four classes, and only three of those will be persisted. We’ll start by defining the necessary attributes and associations to get us started, and we’ll add some extra bits to it as needed. Click on the Model View tab of the project manager and expand the ECOBackup node. There you should see a “Package_1”, open this to get to the model surface. From the tool palette, drag an ECO class on to the diagram and name it Project. This class will represent a single backup project, so we’ll define attributes needed at that level. The attributes that initially need to be added are a Name for the project, and what name to give the backup file being made. Since the goal is to be able to backup somewhere remotely, we will also need information about the destination computer: ServerName, ServerPath, ServerUserID, and ServerPassword. The Project class also needs two public methods: Preview and Backup. Keyboard shortcut: Use Ctrl-W to add attributes and Ctrl-M to add operations to a class. According to our requirements, for a Project, we need to be able to define folders on the computer that we want backed up, and whether the backup should process a folder’s sub-folders. So we’ll add a second class to the project called Folder. Folder only needs two attributes: Path (String) and Recurse (Boolean). In addition to Folder, we need to be able to define file masks to control which files are backed up, so a 3rd class needs to be created which we’ll call FileMask and it will contain just one string attribute named Mask. Before our project can work, we need to define associations between them. For both the Folder and FileMask classes, there can be one to many of them for each Project, therefore we will add an association to each of these. If you create the associations by first clicking on Project and dragging to the other class, then for both, End 1 will be the Project. For the End 2 property then, we want to change the Multiplicity of each to 0..*. Strictly speaking this is enough, but to make the model better, we should recognize that both Folder and FileMask cannot logically exist apart from a Project, therefore they should be marked Composite in the End 2 Aggregation property. This will give the correct visual representation in the model as well. With this much defined in the model, we could go ahead and start defining the user interface. However, thinking ahead a bit, in order for Project to do its work, either Backup or Preview, it will need to process its Folders and FileMasks in order to generate a list of files to be backed up – which means we need something to store that information in - another class. Drop a new class on the diagram and name it FileEntry. Add two attributes, FilePath (string) and FileSize (double). Just like Folder and FileMask, we need to add a one-to-many composite association between Project and FileEntry. An important difference between FileEntry and the other classes in our model is that there is no need to store a FileEntry permanently, it is only needed while Project performs its tasks, therefore we need to mark the Persistence property of FileEntry as transient so that ECO will not bother storing them. Here’s our diagram so far: At this point we are ready to start developing our user interface. We will revisit the model later in order to add some additional details. Go back to Winform1. We’ll start by setting up display for Projects and the ability to create or delete them. Drop a DataGrid and a couple of buttons on the form. For the grid, I found a size of roughly 225 wide by 275 high, and placed at the upper left to be about right. Set the CaptionText of the DataGrid to Projects. Position the two buttons under it. Name the DataGrid to ProjectList and the buttons to AddProject and DeleteProject respectively. Here we’ll add our first ECO ExpressionHandle component. Name it to ProjectsHandle and set its RootHandle to rhRoot in order to connect it to the EcoSpace. The Expression property needs to be set to Project.allInstances, this can just be typed in or you can click on the ellipsis and use the expert to help you – selecting Project from the class node then allInstances from the Ocl Operations node. Now we can connect the DataGrid and buttons. For the DataGrid, set its DataSource property to our new ProjectsHandle. For the buttons you will see a property called “RootHandle on EcoListActions”, set this also to the ProjectsHandle. You can now set the actions for these buttons. Since they will be operating on the list of Projects, find the “EcoListAction on EcoListActions” property and set this to Add and Delete respectively. Eco even changes the button captions for you. Finally, for the Delete button, we want to disable it if there are no projects to delete. This can be done using the property “EnabledOcl on EcoListActions”, set it to self->notEmpty. Because this button is tied to the ProjectsHandle expression handle, that’s what “self” refers to in this OCL statement. Drop one more button for now and place it somewhere below the Add and Delete buttons, name this SaveButton. Find the “EcoAction on EcoGlobalActions” property and set it to UpdateDatabase. While not very pretty yet, you can now compile and run this project and you will be able to add and delete projects and edit them in the grid. You can click the Update DB button to save the project to file, and as you can see, the Delete button will correctly be disabled whenever there are no projects in the list. It’s never too early to start making the UI nicer. I prefer to edit records in individual controls instead of grids. Since we will also need to manage other classes as well, we’ll use a Tab Control to divide up the functionality. Drop one down to the right and set its Dock property to Right, then size it to take up all the space right of the datagrid. Add three tabs and label them “Project”, “Folders”, and “File Masks”, respectively. On the Projects tab, we need an edit control for each of the six current Project attributes and corresponding labels. Now we could hook these edit controls directly to the ProjectsHandle handle and everything would work, however we will soon need to access the current project in code anyway and to do that we will need a CurrencyManager, therefore we’ll do that now. .Net binding is designed to allow any list type component (DataGrid, Listbox) link to many kinds of data containers whether a DataSet, DataView, ArrayList, or an ECO ExpressionHandle. However, the interface that allows this has no concept of a current position in the list. This is done separately via a BindingContext. The ECO CurrencyManager wraps this functionality. Drop a CurrencyManager on the form and name it to “ProjectsCM”. Set its RootHandle to ProjectsHandle and its BindingContext to the ProjectList datagrid. We’ll now be able to use this CurrencyManager to hook up to the currently selected Project. For each of the edit controls, in the DataBindings.Text property, you’ll now be able to select the desired attribute from the ProjectsCM handle. Don’t forget to set the PasswordChar property of the edit control hooked to the UserPassword attribute! Now that we have separate edit controls hooked up, we don’t need the datagrid to be editable, or to show all attributes. The first thing to do is set its ReadOnly flag to True. Now click the ellipsis of the TableStyles property and in the dialog add a TableStyle. Click its GridColumStyles ellipsis. Here you can add a Member, set its HeaderText to Name and select the Name attribute in the MappingName property. You can also set the column Width here. Here’s our form so far: Now let’s add functionality for Folders and File Masks. On the Folders tab we need another datagrid and a couple of buttons to add and delete folders. These can be laid out any way desired, each line of the grid will display a folder path. For the DataGrid, you can turn off the CaptionVisible property since we already know we’re on the Folders tab. To power these controls, we need a new ExpressionHandle. Drop one down and name it “FoldersHandle”. Since we want this handle to present the set of folders linked to the currently selected Project, we need to set this expression’s RootHandle to the CurrencyManager, ProjectsCM. This is very much like creating a master/detail link between two datasets in Win32 Delphi applications. The expression property needs to access the list of folders. Since the FoldersHandle is tied to the current Project, this means that “self”, specified in the Expression property, will refer to that Project object. Because of the association between Project and Folder in the model, there will be a Role available in the Project object called Folders. Thus the Expression property needs to be set to self.Folders. Now the grid can be hooked to the FoldersHandle via its DataSource property. On doing this, the grid will display all three members – the 3rd being the Project this object belongs to. We don’t need the Project to display here. This is solved simply by adding a TableStyle to the grid, adding a GridColumnStyle to that and adding two columns, setting these to the Path and Recurse fields respectively. Set the column widths while there. For the Delete button, set the RootHandle on EcoListActions to the FolderHandle, the EcoListAction on EcoListActions to Delete, and the Enabled on EcoListActions to self->not Empty. For the Add button, we can’t use the EcoAction because we need to prompt the user to enter a folder path, so we need to write our first code here! Label the Add button appropriately, and then double click the Add button’s Click event. The following code will do what we need. After typing in this code, you will notice that LastBrowsePath has not been declared. We can use the Declare Field refactoring feature to do this (from the menu or Shift-Ctrl-D), set the type to string. This variable is not necessary, but simply adds a nice touch – each time the user selects a folder, it will start the browse dialog in the location that was last selected from. The above code presents a folder browser to the user to allow a selection and, if selected, adds a new Folder object to the Project. The try/finally block isn’t really needed here since it will be garbage-collected anyway, but good habits die hard. As you can see, creating a new object in code and adding it as a member of another object’s list is very simple in ECO. The only line that looks weird is the one getting access to the current Project object. We use the CurrencyManager object to get the current element from the ProjectList datagrid. We need to use the AsObject property of that element to get an actual Project object, and finally it needs to be cast as a Project object in order to assign it to our variable. The Folders tab is now functional, the project can be run and tested. The grid is left editable so the Recurse field can be edited to True or False (this could be enhanced to be done with a button or checkbox, to do so would also require another CurrencyManager for the Folders list). Different folders can be assigned to different projects and all is saved properly. The File Masks tab will be similar, but here we’ll just use a Listbox instead of a DataGrid. Again we need two buttons to add and delete File Masks, and we need a TextBox to allow entry of masks. Lay these out as desired, the ListBox does not need to be too wide. Once again we need an ExpressionHandle, drop one down and name this one “MasksHandle”. Again its RootHandle will be ProjectsCM and the expression will be self.FileMasks. For the Listbox, name it “MaskList” and set its Datasource to the MasksHandle. In addition, set its DisplayMember property to the Mask attribute. You can optionally set Sorted to True. Name the textbox to “MaskText”. Using the same pattern as described for the Delete button on the Folder tab, the delete button for File Masks can be hooked up also using the MasksHandle. Again like the Add button on the folders tab, set its caption and then create a Click event for it. The code needed for adding a File Mask is as follows: Most of the above code is merely checking that there is something entered and that it isn’t duplicating an already added mask. Then it simply creates a new FileMask object, sets its Mask attribute, and adds it to the current Project object. At this point we can completely create and configure backup projects. One of our main requirements was to be able to preview a backup as well as execute it and we have endowed our Project class with a Preview method. Now it’s time to implement it. We need a place to display the preview – the list of actual files that would be backed up by the Backup method. We can add another tab to the Tab control and label it Preview. To this, add a DataGrid named PreviewGrid, and a button labeled Preview. Again we need an ExpressionHandle to power this feature, add one and name it FileEntriesHandle. The RootHandle will once again be ProjectsCM, and the Expression will be self.FileEntries. Link the datagrid up by setting the DataSource to FileEntriesHandle. Once again it will show the unneeded Project column in the grid. Get rid of this as before by adding a TableStyle and defining just two columns for the FilePath and FileSize attributes. Create a Click event for the Preview button. The code needed here is: As elsewhere, we need to get access to the current Project. Then we call the Preview method of the Project. Of course we don’t yet have any functionality in the Preview method. We’ll fix this now. Go to the model and right click on the Preview method in the Project class. Here you can click on “Go to Definition”. This takes you to the interface declaration of Preview (actually the attribute line above it). Move down to the Preview method and you can use Ctrl-Shift-DownArrow to move to the implementation section. In this method, you only need type one line: Now of course we need to define the BuildEntryList method. We’re placing this in a separate method because our Backup method is also going to need to call this. Following is the BuildEntryList method. Note that you need to add the System.IO namespace to the unit’s Uses clause. // find requested files for passed directory // recurse folders from passed starting point begin This method makes use of Delphi nested procedures and should make the code easier to understand. The mainline of the method starts by clearing any existing FileEntry objects from the Project. It then loops through the list of Folder classes that have been added by the user for this Project. For each, it calls ProcessMask to find and add any matching files in that folder. It then checks to see if it should recurse sub-folders, and if so, calls RecurseFolders. RecurseFolders is a recursive procedure. It asks the passed DirectoryInfo object for a list of sub-folders. If any are returned, it loops through them, calling ProcessMask and then calling itself in order to continue the recursion. This will continue for any depth of sub-folders. ProcessMask loops through each of the FileMask objects the user has added and, for each, asks the passed DirectoryInfo object for a list of matching files (much easier then the equivalent FindFirst/FindNext/FindClose technique needed in Win32!). For each file found, a FileEntry object is created and added to the Project’s FileEntrys list. Note the FileInfo object returned by the system must be refreshed in order for it to correctly report the size of the file. With this complete, we have some actual functionality, run the application and, with folders and masks defined, go to the Preview tab and click the button! Before we can add the functionality, first we need to make a reference to the needed zip dll. As noted at the beginning of this article, you need to download and install SharpZipLib. We also need to reference the Indy components in order to use its FTP component. Right click on the References node in the Project Manager and click Add Reference. Here in the list you should be able to find the needed Indy assemblies, these are IndyCore, IndyProtocols, and IndySystem. Highlight these 3 and click Add Reference. You can then click the browse button to find the ICSharpCode.SharpZipLib.dll assembly, wherever you installed it. Clicking OK on this dialog will add the four selected references to the project. You will then also be able to add these to the Uses clause. Add a new button on the main form under the ProjectList datagrid and label it Backup. Name it as BackupButton and create a Click event for it. The Click event will contain the following code: p.Backup; finally Here we use a try/finally block to disable the Backup button and re-enable it at completion, just to ensure it cannot be clicked again while a backup is in progress. As usual, we get the current Project object and clear out any FileEntry objects it may be holding. To make the display a little cleaner, we also update the PreviewGrid to show it empty prior to starting the backup. It will be regenerated by the backup process. Finally, we call the Backup method. Find your way to the Project class’ Backup method. In that package unit, add the following references to the Uses clause: ICSharpCode.SharpZipLib.Zip, ICSharpCode.SharpZipLib.Core, IDFTP, and IDComponent. Now we can add the code for the Backup method. if BackupName = ” then memfile := MemoryStream.Create; // now process the entries // upload to a server? memfile.Free; The first thing Backup does is call the BuildEntryList method we created earlier. This gives it an up-to-date list of files to be backed up. Then we make sure there’s a name for the backup file, if one was not specified then we generate one combining the Project name with the current timestamp. We then create a zip file using a memory stream and add each of the target files to it. Finally, if server information has been specified, we attempt to FTP the file to the specified location on the server, otherwise we assume the ServerPath attribute is a local file path and attempt to save the zip file to there. We now have a fully functional backup utility. However, it has one major flaw: there is no feedback of any kind while it’s performing a backup, no way to see its progress. Typically, a backup utility is going to have a progress meter, and usually displays the file names as they are processed, so we’ll do that. We’d like to get feedback from our Project object as it’s performing its work. However, under no circumstances do we want the model to know anything about our user interface, thus it cannot directly access our form. In order to get feedback, we need to observe our Project object. ECO implements the Observer (also know as Publish and Subscribe) pattern in all ECO classes you define and provides the necessary methods to let you set up such subscriptions. You can observe an object or a specific attribute (property) of an object. We will add some new attributes to our Project class specifically to provide information about its progress. The attributes we will add are as follows: BackupSize: Int64 ProgressPoint: Integer ProgressState: string Because these are only intended for use while the Project object is performing a backup, we do not want these attributes to be stored when saved to file. Therefore be sure to set the Persistence property of all three of these to Transient. Now we need to add some code to make use of these attributes. Navigate to the BuildEntryList method and as the first line of its (main) code, add: We will want to report the status of the FTP process, so we need to provide a couple of event handlers to hook to. These will look as follows, use code-completion after entering the code in order to declare them in the class: procedure Project.FTPWork(ASender: TObject; AWorkMode: TWorkMode; In the Backup method, there are a number of additional lines to be added, rather than listing each one, here is the complete backup method again with the new lines in bold. if BackupName = ” then ProgressState := ‘Compressing Files’; // now process the entries BackupSize := zip.Length; memfile.Position := 0; memfile.Free; To take advantage of subscriptions, the observer must implement the ISubscriber interface. ISubscriber is defined in the Borland.Eco.Subscription namespace, so add this to the Uses clause of the form. We can then alter our form to implement the interface by changing the first line of the form definition to be: The ISubscriber interface requires two methods to be implemented, these are IsAlive and Receive. The IDE can help here, just scroll down to the bottom of the TWinform1 class and, in the public section, hit Ctrl-Space, Delphi will show you a list of overrideable methods with IsAlive and Receive at the top and in red. Highlight both of these (the list allows multi-select) and hit return, then Shift-Ctrl-C to create the implementation stubs. The IsAlive method needs only one line – set the Result to True to indicate you want the subscription to continue. Before implementing the Receive method, let’s set up the subscriptions. These will go in the click event of the Backup button, just before the call to Backup: Underlying our Project (and other) objects, is an ECO object that implements the plumbing ECO needs to do its work for us. The AsIObject property gives us access to that underlying object and the Properties property gives access to the plumbing for our individual properties. The ability to subscribe exists at that level. The above lines set up a subscription for our form (self) on two of the attributes we added. When passing self, we are actually passing the ISubscriber interface we’ve implemented, and so ECO will now be able to call our Receive method whenever either of these Project attributes changes. We now need to add a couple of items to our user interface to display progress, a label and a progress gauge. Name them ProgressLabel and ProgressBar respectively. Below is the code for the Receive method. The first line gets the actual attribute that has changed. When the type of the object is a string, it must be ProgressState that has changed, if an integer, then it must be ProgressPoint. If an integer, we check if it is -1, the signal from Project that it is about to start the counting, this gives us an opportunity to initialize our progress gauge. Result := True; You should now be able to compile and run the application and, pressing the backup button on a project, you should see visible feedback. This way of doing things is acceptable for our purposes; however, if your application needs to set up subscriptions to many different classes or attributes, then trying to sort it all out in a single Receive method is going to get ugly very quickly and becomes almost impossible as soon as you need to receive two attributes of the same type. Fortunately, because subscriptions are done using interfaces, we can easily create a separate class to handle individual subscriptions, create as many of these as we need, and use event handlers to get the subscription events into our main form for processing. Because this class will be handy for any project needing such subscriptions, we’ll put it in its own unit. Create a new code-only unit (File | New | Other | Delphi for .Net Projects | New Files | Unit). Save this file as ECOSubscribe.pas. The unit should look like this: interface uses type implementation { TECOSubscriber } function TECOSubscriber.IsAlive: Boolean; function TECOSubscriber.Receive(sender: TObject; e: EventArgs): Boolean; procedure TECOSubscriber.set_OnReceive(const Value: EventHandler); end. This class acts as a relay, it is able to receive calls from a subscribed object and pass that call onto whoever attaches to its OnReceive event. We can create an instance of this class for each subscription we wish to make, and attach a different event handler to each one. We can now improve our implementation in the main form. Add ECOSubscribe to the main form’s Uses clause. In the main form’s private section, add the following declarations: Complete the procedures with Shift-Ctrl-C and add code to the implementations so they look as follows: procedure TWinForm1.ProgressPointChanged(sender: TObject; e: EventArgs);
Defining the Model


Defining the User Interface

var f: Folder;
p: Project;
dlg: FolderBrowserDialog;
begin
dlg := FolderBrowserDialog.Create;
try
dlg.ShowNewFolderButton := False;
dlg.SelectedPath := LastBrowsePath;
if dlg.ShowDialog(Self) = System.Windows.Forms.DialogResult.OK then
begin
f := Folder.Create(EcoSpace);
f.Path := dlg.SelectedPath;
p := ProjectsCM.CurrentElement(ProjectList).AsObject as Project;
p.Folders.Add(f);
LastBrowsePath := dlg.SelectedPath;
end;
finally
dlg.Free;
end;
var fm: FileMask;
p: Project;
begin
if MaskText.Text.Trim = ” then
begin
MessageBox.Show(’Please enter file mask specification’);
Exit;
end;
if MaskList.FindStringExact(MaskText.Text) >= 0 then
begin
MessageBox.Show(’File mask already added’);
Exit;
end;
fm := FileMask.Create(EcoSpace);
fm.Mask := MaskText.Text;
p := ProjectsCM.CurrentElement(ProjectList).AsObject as Project;
p.FileMasks.Add(fm);
end;
Adding the Preview Feature
var p: Project;
begin
p := ProjectsCM.CurrentElement(ProjectList).AsObject as Project;
p.Preview;
end;
BuildEntryList;
procedure Project.BuildEntryList;
var fld: Folder;
di: DirectoryInfo;
procedure ProcessMask(d: DirectoryInfo);
var mask: FileMask;
fi: FileInfo;
files: array of FileInfo;
fe: FileEntry;
begin
// apply each mask in turn
for mask in FileMasks do
begin
files := d.GetFiles(mask.Mask);
for fi in files do
begin
fe := FileEntry.Create(self.AsIObject.ServiceProvider);
fi.Refresh;
fe.FileSize := fi.Length;
fe.FilePath := fi.FullName;
self.FileEntrys.Add(fe);
end;
end;
end;
procedure RecurseFolders(di: DirectoryInfo);
var dirs: array of DirectoryInfo;
di2: DirectoryInfo;
begin
dirs := di.GetDirectories;
for di2 in dirs do
begin
ProcessMask(di2);
RecurseFolders(di2);
end;
end;
FileEntrys.Clear;
for fld in Folders do
begin
di := DirectoryInfo.Create(fld.Path);
ProcessMask(di);
if fld.Recurse then
begin
RecurseFolders(di);
end;
end;
end;
![]()
Adding the Backup Feature
var p: Project;
begin
BackupButton.Enabled := False;
try
p := ProjectsCM.CurrentElement(ProjectList).AsObject as Project;
// clear the file list display
p.FileEntrys.Clear;
if PreviewGrid.Visible then
PreviewGrid.Update;
BackupButton.Enabled := True;
end;
var fe: FileEntry;
zip: ZipOutputStream;
zentry: ZipEntry;
infile: FileStream;
memfile: MemoryStream;
buffer: array of byte;
ftp: TIdFtp;
begin
BuildEntryList;
BackupName := BackupName.Format(’{0}{1}.zip’, self.Name,
DateTime.Now.ToString(’yyyyMMddhhmmss’));
zip := ZipOutputStream.Create(memfile);
for fe in FileEntrys do
begin
zentry := ZipEntry.Create(fe.FilePath);
infile := &File.OpenRead(fe.FilePath);
SetLength(buffer, infile.Length);
infile.Read(buffer, 0, infile.Length);
zip.PutNextEntry(zentry);
zip.Write(buffer, 0, infile.Length);
end;
zip.Finish;
zip.Flush;
memfile.Position := 0;
if ServerName <> ” then
begin // yes
ftp := TIdFtp.Create;
try
ftp.Host := ServerName;
ftp.Username := ServerUserID;
ftp.Password := ServerPassword;
ftp.Connect();
ftp.ChangeDir(ServerPath);
ftp.Put(memfile, BackupName);
finally
ftp.Free;
end;
end else
begin // save locally
infile := &File.OpenWrite(ServerPath + ‘\’ + BackupName);
memfile.WriteTo(infile);
infile.Close;
end;
zip.Close;
end;
Feedback
ProgressState := ‘Preparing File List’;
procedure Project.FTPStatus(ASender: TObject; const AStatus: TIDStatus;
const AStatusText: string);
begin
ProgressState := AStatusText;
end;
AWorkCount: integer);
begin
ProgressState := ‘Writing ‘ + AWorkCount.ToString(’N0′) + ‘ of ‘
+ BackupSize.ToString(’N0′);
end;
procedure Project.Backup;
var fe: FileEntry;
zip: ZipOutputStream;
zentry: ZipEntry;
infile: FileStream;
memfile: MemoryStream;
buffer: array of byte;
ftp: TIdFtp;
begin
BuildEntryList;
BackupName := BackupName.Format(’{0}{1}.zip’, self.Name,
DateTime.Now.ToString(’yyyyMMddhhmmss’));
memfile := MemoryStream.Create;
zip := ZipOutputStream.Create(memfile);
ProgressPoint := -1; // tell subscribers to initialize
ProgressPoint := 0; // start
for fe in FileEntrys do
begin
ProgressState := fe.FilePath;
zentry := ZipEntry.Create(fe.FilePath);
infile := &File.OpenRead(fe.FilePath);
SetLength(buffer, infile.Length);
infile.Read(buffer, 0, infile.Length);
zip.PutNextEntry(zentry);
zip.Write(buffer, 0, infile.Length);
ProgressPoint := ProgressPoint + 1;
end;
zip.Finish;
zip.Flush;
ProgressState := ‘Writing Backup File’;
// upload to a server?
if ServerName <> ” then
begin // yes
ftp := TIdFtp.Create;
try
ftp.Host := ServerName;
ftp.Username := ServerUserID;
ftp.Password := ServerPassword;
ftp.OnStatus := FTPStatus;
ftp.OnWork := FTPWork;
ftp.Connect();
ftp.ChangeDir(ServerPath);
ftp.Put(memfile, BackupName);
finally
ftp.Free;
end;
end else
begin // save locally
infile := &File.OpenWrite(ServerPath + ‘\’ + BackupName);
memfile.WriteTo(infile);
infile.Close;
end;
zip.Close;
ProgressState := ‘Backup complete’;
end;
TWinForm1 = class(System.Windows.Forms.Form, ISubscriber)
p.AsIObject.Properties['ProgressState'].SubscribeToValue(self);
p.AsIObject.Properties['ProgressPoint'].SubscribeToValue(self);
var o: TObject;
p: Project;
begin
o := (ElementChangedEventArgs(e).Element.AsObject);
if o is string then
begin
ProgressLabel.Text := o.ToString;
ProgressLabel.Refresh;
end
else if o is Integer then
begin
if Integer(o) = -1 then
begin
p := ProjectsCM.CurrentElement(ProjectList).AsObject as Project;
ProgressBar.Value := 0;
ProgressBar.Maximum := p.FileEntrys.Count;
end else
if Integer(o) <= ProgressBar.Maximum then
begin
ProgressBar.Value := Integer(o);
end;
ProgressBar.Refresh;
end;
end;
Doing it better…
unit ECOSubscribe;
Borland.Eco.Subscription;
TECOSubscriber = class(TObject, ISubscriber)
private
FOnReceive: EventHandler;
published
public
function IsAlive: Boolean;
function Receive(sender: TObject; e: EventArgs): Boolean;
procedure set_OnReceive(const Value: EventHandler);
property OnReceive: EventHandler add FOnReceive remove set_OnReceive;
end;
begin
Result := True;
end;
begin
if Assigned(FOnReceive) then
FOnReceive(Self, e);
end;
begin
FOnReceive := Value;
end;
FProgressState: TECOSubscriber;
FProgressPoint: TECOSubscriber;
procedure ProgressStateChanged(sender: TObject; e: EventArgs);
procedure ProgressPointChanged(sender: TObject; e: EventArgs);
procedure TWinForm1.ProgressStateChanged(sender: TObject; e: EventArgs);
begin
ProgressLabel.Text :=
(ElementChangedEventArgs(e).Element.AsObject).ToString;
end;
var p: Project;
point: integer;
begin
point := Integer(ElementChangedEventArgs(e).Element.AsObject);
if point = -1 then
begin
p := ProjectsCM.CurrentElement(ProjectList).AsObject as Project;
ProgressBar.Value := 0;
ProgressBar.Maximum := p.FileEntrys.C
Share This | Email this page to a friend
Posted by Wayne Niddery on September 10th, 2006 under General |One Response to “ECO Example: Backup Utility”
Leave a Comment
Server Response from: blog1.codegear.com

RSS Feed
October 5th, 2006 at 3:02 pm
Wayne,
Nice article.
Unfortunately for me, as of today (Oct 5, 2006), all of the image links are broken, making the article a little hard to follow.
Ken White
delphiadpsi@gmail.com