取经之旅——把WinForms开发的桌面应用移植到Silverlight开发的RIA(第2部分)

6,数据文件的部署

之前,数据文件是通过ClickOnce一起和应用程序部署的。ClickOnce会自动判断数据文件是否更新了,然后来确定是否下载这些数据文件。而在Silverlight中,如果把数据文件作为Content打包在XAP文件中,那么每次下载(或更新)XAP都会下载这些数据文件。解决方法有两种:把数据文件单独放在一个程序集中,把程序集设置为On-Demand下载或用Application Library Caching机制来缓存;或者,自定义一个数据文件下载和升级的机制。

我采用了第二种方式,即自定义了数据文件的下载更新机制,下面就详细介绍。

首先了解一下参考资料《Silverlight: Downloading Zipped files with the WebClient (stand Silverlight 2 beta 1)》

http://www.galasoft.ch/mydotnet/articles/article-2008032301.html

接着,说明一下我的思路:

  • 在承载Silverlight应用程序的Web应用程序上建立一个文件夹,如“LCADB”
  • 在Web端的LCADB目录中放置一个(或多个)数据文件的zip文件,和一个manifest.xml文件来描述LCADB包含哪些文件,这些文件的最新更新时间
  • 每个zip文件都包含了若干xml文件,我的基础数据都是保存在xml文件中的
  • 如果是Out-Of-Browser的话,在Silverlight程序启动的时候,首先要检查是应用程序本身否有更新,
    • 更新完毕提示用户重启应用程序,
    • 无更新则调用InstallOrUpdateDB
  • 如果不是OfB的话,要用InstallState属性来跳过应用程序更新的代码,直接调用InstallOrUpdateDB
  • InstallOrUpdateDB,检查是否已经分配独立存储区(我分配了100M,数据文件解压后大致用到30M,其他可能作为临时空间,这点后面叙述)
    • 如果未分配,显示一个“Install DB”的按钮给用户,让用户点击以运行IncreaseQuotaTo,这样做的原因是安全机制限制了IncreaseQuotaTo方法必须由用户的事件所引发
      • 分配完成,接着调用CheckDBManifest
    • 如果已分配,直接调用CheckDBManifest
  • CheckDBManifest,用来检查服务器上的manifest.xml内容,和本地保存的manifest.xml进行比较以确定需要下载那些zip文件,这里是通过DownloadStringAsync方法直接获得manifest.xml的文本内容
  • 如果需要下载zip文件,就调用DownloadAndExtract,
  • DownloadAndExtract,用来下载zip文件并解压到独立存储区中,这里是通过OpenReadAsync来获取文件流,
  • 在OpenReadAsync异步方法完成后,解压zip文件的内容到独立存储区中,解压完成后,继续处理下一个需要下载的zip文件,并不断重复。在此处,我对SharpZipLib进行扩展,可以把文件解压到独立存储区中。

整个流程就是这样,由于我没有安装Visio 2010,就不画流程图了。相关代码如下:

public partial class MainPage : UserControl
{
   Application app = Application.Current;
   public MainPage()
   {
       InitializeComponent();
       //在OfB中才用代码去更新
       if (app.InstallState==InstallState.Installed)
           app.CheckAndDownloadUpdateCompleted += new CheckAndDownloadUpdateCompletedEventHandler(app_CheckAndDownloadUpdateCompleted);
   }

   void app_CheckAndDownloadUpdateCompleted(object sender, CheckAndDownloadUpdateCompletedEventArgs e)
   {
       if (e.UpdateAvailable)
       {
           MessageBox.Show("An application update has been downloaded. " +
               "Restart the application to run the new version.");
       }
       else if (e.Error != null &&
           e.Error is PlatformNotSupportedException)
       {
           MessageBox.Show("An application update is available, " +
               "but it requires a new version of Silverlight. " +
               "Visit the application home page to upgrade.");
       }
       else
       {
           MessageBox.Show("There is no update available.");
           this.IsEnabled = true;
           InstallOrUpdateDB();
       }
   }

   private void InstallOrUpdateDB()
   {
       if (ProgramBase.ShouldIncreaseQuota)
       {
           button1.Visibility = System.Windows.Visibility.Visible;
       }
       else
       {
           this.CheckDBManifest();
       }
   }

   private void UserControl_Loaded(object sender, RoutedEventArgs e)
   {

       // Do not load your data at design time.
       // if (!System.ComponentModel.DesignerProperties.GetIsInDesignMode(this))
       // {
       //     //Load your data here and assign the result to the CollectionViewSource.
       //     System.Windows.Data.CollectionViewSource myCollectionViewSource = (System.Windows.Data.CollectionViewSource)this.Resources["Resource Key for CollectionViewSource"];
       //     myCollectionViewSource.Source = your data
       // }
       if (app.InstallState == InstallState.Installed)
       {
           if (NetworkInterface.GetIsNetworkAvailable())
               this.IsEnabled = false;
               app.CheckAndDownloadUpdateAsync();
       }
       else
           InstallOrUpdateDB();
   }

   private void CheckDBManifest()
   {
       if (!NetworkInterface.GetIsNetworkAvailable()) return;
       WebClient wc = new WebClient();
       wc.DownloadStringCompleted += new DownloadStringCompletedEventHandler(wc_DownloadStringCompleted);
       wc.DownloadProgressChanged += new DownloadProgressChangedEventHandler(wc_DownloadProgressChanged);
       label1.Content = "DB updated checking ...";
       var url = ProgramBase.LCADBWebPath + "manifest.xml";
       wc.DownloadStringAsync(new Uri(url));
   }

   void wc_DownloadProgressChanged(object sender, DownloadProgressChangedEventArgs e)
   {
       progressBar1.Value= e.ProgressPercentage;
   }

   List<LCADBZipFile> zipfilesWeb;
   List<LCADBZipFile> zipfilesLocal;
   int checkfileIndex = 0;
   void wc_DownloadStringCompleted(object sender, DownloadStringCompletedEventArgs e)
   {
       label1.Content = "Finished DB updated checking.";

       var lcadb_zipfiles_manifest = "lcadb_zipfiles_manifest.xml";

       XDocument docWeb = XDocument.Parse(e.Result);
       zipfilesWeb = (from m in docWeb.Descendants("file")
                      select new LCADBZipFile
                      {
                          Name = m.Attribute("name").Value,
                          Created = DateTime.Parse(m.Attribute("created").Value)
                      }).ToList();
       
       using (var store = ProgramBase.OpenStore())
       {
           if (store.FileExists(lcadb_zipfiles_manifest))
           {
               var s = store.OpenFile(lcadb_zipfiles_manifest, FileMode.Open);
               XDocument docLocal = XDocument.Load(s);
               zipfilesLocal = (from m in docLocal.Descendants("file")
                                select new LCADBZipFile
                                {
                                    Name = m.Attribute("name").Value,
                                    Created = DateTime.Parse(m.Attribute("created").Value)
                                }).ToList();
               s.Close();
           }
           else
           {
               zipfilesLocal = new List<LCADBZipFile>();
           }
           docWeb.Save(store.OpenFile(lcadb_zipfiles_manifest, FileMode.Create));
       }

       //检查哪些文件需要下载
       foreach (var item in zipfilesWeb)
       {
           var localfile = zipfilesLocal.FirstOrDefault(o => o.Name == item.Name);
           if (localfile == null || localfile.Created < item.Created)
           {
               qDownloadFiles.Enqueue(item.Name);
           }
       }
       DownloadAndExtract();
   }

   private Queue<string> qDownloadFiles = new Queue<string>();

   private void DownloadAndExtract()
   {
       if (qDownloadFiles.Count > 0)
       {
           var name = qDownloadFiles.Dequeue();
           //下载并解压
           WebClient wc2 = new WebClient();
           wc2.OpenReadCompleted += new OpenReadCompletedEventHandler(wc2_OpenReadCompleted);
           wc2.DownloadProgressChanged += new DownloadProgressChangedEventHandler(wc2_DownloadProgressChanged);
           //显示下载进度
           label1.Content = "Downloading " + name;
           wc2.OpenReadAsync(new Uri(ProgramBase.LCADBWebPath + name));
       }
       else
       {
           //显示主页面
           label1.Content = "Downloaded!";
       }           
   }

   void wc2_DownloadProgressChanged(object sender, DownloadProgressChangedEventArgs e)
   {
       progressBar1.Value = e.ProgressPercentage;
   }

   void wc2_OpenReadCompleted(object sender, OpenReadCompletedEventArgs e)
   {
       FastZip zip = new FastZip();
       string path=IO.Path.Combine(ProgramBase.LCADBXmlPath,zipfilesWeb[checkfileIndex].Name.Split('_')[0]);
       using (var store=ProgramBase.OpenStore())
       {
           if (!store.DirectoryExists(path)) store.CreateDirectory(path);
           zip.ExtractZip(store, e.Result, path,FastZip.Overwrite.Always,null,"","",true);
       }

       //继续检查下载下一个
       DownloadAndExtract();
   }

   class LCADBZipFile
   {
       public string Name { get; set; }
       public DateTime Created { get; set; }
   }

   private void button1_Click(object sender, RoutedEventArgs e)
   {
       ProgramBase.CreateDBDirectory();
       this.CheckDBManifest();
   }
}

SharpZipLib的扩展代码如下:

#region 在独立存储区中解压zip文件
//developed by zyg, ITKE
public void ExtractZip(IsolatedStorageFile store, Stream stream, string targetDirectory, Overwrite overwrite, ConfirmOverwriteDelegate confirmDelegate,
                    string fileFilter, string directoryFilter, bool restoreDateTime)
{
  if ((overwrite == Overwrite.Prompt) && (confirmDelegate == null))
  {
      throw new ArgumentNullException("confirmDelegate");
  }

  continueRunning_ = true;
  overwrite_ = overwrite;
  confirmDelegate_ = confirmDelegate;
  targetDirectory_ = targetDirectory;
  fileFilter_ = new NameFilter(fileFilter);
  directoryFilter_ = new NameFilter(directoryFilter);
  restoreDateTimeOnExtract_ = restoreDateTime;

  using (zipFile_ = new ZipFile(stream))
  {

#if !NETCF_1_0
      if (password_ != null)
      {
          zipFile_.Password = password_;
      }
#endif

      System.Collections.IEnumerator enumerator = zipFile_.GetEnumerator();
      while (continueRunning_ && enumerator.MoveNext())
      {
          ZipEntry entry = (ZipEntry)enumerator.Current;
          if (entry.IsFile)
          {
              if (directoryFilter_.IsMatch(Path.GetDirectoryName(entry.Name)) && fileFilter_.IsMatch(entry.Name))
              {
                  ExtractEntryInIsolatedStorage(store, entry);
              }
          }
          else if (entry.IsDirectory)
          {
              if (directoryFilter_.IsMatch(entry.Name) && CreateEmptyDirectories)
              {
                  ExtractEntryInIsolatedStorage(store, entry);
              }
          }
          else
          {
              // Do nothing for volume labels etc...
          }
      }
  }
}

private void ExtractEntryInIsolatedStorage(IsolatedStorageFile store, ZipEntry entry)
{
  bool doExtraction = false;

  string nameText = entry.Name;

  if (entry.IsFile)
  {
      // TODO: Translate invalid names allowing extraction still.
      doExtraction = NameIsValid(nameText) && entry.IsCompressionMethodSupported();
  }
  else if (entry.IsDirectory)
  {
      doExtraction = NameIsValid(nameText);
  }

  // TODO: Fire delegate were compression method not supported, or name is invalid?

  string dirName = null;
  string targetName = null;

  if (doExtraction)
  {
      // Handle invalid entry names by chopping of path root.
      if (Path.IsPathRooted(nameText))
      {
          string workName = Path.GetPathRoot(nameText);
          nameText = nameText.Substring(workName.Length);
      }

      if (nameText.Length > 0)
      {
          targetName = Path.Combine(targetDirectory_, nameText);
          if (entry.IsDirectory)
          {
              dirName = targetName;
          }
          else
          {
              //dirName = Path.GetDirectoryName(Path.GetFullPath(targetName));
              dirName = targetDirectory_;
          }
      }
      else
      {
          doExtraction = false;
      }
  }

  if (doExtraction && !store.DirectoryExists(dirName))
  {
      if (!entry.IsDirectory || CreateEmptyDirectories)
      {
          try
          {
              //Directory.CreateDirectory(dirName);
              store.CreateDirectory(dirName);
          }
          catch (Exception ex)
          {
              doExtraction = false;
              if (events_ != null)
              {
                  if (entry.IsDirectory)
                  {
                      continueRunning_ = events_.OnDirectoryFailure(targetName, ex);
                  }
                  else
                  {
                      continueRunning_ = events_.OnFileFailure(targetName, ex);
                  }
              }
              else
              {
                  continueRunning_ = false;
              }
          }
      }
  }

  if (doExtraction && entry.IsFile)
  {
      ExtractFileEntryIsolatedStorage(store,entry, targetName);
  }
}

private void ExtractFileEntryIsolatedStorage(IsolatedStorageFile store, ZipEntry entry, string targetName)
{
  bool proceed = true;
  if (overwrite_ != Overwrite.Always)
  {
      if (store.FileExists(targetName))
      {
          if ((overwrite_ == Overwrite.Prompt) && (confirmDelegate_ != null))
          {
              proceed = confirmDelegate_(targetName);
          }
          else
          {
              proceed = false;
          }
      }
  }

  if (proceed)
  {
      if (events_ != null)
      {
          continueRunning_ = events_.OnProcessFile(entry.Name);
      }

      if (continueRunning_)
      {
          try
          {
              using (var outputISStream=store.OpenFile(targetName,FileMode.OpenOrCreate))
              {
                  if (buffer_ == null)
                  {
                      buffer_ = new byte[4096];
                  }
                  if ((events_ != null) && (events_.Progress != null))
                  {
                      StreamUtils.Copy(zipFile_.GetInputStream(entry), outputISStream, buffer_,
                          events_.Progress, events_.ProgressInterval, this, entry.Name);
                  }
                  else
                  {
                      StreamUtils.Copy(zipFile_.GetInputStream(entry), outputISStream, buffer_);
                  }

                  if (events_ != null)
                  {
                      continueRunning_ = events_.OnCompletedFile(entry.Name);
                  }
              }
              
          }
          catch (Exception ex)
          {
              if (events_ != null)
              {
                  continueRunning_ = events_.OnFileFailure(targetName, ex);
              }
              else
              {
                  continueRunning_ = false;
              }
          }
      }
  }
}
#endregion

注意,以上代码未经过仔细测试,请谨慎使用!

你可能感兴趣的:(silverlight)