using System;
using System.Collections.Generic;
using System.IO;
using System.Net.Mail;
using System.Text;
using System.Text.RegularExpressions;
using System.Windows.Forms;
using EnvDTE;
using EnvDTE80;
using Ionic.Zip;
using Ionic.Zlib;
using Thread=System.Threading.Thread;
namespace GBackupSolution.Core
{
class ClsBackup
{
string SolutionFile { get; set; }
string SolutionFileBackup { get
{
return SolutionFile + ".bk";
}}
string SolutionPath { get; set; }
string SolutionName { get; set; }
string BackupFile { get; set; }
int NumberItem { get; set; }
long TotalLine { get; set; }
long TotalChar { get; set; }
long UncompressSize { get; set; }
bool NeedToReplace { get; set;}
List<ItemFile> ItemList { get; set;}
List<string> DeleteList { get; set;}
private DTE2 applicationObject;
public ClsSetting settings;
public ClsBackup(DTE2 passedDTE)
{
applicationObject = passedDTE;
SolutionFile = applicationObject.Solution.FileName;
SolutionPath = Path.GetDirectoryName(SolutionFile);
SolutionName = Path.GetFileNameWithoutExtension(SolutionFile);
settings = applicationObject.Globals.get_VariableExists("GBackupSolutionData") ? ClsSetting.LoadFromString(applicationObject.Globals["GBackupSolutionData"] as string) : ClsSetting.LoadDefault();
// Create new list
ItemList = new List<ItemFile>();
DeleteList = new List<string>();
NumberItem = 0;
TotalLine = 0;
TotalChar = 0;
UncompressSize = 0;
}
public void StartToBackup()
{
try
{
var thrMainThread = new Thread(DoBackUp);
thrMainThread.Start();
}
catch (Exception ex)
{
MessageBox.Show(ex.ToString(), "ClsBackup contructor error", MessageBoxButtons.OK);
}
// NOTE: for testing
// DoBackUp();
}
private void DoBackUp()
{
// Step1 : Get all items.
if (GetAllItems())
{
// Step2 : Zip all items.
if (DoZip())
{
// Step3 : Send to Gmail.
if (!settings.OtherBackupLocally)
SendToGmail();
else if (settings.OtherPromptDone)
{
applicationObject.StatusBar.Text = "Backup solution to file successfully!";
MessageBox.Show("Backup solution to file successfully!", "Successfully!", MessageBoxButtons.OK,
MessageBoxIcon.Information);
}
}
else
{
applicationObject.StatusBar.Text = "FAILED to backup solution to file!";
MessageBox.Show("FAILED to backup solution to file!", "FAILED!", MessageBoxButtons.OK,
MessageBoxIcon.Information);
}
}
// Do something here
}
private bool GetAllItems()
{
try
{
applicationObject.StatusBar.Text = "Getting all project files...";
foreach (Project project in applicationObject.Solution.Projects)
{
bool bIsExternal = false;
bool bIsBackup = false;
var strProjectPath = "";
var strProjectFolder = "";
if (project.Name.Contains("Miscellaneous"))
continue;
// Detect if this project is external.
if (project.FileName == project.UniqueName || project.FullName == project.UniqueName)
{
bIsExternal = true;
strProjectPath = project.UniqueName;
}
else
strProjectPath = Path.Combine(SolutionPath, project.UniqueName);
// Exit if a project file does not exist
if (!File.Exists(strProjectPath))
continue;
#region Modify Solution file with external project
// If a project is external and the *.sln file is not modified yet
if (bIsExternal && !NeedToReplace)
{
// Make a new copy of Solution file
if (!File.Exists(SolutionFileBackup))
File.Copy(SolutionFile, SolutionFileBackup);
// Change to project path to new one
var rd = new StreamReader(SolutionFileBackup);
var slnContent = rd.ReadToEnd();
rd.Close();
rd.Dispose();
File.Delete(SolutionFileBackup);
slnContent = slnContent.Replace(strProjectPath.Substring(0,
Directory.GetParent(strProjectPath).Parent.FullName.Length + 1), "");
var writer = new StreamWriter(SolutionFileBackup);
writer.Write(slnContent);
writer.Close();
writer.Dispose();
// Mark this solution file need to be replaced
NeedToReplace = true;
}
#endregion
strProjectFolder = Directory.GetParent(strProjectPath).Name;
// Read all content of project file.
var reader = new StreamReader(strProjectPath);
var projContent = reader.ReadToEnd();
#region Add all source code files
// For source code files
var r = new Regex("<Compile Include=\"(.*?)\"", RegexOptions.Singleline);
var ms = r.Matches(projContent);
foreach (Match ma in ms)
AddItemWithHierarchialStructure(ma.Groups[1].Value, strProjectPath, strProjectFolder, false);
#endregion
#region Add all resources
r = new Regex("<Content Include=\"(.*?)\"", RegexOptions.Singleline);
ms = r.Matches(projContent);
foreach (Match ma in ms)
AddItemWithHierarchialStructure(ma.Groups[1].Value, strProjectPath, strProjectFolder, false);
#endregion
#region Add *.resx files and all included resources in it
r = new Regex("<EmbeddedResource Include=\"(.*?)\"", RegexOptions.Singleline);
ms = r.Matches(projContent);
foreach (Match ma in ms)
{
AddItemWithHierarchialStructure(ma.Groups[1].Value, strProjectPath, strProjectFolder, true);
// add included content
var rd = new StreamReader(ItemList[ItemList.Count-1].Path);
var resxContent = rd.ReadToEnd();
// We get all resource path
r = new Regex(@"<data name="".*?"">\r?\n?[ \t]*<value>..\\(.*?);.*?>\r?\n?[ \t]*</data>");
ms = r.Matches(resxContent);
foreach (Match match in ms)
AddItemWithHierarchialStructure(match.Groups[1].Value, strProjectPath, strProjectFolder, true);
rd.Close();
rd.Dispose();
}
#endregion
#region Add all references
// Change project file to map with new reference
// We don't get details info of refs files.
if (settings.BackupIncludeRef)
{
r = new Regex("<HintPath>(.*?)</HintPath>", RegexOptions.Singleline);
ms = r.Matches(projContent);
if (ms.Count > 0)
{
var newContent = projContent;
foreach (Match ma in ms)
{
AddItemWithHierarchialStructure(ma.Groups[1].Value, strProjectPath, strProjectFolder, true);
ItemList[ItemList.Count - 1].HieraryPath = "References";
newContent = newContent.Replace(ma.Groups[1].Value, Path.Combine("References", Path.GetFileName(ma.Groups[1].Value)));
}
if (!File.Exists(strProjectPath + ".bk"))
File.Delete(strProjectPath + ".bk");
var writer = new StreamWriter(strProjectPath + ".bk");
writer.Write(newContent);
writer.Close();
writer.Dispose();
bIsBackup = true;
DeleteList.Add(strProjectPath + ".bk");
}
}
#endregion
// Add *.csproj to Item list.
AddItemWithHierarchialStructure(strProjectPath, strProjectPath, strProjectFolder, false);
ItemList[ItemList.Count - 1].IsBackup = bIsBackup;
reader.Close();
reader.Dispose();
}
return true;
}
catch (Exception ex)
{
MessageBox.Show(ex.ToString());
return false;
}
}
private bool DoZip()
{
try
{
applicationObject.StatusBar.Text = "Compressing to backup file...";
var zip = new ZipFile(Encoding.UTF8) { CompressionLevel = (CompressionLevel)settings.BackupCompresionLvl };
if (!string.IsNullOrEmpty(settings.BackupPass))
{
zip.Password = settings.BackupPass;
zip.Encryption = EncryptionAlgorithm.PkzipWeak;
}
foreach (var itemFile in ItemList)
{
// TO: We need to build a hierarchial structure
var path = Path.Combine(Path.Combine(SolutionName, itemFile.ProjectName), itemFile.HieraryPath);
if (itemFile.IsBackup)
{
zip.AddFile(itemFile.Path + ".bk", path).FileName = Path.Combine(path, Path.GetFileName(itemFile.Path));
}
else
zip.AddFile(itemFile.Path, path);
TotalLine += itemFile.LineCount;
TotalChar += itemFile.CharCount;
UncompressSize += itemFile.Size;
}
if(settings.BackupIncludeUserStt)
{
var suoFile = Path.Combine(SolutionPath, SolutionName + ".suo");
if(Directory.Exists(suoFile))
zip.AddFile(suoFile, SolutionName); // *.suo file
var userFile = Path.Combine(SolutionPath, SolutionName + ".user");
if (Directory.Exists(userFile))
zip.AddFile(userFile, SolutionName); // *.user file
}
// Replace the solution file to orginal
if (NeedToReplace)
zip.AddFile(SolutionFileBackup, SolutionName).FileName = Path.Combine(SolutionName,SolutionName) + ".sln"; // *.sln file
else
zip.AddFile(SolutionFile, SolutionName); // *.sln file
NumberItem = zip.Count;
var pathToSave = GetFormatString(settings.BackupPath);
if (!Directory.Exists(pathToSave))
Directory.CreateDirectory(pathToSave);
var nameToSave = GetFormatString(settings.BackupName);
BackupFile = Path.Combine(pathToSave, nameToSave);
// TODO: Make progress bar to show percent
//zip.SaveProgress += ZipSaveProgress;
zip.Save(BackupFile);
if (NeedToReplace)
{
File.Delete(SolutionFileBackup);
NeedToReplace = false;
}
foreach (var list in DeleteList)
File.Delete(list);
return true;
}
catch (Exception exception)
{
// TODO: handle exception here
MessageBox.Show(exception.ToString());
return false;
}
}
private bool SendToGmail()
{
try
{
applicationObject.StatusBar.Text = "Sending backuped files to Gmail...";
var gUser = settings.GmailUsername.Contains("@gmail.com")
? settings.GmailUsername
: settings.GmailUsername + "@gmail.com";
var toAddress = !settings.GmailToOther ? new MailAddress(settings.GmailUsername) : new MailAddress(settings.Gmail2OtherEmail);
var fromAddress = new MailAddress(gUser);
var mailMessage = new MailMessage(fromAddress, toAddress)
{
Subject = GetFormatString(settings.GmailSubject),
IsBodyHtml = true,
BodyEncoding = Encoding.UTF8
};
if (settings.GmailToOther)
mailMessage.CC.Add(fromAddress);
var sb = new StringBuilder();
sb.Append(
string.Format(
@"<html>
<body>
<table width=`100%`>
<tr><td>
<table style=`width: 800px; font-family: Tahoma; font-size: 80%;` align=`left`>
<tr style=`font-weight: 700;`>
<td colspan=`2` align=`center` style=`font-weight: 700; font-size: medium`>
<a href='http://www.codeproject.com' target=_blank >GBackupSolution Add-in</a>
</td>
</tr>
<tr style=`font-weight: 700;`>
<td><br></td>
</tr>
<tr style=`font-weight: 700;`>
<td style=`width: 150px`>Backup Solution</td>
<td style=`width: 650px; font-weight: 200`>{0}</td>
</tr>
<tr style=`font-weight: 700;`>
<td style=`width: 150px`>Backup Date/Time</td>
<td style=`width: 650px; font-weight: 200`>{1}</td>
</tr>
<tr style=`font-weight: 700;`>
<td style=`width: 150px`>Files in Backup</td>
<td style=`width: 650px; font-weight: 200`><b>{2}</b> files.</td>
</tr>
<tr style=`font-weight: 700;`>
<td style=`width: 150px`>Statictis</td>
<td style=`width: 650px; font-weight: 200`><b>{3}</b> lines and <b>{4}</b> characters.</td>
</tr>
<tr style=`font-weight: 700;`>
<td style=`width: 150px`>Solution Size</td>
<td style=`width: 650px; font-weight: 200`><b>{5}</b> / <b>{6}</b> (compress/uncompress).</td>
</tr>
</table>
</td>
</tr>
<tr>
<td style=`font-family: Tahoma; font-size: 80%; font-weight: 700;`> Item list details:</td>
</tr>
<tr>
<td>".Replace("`", "\""),
SolutionPath,
DateTime.Now.ToString("yyyy-MM-dd hh:mm:ss"),
NumberItem,
TotalLine,
TotalChar,
ByteFormatter.ToString(new FileInfo(BackupFile).Length),
ByteFormatter.ToString(UncompressSize)
));
sb.Append(
@"<table style=`width: 800px; font-size: xx-small; font-family: Tahoma; border: 1px solid #663300; background-color: #FFFFCC` align=`left`>
<tr style=`font-weight: 700; font-size: 120%; border-style: solid; border-width: 1px; background: #CCCFFF`>
<td style=`width: 150px`>File Name</td>
<td style=`width: 400px`>Folder</td>
<td style=`width: 90px`>Modified Date</td>
<td style=`width: 50px` nowrap>Lines</td>
<td style=`width: 60px` nowrap>Chars</td>
</tr>".Replace("`", "\""));
// TODO : make it better
foreach (var itemFile in ItemList)
{
sb.Append(
string.Format(
@"<tr>
<td>{0}</td>
<td>{1}</td>
<td>{2}</td>
<td>{3}</td>
<td>{4}</td>
</tr>", Path.GetFileName(itemFile.Path),
Path.GetDirectoryName(itemFile.Path),
itemFile.Time.ToString("yyyy-MM-dd hh:mm:ss"),
itemFile.LineCount,
itemFile.CharCount));
}
sb.Append(
@"</table></td>
</tr>
<tr>
<td align=`center` stype=`width: 80px; font-size: x-small; font-family: Tahoma;`>
Copyright © 2010 by <a href='http://www.thekok.net' target=_blank >Tiệp Lê</a>. Contact to me via email:<a href=`mailto:lktiep@gmail.com`> lktiep@gmail.com</a>
</td>
</tr>
</body></html>");
mailMessage.Body = sb.ToString();
// TODO: Just for testing
//using(var writer = new StreamWriter(Path.Combine(SolutionPath, "message.html")))
//{
// writer.Write(sb);
//}
//return true;
var mailAttachment = new Attachment(BackupFile);
mailMessage.Attachments.Add(mailAttachment);
// Sending email
var mailClient = new SmtpClient
{
Port = 587,
EnableSsl = true,
UseDefaultCredentials = false,
Credentials =
new System.Net.NetworkCredential(gUser, settings.GmailPassword),
Host = "smtp.gmail.com",
DeliveryMethod = SmtpDeliveryMethod.Network,
Timeout = int.MaxValue
};
//NOTE: Who want to use SendAsync then uncomment below lines
//mailClient.SendCompleted += MClientSendCompleted;
//mailClient.SendAsync(mailMessage, mailAttachment.Name);
mailClient.Send(mailMessage);
applicationObject.StatusBar.Text = "Backup solution successfully to " + BackupFile;
if (settings.OtherPromptDone)
MessageBox.Show("Send backup file to Gmail successfully!", "Successfully!", MessageBoxButtons.OK, MessageBoxIcon.Information);
return true;
}
catch (Exception ex)
{
//MessageBox.Show(ex.ToString());
applicationObject.StatusBar.Text = "Send backup file to Gmail unsuccessfully";
MessageBox.Show(ex.ToString(), "Unsuccessfully!", MessageBoxButtons.OK, MessageBoxIcon.Error);
return false;
}
}
void MClientSendCompleted(object sender, System.ComponentModel.AsyncCompletedEventArgs e)
{
var subject = e.UserState as string;
if (e.Cancelled)
{
// Never mind it.
Console.WriteLine("[{0}] Send canceled.", subject);
}
if (e.Error != null)
{
applicationObject.StatusBar.Text = "Send backup file to Gmail unsuccessfully";
MessageBox.Show("FAILED to send backup file to Gmail.", "Unsuccessfully!", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
else
{
applicationObject.StatusBar.Text = "Backup solution successfully to " + BackupFile;
if (settings.OtherPromptDone)
MessageBox.Show("Send backup file to Gmail successfully!", "Successfully!", MessageBoxButtons.OK, MessageBoxIcon.Information);
}
}
private void AddItemWithHierarchialStructure(string itemPath, string projPath, string projName, bool bIsResource)
{
var item = File.Exists(itemPath) ? new ItemFile(itemPath) : new ItemFile(Path.Combine(Path.GetDirectoryName(projPath), itemPath));
if (!File.Exists(item.Path))
return;
item.ProjectName = projName;
item.HieraryPath = item.Path.Substring(Directory.GetParent(projPath).FullName.Length + 1).Replace(Path.GetFileName(itemPath), "");
if (bIsResource)
item.LineCount = item.CharCount = 0;
ItemList.Add(item);
}
public string GetFormatString(string strOrignal)
{
var now = DateTime.Now;
return strOrignal.
Replace("$N", SolutionName).
Replace("$P", SolutionPath).
Replace("$Date", now.ToString("yyyyMMdd_HHmm")).
Replace("$Y", now.Year.ToString()).
Replace("$M", now.Month.ToString("D2")).
Replace("$D", now.Day.ToString("D2")).
Replace("$h", now.Hour.ToString("D2")).
Replace("$m", now.Minute.ToString("D2")).
Replace("$s", now.Second.ToString("D2"));
}
internal class ItemFile
{
public string Name { get; set; }
public string Path { get; set; }
public string HieraryPath { get; set; }
public long Size { get; set; }
public long LineCount { get; set; }
public long CharCount { get; set; }
public DateTime Time { get; set; }
public string ProjectName { get; set; }
public bool IsBackup { get; set; }
public ItemFile(string path)
{
Path = path;
HieraryPath = "";
Name = System.IO.Path.GetFileNameWithoutExtension(path);
if (File.Exists(path))
GetDetailInfo();
}
private void GetDetailInfo()
{
// Step1: Count lines & chars
using(var reader = File.OpenText(Path))
{
LineCount = 0;
CharCount = 0;
string line = reader.ReadLine();
while (line != null)
{
LineCount++;
CharCount += line.Length;
line = reader.ReadLine();
}
reader.Close();
}
// Step2: Get Size & time modified
var fileInfo = new FileInfo(Path);
Size = fileInfo.Length;
Time = fileInfo.LastWriteTime;
}
}
public static class ByteFormatter
{
private const long KB = 1024;
private const long MB = KB * 1024;
private const long GB = MB * 1024;
private const string BFormatPattern = "{0} b";
private const string KBFormatPattern = "{0:0} KB";
private const string MBFormatPattern = "{0:0,###} MB";
private const string GBFormatPattern = "{0:0,###.###} GB";
public static string ToString(long size)
{
if (size < KB)
{
return String.Format(BFormatPattern, size);
}
if (size >= KB && size < MB)
{
return String.Format(KBFormatPattern, size / 1024.0f);
}
return size >= MB && size < GB
? String.Format(MBFormatPattern, size / 1024.0f)
: String.Format(GBFormatPattern, size / 1024.0f);
}
}
}
}