Friday, October 17, 2008

Asynchronious call Delegate

参见 http://blogs.yodersolutions.com/net/?p=17
当我们声明了Delegate 变量(比如FooDelegate), 我们可以使用下面两种方法,来触发这个Delegate,
1. FooDelegate.Invoke(), 或者直接是FooDelegate().
2. FooDelegate.BeginInvoke().
第一种调用方法是同步执行这个delegate.
而第二种方式是异步执行这个delegate, 这个delegate会在另一个线程中被执行, 当前线程将继续执行其它代码. 需要说明的是, 一定要确保这个delegate有机会调用了EndInvoke(), 否则会有资源和内存泄漏.
异步调用的一个简单示例是:
subscriber.BeginInvoke(this, new EventArgs(), Callback, subscriber);
其中第3个参数是回调函数, 它的签名是AsynCallback delegate.当subscriber被执行完后, Callback会被调用.
其中第4个参数一定要是subscriber本身,或者是subscriber本身的一个wrapper, 这样在callback函数体中, 就能访问到subscriber本身来EndInvoke.


class Program
{
static void Main(string[] args)
{
EventPublisher eventPublisher = new EventPublisher();
eventPublisher.FooEvent += new EventHandler(eventPublisher_FooEvent1);
eventPublisher.FooEvent += new EventHandler(eventPublisher_FooEvent2);
Stopwatch sw = new Stopwatch();
sw.Start();
eventPublisher.AsynFireFooEvent();
Console.WriteLine(sw.ElapsedMilliseconds.ToString());
Console.ReadKey();
}

static void eventPublisher_FooEvent1(object sender, EventArgs e)
{
Thread.Sleep(100);
}
static void eventPublisher_FooEvent2(object sender, EventArgs e)
{
Thread.Sleep(200);
}
}



class EventPublisher
{
public event EventHandler FooEvent;


/// <summary>
/// synchronized invoke the all the subscriber services.
/// The process will return until all delegates executions are over.
/// </summary>
public void FireFooEvent()
{
if (FooEvent != null)
FooEvent(this, new EventArgs());
}


/// <summary>
/// Raising events asynchronously lets the event subscribers do their processing in another thread
/// and allows the thread that raised the event to continue processing.
/// </summary>
public void AsynFireFooEvent()
{
if (FooEvent != null)
{
Delegate[] subscribers = FooEvent.GetInvocationList();
foreach (EventHandler subscriber in subscribers)
{
//the 3rd arg should be a AsyncCallback type delegate. When one subscriber service will completed, the AsyncCallback method will be raised.

//the 4th arg should be the exact subscriber or subscriber wrapper, so that in callback function,
// we can access the subcriber to call EndInvoke()
subscriber.BeginInvoke(this, new EventArgs(), Callback, subscriber);
}
}
}



void Callback(IAsyncResult ar)
{
EventHandler raisedEvent = (EventHandler)ar.AsyncState;
raisedEvent.EndInvoke(ar);
}
}

Thursday, October 9, 2008

Auto build script by using NAnt

当我们想专门为一个C#项目架一个build服务器, 怎样对VS的solution文件进行自动build是个问题, 最简单的办法是, 编写一个bat文件, 运行这个bat文件就能完成solution的编译甚至运行单元测试 . NAnt是个很好的选择.
简单地讲, NAnt就是一个能将xml文件作为参数的命令行工具, 你可以在这个xml中按照NAnt的schema编写你想要完成的任务, 比如压缩文件, 比如删除目录, 当然NAnt最主要的作用是可以编写任务来批量编译c#的代码. 但是考虑到MS官方已经发布了msbuild, 所以一个比较好的做法是:在NAnt的参数xml文件中, 使用msbuild来编译.Net solution, 用NAnt本身的功能完成其它事情.
当然对于Nant新手来说, 编写这样的xml并不简单. 幸好我们有一些现成工具可以使用, Tree Surgeon就是这样一个开源项目(http://www.codeplex.com/treesurgeon).
TreeSurgeon不仅仅帮你编写xml的build文件(你可以根据需要做比较的修改), 而且会自动将NAnt以及NUnit等整理到一个tools目录. Treesurgeon创建的solution目录结构也很有参考价值, 比如和src目录同级别的目录, 还有bin, tools, lib, dist目录, 其中bin是存放solution的输出文件(需要在Visual studio中将这个solution的所有project的output目录制定到这个bin目录), dist目录是专门作为solution的release目录.
如果想测试这个xml文件中某些变量设置是否正确, 可以使用在project tag之下, 创建echo tag, 在用NAnt执行这个xml文件时, echo中的message信息就会被输出到屏幕上.


<echo message="${framework::get-framework-directory(framework::get-target-framework())}\msbuild.exe"/>

下面的bat和xml是使用NAnt的示例(它是由TreeSurgeon生成, 并参考了Ninject源码中的NAnt build_xml文件). 使用非常方便,
cmd> build 或
cmd> build test
build.bat的内容是:


@tools\nant\NAnt.exe -buildfile:DbGear.build %*
rem pause

DbGear.build是一个xml文件, 用NAnt命令行程序执行这个xml文件(NAnt0.86版本如用msbuild tag来调用.net 3.5 的msbuild时候, 会有bug. 所以这个例子并没有使用常规的msbuild tag,而是使用 exec tag直接调用3.5版本的msbuild命令行程序)


<?xml version="1.0" ?>
<!--refer to treesurgeon on codeplex, and build file of ninject project-->
<project name="DbGear" default="compile" xmlns="http://nant.sf.net/schemas/nant.xsd">
<!--tell NAnt try to use msbuild from .net sdk 3.5-->
<property name="nant.settings.currentframework" value="net-3.5" />

<!--declare path variables-->
<property name="path.base" value="${project::get-base-directory()}"/>
<property name="path.src" value="${path.base}/src"/>
<property name="path.tools" value="${path.base}/tools"/>
<property name="path.build" value="${path.base}/bin"/>
<property name="path.dist" value="${path.base}/dist"/>
<property name="path.build.testresult" value="${path.build}/test-result"/>
<property name="path.build.debug" value="${path.build}/debug"/>

<!--define solution variables-->
<property name="version" value="1.0.0.0" overwrite="false"/>
<property name="file.solution" value="${path.src}/DbGear.sln"/>
<!--choose one suitable compiling config, eg Debug, Release or other customized config schema-->
<property name="build.configuration" value="debug"/>

<!-- define build targets -->
<target name="clean" description="Delete Automated Build artifacts">
<delete dir="${path.build}" if="${directory::exists(path.build)}"/>
<delete dir="${path.dist}" if="${directory::exists(path.dist)}"/>
</target>

<target name="init" description="create assembly out path">
<mkdir dir="${path.dist}" if="${not(directory::exists(path.dist))}"/>
<mkdir dir="${path.build}" if="${not(directory::exists(path.build))}"/>
<mkdir dir="${path.build.testresult}" if="${not(directory::exists(path.build.testresult))}"/>
<mkdir dir="${path.build.debug}" if="${not(directory::exists(path.build.debug))}"/>
</target>



<echo message="Build configuration:" />
<echo message="${framework::get-framework-directory(framework::get-target-framework())}\msbuild.exe"/>

<!--nant0.86 has a bug with Framework 3.5 section, so we will use exec to call msbuild.exe of 3.5, rather than the regular msbuild tag -->
<!--
<target name="compile" depends="init" description="Compiles using the AutomatedDebug Configuration">
<msbuild project="${file.solution}">
<property name="Configuration" value="${build.configuration}" />
</msbuild>
</target>
-->

<target name="compile" depends="init" description="Compiles using the Debug Configuration">
<exec program="${framework::get-framework-directory(framework::get-target-framework())}\msbuild.exe" >
<arg value="${file.solution}" />
<arg value="/verbosity:minimal" />
<arg value="/property:Configuration=${build.configuration}" />
<arg value="/property:WarningLevel=0" />
</exec>
</target>

<target name="test" depends="compile, unittest"
description="Compile and Run Tests" />

<target name="full" depends="clean, test"
description="Compiles, tests, and produces distributions" />

<!-- Internal targets -->
<target name="unittest" description="runs the unit tests">
<!-- Test Assembly -->
<exec program="${path.tools}/nunit/nunit-console.exe">
<arg value="${path.build.debug}/UnitTests.dll" />
<arg value="/xml=${path.build.testresult}/UnitTests-Results.xml" />
</exec>
</target>


<target name="dist">
<zip zipfile="${path.dist}\DbGear-${version}.zip">
<fileset basedir="${path.build.debug}">
<include name="**/*" />
<exclude name="**/*.pdb" />
</fileset>
</zip>
</target>


<target name="package-source">
<zip zipfile="${path.dist}\DbGear-${version}-source.zip" ziplevel="9">
<fileset basedir="${path.src}">
<include name="**/*.cs"/>
<include name="**/*.csproj"/>
<include name="**/*.sln"/>
<include name="**/*.txt"/>
<include name="**/*.build"/>
<exclude name="**/*.pdb" />
</fileset>
</zip>
</target>
</project>

Saturday, September 27, 2008

Using Ninject(one DI container)

在设计中, 经常会遇到这样的情况, 在一个类中要创建或使用另一个类的实例, 暂且将前一个类称为大类, 后一个类称为小类, 也许小类有很多变种, 我们会根据不同的情况, 让大类使用不同的小类变种. 可以看出这里有一个耦合关系, 一个好的办法是使用依赖注入模式(Dependency Inject), Martin Fowler 有个非常精彩的文章专门讲述DI).
DI原理其实很简单, 我们设计的大类要依赖于小类的接口或基类, 而不是一个具体的小类, 同时不要在大类中创建小类实例, 而是通过大类的构造子或property将小类的实例传进来即可.
目前.Net领域有不少DI容器框架, (这里是DI容器框架列表), 这些框架主要有两个功能, (1)帮助完成依赖的注册与依赖实例的创建, 即绑定小类接口和某个具体小类, 绑定的地方不是在大类中, 所以实现了解耦. (2)更高级的功能是, DI 容器框架可以帮助我们创建大类实例, 就像是一个Factory工厂方法一样, 当然大类所依赖的小类, 也不同我们手动创建, 容器自动按照我们定义的小类绑定关系来创建小类实例.
Ninject是一个新的DI框架, 它的特点是:
(1)不需要使用Xml来定义绑定关系, 使用一个自定义的Module类(基类为StandardModule)来定义绑定关系;
(2)不仅仅支持constructor注入, 而且支持property注册;
(3)对于constrctor注入, 构造子还可以包含其他类型参数, 不仅仅是所依赖的类型参数.
(4)对于非constructor注入, 不仅仅支持无参构造子, 而且支持带参构造子.
(5)支持基于类型的条件绑定, 可以使用Bind().To().ForMembersOf();
(6)部分支持基于variable的Context的条件绑定, 示例代码中对此有说明.(Ninject支持另一种条件绑定, 但它需要在大类的代码加上Parameter attribute或Method attribute, 增加了大类和小类的耦合度, 所以我不喜欢, 好在基于context variable的绑定基本够用了)
(7)对于利用DI容器创建的对象, 可以在定义绑定时, 指定对象的生命周期, 比如Singleton以及SingleCall的生命周期模式.

下面是一个使用Ninject的简单示例, 需要添加Ninject.core和Ninject.condition两个assembly.
主程序类和自定义的Module类:



using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;

using Ninject.Core;
using Ninject.Conditions;
using Ninject.Core.Behavior;
using Ninject.Core.Parameters;
using Ninject.Core.Activation;

namespace NinjectDemo
{
class Program
{
static void Main(string[] args)
{
#region DI: Old way
IEditor editor = new NotePad();
var csProgrammer = new WindowsProgrammer(editor);
csProgrammer.WriteCode();
#endregion

#region DI container
#region Initialization for using Ninject
var module = new ProgrammerModule();
IKernel kernel = new StandardKernel(module);
#endregion

#region Simple inject
var vbProgrammer = kernel.Get<WindowsProgrammer>();
vbProgrammer.WriteCode();

//Get correct concrete class according to binding condition
var linuxProgrammer = kernel.Get<LinuxProgrammer>();
linuxProgrammer.WriteCode();
#endregion

#region Including other parameters rather than inject interface
Dictionary<string, object> param = new Dictionary<string, object>();
//there is another parameter named firstName in constructor
param.Add("firstName", "Harry-Mac OS");
var macProgrammer = kernel.Get<MacProgrammer>(With.Parameters.ConstructorArguments(param));
macProgrammer.WriteCode();
#endregion

#region Control binding by context variable
IParameterCollection pc = With.Parameters.ContextVariable("OS", "Mac").ConstructorArguments(param);
var programmer = kernel.Get<Programmer>(pc);
macProgrammer.WriteCode();
#endregion

#endregion DI container

Console.WriteLine("Enter any key to exit...");
Console.ReadKey();
}
}


class ProgrammerModule : StandardModule
{
/// <summary>
/// 注册接口和对象的绑定
/// </summary>
public override void Load()
{
//This is a default bind for IEditor inteface
Bind<IEditor>().To<NotePad>();
//Bind<IEditor>().To<NotePad>().Always();// same to the above line

//This is a condtional bind for IEditor injection
Bind<IEditor>().To<Vi>().ForMembersOf<LinuxProgrammer>();
//or the following code
//Bind<IEditor>().To<Vi>().OnlyIf(c => c.Member.DeclaringType == typeof(LinuxProgrammer));
//or the following code
//Bind<IEditor>().To<Vi>().OnlyIf(delegate(IContext c) { return c.Member.DeclaringType == typeof(LinuxProgrammer); });

Bind<IEditor>().To<Vi>().ForMembersOf<MacProgrammer>();

//This is another kind of conditional bind.
//我们获取对象要用kernel.Get<T1>()方法, 绑定用Bind<T2>()方法,
// 这种context绑定仅仅适合于T1和T2同一类型或基于同一类型
Bind<Programmer>().To<MacProgrammer>().Only(When.Context.Variable("OS") == "Mac");
}

/// <summary>
/// Load2 方法在本项目中其实没用, 只是为了列出Ninject在绑定时可指定对象的生命周期
/// </summary>
public void Load2()
{
//每次调用kernel.Get<>(), 总是返回一个新的对象, 如果Remoting中讲的SingleCall对象
Bind<IEditor>().To<NotePad>();
//Bind<IEditor>().To<NotePad>().Using<TransientBehavior>(); //same as the above

//获取的对象是Singleton对象, 即使是不同线程, 获得的对象总是同一个
Bind<IEditor>().To<NotePad>().Using<SingletonBehavior>();

//One instance of the type will be created per web request, and will be destroyed when the request ends.
Bind<IEditor>().To<NotePad>().Using<OnePerRequestBehavior>();

//在同一个线程中, 每次调用kernel.Get<>()获取的对象总是同一个
Bind<IEditor>().To<NotePad>().Using<OnePerThreadBehavior>();
}
}

}


下面是IEditor接口和实现类:



using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace NinjectDemo
{

interface IEditor
{
string Code();
}

public class Vi : IEditor
{
public string Code()
{
return "This is Vi editor";
}
}

public class NotePad : IEditor
{
public string Code()
{
return "This is NotePad editor";
}
}
}

下面是Programmer基类和派生类:



using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

#region Ninject namespace
using Ninject.Conditions;
using Ninject.Core.Behavior;
using Ninject.Core;
#endregion

namespace NinjectDemo
{
abstract class Programmer
{
IEditor m_Editor;
public Programmer(IEditor editor)
{
this.m_Editor = editor;
}

public virtual void WriteCode()
{
Console.WriteLine(m_Editor.Code());
}

}

class WindowsProgrammer:Programmer
{
public WindowsProgrammer(IEditor editor)
: base(editor)
{
}
}

class MacProgrammer : Programmer
{
string m_FirstName;

[Inject]
public MacProgrammer(IEditor editor, string firstName)
: base(editor)
{
m_FirstName = firstName;
}

public override void WriteCode()
{
Console.Write( m_FirstName+" , ");
base.WriteCode();
}
}

class LinuxProgrammer : Programmer
{
[Inject]
public LinuxProgrammer(IEditor editor)
: base(editor)
{
}
}
}

Tuesday, August 26, 2008

check exchange mail by firefox

This summary is not available. Please click here to view the post.

Thursday, August 21, 2008

svn authorization

预先的环境是: svn server的IP是: 11.12.13.14, OS是windows 2003 server. repository的目录是D:\source, 我们在该repository下建立了两个项目, 分别是Project1和Project2. 建立repository的过程, 见这个blog
常用的Svn访问协议有三种, 第一种是file协议, 第二是svn协议, 第三是https协议.
https协议比较适合于跨广域网的项目开发, 需要有apache的支持, 所以配置比较复杂一些, 可参考网络上的文章 .
  1. Setting up an ASP.NET website development environment using Visual Studio .NET, Subversion, and Windows XP http://www.codeproject.com/KB/aspnet/Subversion.aspx
  2. HOWTO: Subversion for Windows with Apache server - a beginner's guide [version 0.4] http://svn.spears.at/
  3. Setting Up Subversion for One or Multiple Projects http://www.linuxjournal.com/article/7655
file协议最适合一个人的项目, 设置非常简单, 你在你本机开一个svn server, 同时在你本机创建一个repository. 你的svn客户端就可以用file协议访问repository了. 也可以多人一起用, 但必须将repository 的目录共享出来, 所以安全性很差, 不建议在多人项目中使用这种模式.
本机file协议访问project1的url是 file:///D:/source/project1 .
如果是局域网访问的话是, file:///\11.12.13.14/source/project1
Svn协议比较适合于局域网内部多人项目的开发, 在svn server建立了一个repository之后, 我们就可以使用svn协议了, URL是 svn://11.12.13.14/source/project1 ,这是因为缺省情况下, 这个repository已经被开启了匿名读权限了, 但写权限被关闭了. 所以任何人都可以读取这个repository的内容. 我们需要为每个人定制不同的访问权限. 需要修改这个repository目录下的三个文件, 它们都在D:\harry\source\conf下.
(1) svnserve.conf文件, 你需要处理掉4个label的注释, 分别是authz-db和password-db和anon-access和auth-access.需要注意的是, 确保每行前边没有多余的空格, 否则当你访问svn会报错. 另外最好为realm这个label设置一个有意义的名称, 因为svn client连接server时候, 会显示server的realm名称. 同时如果你想让多个repository共用同一组用户账号, 可以将这几个repository的passwd文件指向同一个文件, 可以使用相对路径来指定passwd文件(相对路径是基准是conf目录), 也可以用绝对路径来指定. 需要注意的, 路径必须使用unix风格的分隔符/, 而不能是\.

(2) passwd文件, 在[users]section下定义用户和密码, 每个账号一行,用户名和密码用=号分隔 .
(3) authz文件, 这是最关键的配置文件. 下面是authz文件的配置:
[groups]
Prj1_Dev_Team=harry,helen
Prj2_Dev_Team=tom,peter
Test_Team=sally,mark
QA_Team=andy

[/]
*=r #所有人都能读取这个repository
harry=rw #harry具有这个repository的读写权限

[/project1]
#*=rw #取消所有人的读写权限
@Prj1_Dev_Team=rw
@SE_Team_QA=rw

[/project2]
#*=rw
@Prj2_Dev_Team=rw
@SE_Team_QA=rw
@Test_Team=rw

Saturday, August 16, 2008

Registry requisite tools

Regsnap -registry and windows system folder snapshot tool and analysis tool.
RegMon.exe--product from sysinternal company, it is free tool

Tuesday, August 12, 2008

benefits by using svn

首先先讲一下subversion几个特性.
tortoisesvn安装之后, 有个help文档, 非常好, 不仅包含tortoisesvn的帮助, 而且有很多关于subversion服务器端帮助信息. 网络上有一个中文帮助chm文件, 网站为http://svndoc.iusesvn.com

Subversion 在版本控制方面做的非常好, 它用一个global revision number来标示repository的版本状态. 每次commit, 全局版本号码都增一. Subversion 一个非常好的特点是, 若一个文件在两个版本中没有任何修改, 其实它在Subversion中仅有个保存, 这里采用了类似于Unix的hard-link技术. 所以你不用担心频繁的commit会不会使repository存储快速增大.
HEAD revision指的是最新版本的revision, 但是需要注意的是, 如果你要针对HEAD revision进行某种操作时候(比如合并), 你的repository可能同时会被其他人进行commit, 这样你的合并结果可能不是你期望的那个. 所以最好的方式是, 在你合并时候, 指定版本号, 即使是合并最新版本, 也要先获取它的版本号.

Subversion为开发流程提供的新的模式
单单就subversion的合并功能, 其实对于开发模式来讲, 具有非常大的变革. 比如我们要增加一个新的feature, 这个feature的开发有一定的风险或者我们要将这个feature的开发交给另一个team. 通常我们可以将当前的trunk做一个branch, 专供new feature开发的. 由于在new feature的开发中, trunk的代码也在不断演进, 甚至是new feature依赖的基础也可能会变化的, 所以我们不能期待在new feature开发完毕之后, 再将branch和trunk合并, 这样branch的改动量会很大, 所以我们应该每个一周就将trunk的代码合并到branch中, 这样每次合并对于branch这边的冲击会小一些, 迭代几个周期, 当new feature开发完毕之后, 再将branch合并到trunk中.

Monday, August 11, 2008

routine tortoisesvn operations-2

我自己写了一点关于svn的常用功能, 见http://focuswindows.blogspot.com/2008/08/routine-tortoisesvn-operations-1.html


以下内容摘自http://blog.csdn.net/iNarcissuss/archive/2007/09/16/1787411.aspx

Check-out(检出) 和 Export(导出)
+-------------------------------------------------
从 服务器取得 最新版本(Subversion术语--Head Revision)或某个历史版本的拷贝 复制到本地的某个目录,这个本地的目录有个Subversion专门的术语---working copy(工作拷贝),working copy的特点是自动与服务器关联,并受服务器的版本控制。
所以Subverion的CheckOut相当于VSS的 设定工作路径+签出 。
与CheckOut类似的一个命令是------Export(导出),它能将某个版本复制一份到本机的某个目录,与CheckOut不同的是,这个目录不是woking copy,Export(导出)
后就与服务器没有任何关系了,当然更不会受服务器的版本控制。
TortoiseSVN会在是working copy的目录上加上各种标志,而Export的目录不会有任何标志
SVN Update(SVN 更新) & Get Lock
+-------------------------------------------------
最常用的命令,你可以只CheckOut一次,但是最好经常update,特别是在你修改之前尤其特别一定用Update,以保证本地工作拷贝与服务器项目版本库一致。它的功能是
,从服务器取得最新版本跟新本机工作拷贝,相当于VSS的Get Latest Version,如果习惯VSS,并且对SVN的复制--修改--合并模型不放心的话,可以在Update之后,马上使用
“获取锁定”命令,它的作用是使的别人不可以修改你正在修改的文件,以避免冲突。
Import Export (导入 导出)
+-------------------------------------------------
Import是一种快速批量向服务器添加文件的方法,如果你像把本地的文件或文件夹添加到服务器的某个目录下面,那么在本地目录中,右单击选择Import命令,然后
设定URL即可,这是从别的版本管理系统向VSS迁移的一种方法,在版本浏览器上单击某个目录,会有加入文件和加入文件夹的命令,其中加入文件一次只能加入一个文件
加入文件夹只能加入该文件夹下的所有文件,不能加入子文件夹,你可以根据实际需要使用.需要注意的是, 本机目录导入到服务器后,本机目录没有改变,和服务器也没关联,
并不是工作拷贝。Export命令请在本文其他地方找。
Switch & Relocate (切换和重定位)
+-------------------------------------------------
Switch -----在版本库内改变与当前工作拷贝关联的服务器路径URL(只限于同一个Repository内),应用情景:有时,服务器的目录会有所变化,和你当前工作拷贝
关联的服务器URL改变了,这种改变的直接结果是,你在工作拷贝中所做的修改不能提交到服务器,这时就需要Switch到正确的URL再提交。
Relocate----功能和Switch雷同,差别在于,Relocate实现的是Repository和Repository之间的切换。当服务器地址或Repository名称发生变化时,可以用它来更新
你的工作拷贝。
Create/Apply patch (创建和应用补丁)
+-------------------------------------------------
如果你对某个文件没有修改的权限,但又必须对它进行修改,这时你可以对其创建补丁,然后把补丁发给对此目录有write权限的工作人员,他进行一系列的审核,审核
通过后,他会执行应用补丁命令,可以将你所做的修改提交到服务器上。
copy to working copy...(复制到工作拷贝...)
+-------------------------------------------------
首先,注意这里是working copy ,后面在选择目录时一定不要选错!,当然,选错了tortoisesvn会提示错误的,正确的操作应该是选那些有tortoisesvn标志的目录。
执行此命令会在working copy中增加你copy的目录和文件,然后修改,再commit,repository中就做了相应的变化。
当你需要用到这样一个模块,它与已经存在的某个模块类似,然而又不完全一样,需要有一定改动时,用copy to working copy...命令较为快捷。
Resolved ...(已解决的...)
+-------------------------------------------------
当两个人同时编辑一个文件,一个人先提交后,另一个人再进行提交时就会出现冲突,TortoiseSVN会提出必须更新你的工作拷贝后才能对这个文件进行修改,
Subversion冲突的种类及解决方法
+-------------------------------------------------
--->1.当两个人同时编辑一个文件的不同部分,一个人先提交后,另一个人再进行提交时就会出现冲突
--->解决方法:在冲突的文件上调用命令SVN Update(SVN更新),TortoiseSVN会自动从服务器上下载最新版本,并与本地工作拷贝进行合并,然后调用命令
SVN Commit(SVN 提交),就会自动生成一个新版本传到服务器上。
--->2.当两个人同时编辑一个文件的相同部分,一个人先提交后,另一个人再进行提交时就会出现冲突
--->解决方法:这种情况下调用SVN Update时,由于是同一部分修改,TortoiseSVN不知道应该怎么办,它会自动在发生冲突的文件的目录下生成三个文件,一个
发生冲突文件的基础版本,名称为文件的全名再加上基础版本的版本号,如FileConflict.cpp.r90,另一个是你调用SVN Update时更新的版本,名称也是文件
的全名再加上基础版本的版本号,如FileConflict.cpp.r95,它是别人已经修改好提交的文件, 还有一个是你的本地修改,后缀为.mine,而原文件以一种TortoriseSVN能识别的方式显示冲突的双方,而且这个文件上会出现TortoiseSVN的图标,要解决这种
冲突要求造成冲突的两个人进行沟通,确定最终的修改方案,然后进行修改,具体操作是右击有冲突图标的文件,选择“编辑冲突”命令,这时就可以对出现冲突
的文件进行编辑,TortoiseSVN会打开TortoiseMerge,里面会显示别人的修改、你的修改、最终合并的文件,在最终合并的文件中产生冲突的行上右击,根据弹出
菜单选择使用别人的修改、我的修改还是两个都用,编辑完后保存,关闭TortoiseMerge,然后右击有冲突图标的文件,选择Resolved...(已解决的...)命令后
确定即可。

关于“基础版本”及其命令 ---比较差异,检查更新,显示日志,版本分支图
+-------------------------------------------------+-------------------------------------------------
当我们从服务器上CheckOut某个版本,经过一定的修改后,右击修改的文件(注意,不是目录)会有一个“比较差异”命令,调用它会弹出TortoiseMerge,它列出了
当前修改的工作拷贝与我们CheckOut时用到的版本之间的差异,这个被我们CheckOut的版本称为当前工作拷贝的“基础版本”,注意,基础版本不一定是最新版本,最新版
本也不一定是基础版本,比如另一个人和我们CheckOut同一个版本,它在我们之前提交了一份修改会形成一个最新版本----这时,我们进行提交可能造成冲突。
刚才说过,只有文件才会有“比较差异”命令,而目录没有,有没有办法看到目录的变化呢?--肯定有,是哪个呢?先试一下第一个---“显示日志”,结果不行,它只
显示的是当前工作拷贝对应的服务器目录或文件的版本变化记录,从第一个版本到最新版本,一个也不缺。“版本分支图”也一样,它是“显示日志”命令的图形化显示,
功能和“显示日志”完全一样。答案是----“检查更新”,它比较的是当前工作拷贝相对于基础版本的更新记录,不仅能显示文件的变化,也能显示目录的增减等。
Revert (复原) 和 Update to version
+-------------------------------------------------
当你CheckOut并修改了文件后,如果在提交前发现修改不正确,可以用Revert来取消这次的修改,使文件回到基础版本的状态;如果已经提交,可以用Update to Version
(更新至版本)返回某个旧的版本,或者调用显示日志命令,在任意一处日志上单击,在下方会出现修改记录,右击某一记录,在弹出菜单中选择Save Revision to(保存版本至)
,也可以把原来版本进行存档。需要注意的是,你的本地工作拷贝可以返回到原来的版本,可是服务器上只会记录你对它进行最后一个提交的版本,所以服务器端版本不
会 变化,如果你想把服务器端的文档内容也换为原来的状态,可以用旧版本文件覆盖新版本文件,然后提交,或者使用Revert changes from this revision 和 Revert to this revision命令,她们的用法在本文档中有,自己找找,调用显示日志命令,在任意一处日志上右击,在快捷菜单中会出现这两个命令。
注意,Revert命令很危险,它不向Ctrl-Z命令一样一次只撤销一次修改,它的名字是“还原”,它会把所有的修改全部撤销,直接回到基础版本的状态,如果想只撤销
一 次的修改,我的方法是,使用“检查更新”和“比较差异”命令在TortoriseSVN中修改,TortoriseSVN中的"use this text block ","use other text block ","use both text block "等命令很方便,关于这两个命令在本文中也有介绍,自己找。
Revert changes from this revision 跟Revert to this revision的区别?
+-------------------------------------------------
譬如有个文件,有十个版本,假定版本号是1,2,3,4,5,6,7,8,9,10。

-->1.Revert to this revision(恢复到某个以前的版本): 如果在版本6这里点击“Revert to this revision",表示7~10的修改全部作废,历史倒退到了版本6那个年代。
如果你做了多处修改,然后决定要返回到版本 N,你就可以使用这个命令。再次说明,恢复的修改位于你的工作复本,在你提交之前,并不会影响版本库。
注意,这将会丢弃从那个版本以来的所有修改,使用选中的版本来替换文件/文件夹。

-->2.Revert changes from this revision(还原选中版本所做的修改):如果在版本6这里点击“Revert changes from this revision",表示版本6这个历史事件被抹杀了,只剩下9个历史事件了。 如果同时选择了6,7,8三个历史时期并点击“Revert changes from this revision”,表示抹杀6~8这仨历史时期。同理,如果同时选择7~10,然后点击“Revert changes from this revision”,则效果跟在版本6这里点击“Revert to this revision”是一样的。
还原的内容只在你的工作副本中,所以此操作完全不会影响版本库!要注意的是,这个操作仅仅还原该版本中的修改。不是将整个文件替换成选中的那个版本。
它对于已经做过其它无关修改的还原早期修改非常有用。如果你做了本地修改,此命令将会合并修改到工作副本。
关于“基础版本”及其命令 ---比较差异,检查更新,显示日志,版本分支图
+-------------------------------------------------+-------------------------------------------------
当我们从服务器上CheckOut某个版本,经过一定的修改后,右击修改的文件(注意,不是目录)会有一个“比较差异”命令,调用它会弹出TortoiseMerge,它列出了
当前修改的工作拷贝与我们CheckOut时用到的版本之间的差异,这个被我们CheckOut的版本称为当前工作拷贝的“基础版本”,注意,基础版本不一定是最新版本,最新版
本也不一定是基础版本,比如另一个人和我们CheckOut同一个版本,它在我们之前提交了一份修改会形成一个最新版本----这时,我们进行提交可能造成冲突。
刚才说过,只有文件才会有“比较差异”命令,而目录没有,有没有办法看到目录的变化呢?--肯定有,是哪个呢?先试一下第一个---“显示日志”,结果不行,它只
显示的是当前工作拷贝对应的服务器目录或文件的版本变化记录,从第一个版本到最新版本,一个也不缺。“版本分支图”也一样,它是“显示日志”命令的图形化显示,
功能和“显示日志”完全一样。答案是----“检查更新”,它比较的是当前工作拷贝相对于基础版本的更新记录,不仅能显示文件的变化,也能显示目录的增减等。
Branch & Tag (分支与标记)
+-------------------------------------------------
---->1.定义与理解
-->1.1 Branch and Thunk (分支和主线)
版本控制系统的一个特性是能够把各种修改分离出来放在产品的一个分割线上。这条线被称为Branch(分支)。
分支经常被用来试验新的特性,而不会对开发有编译错误的干扰。当新的特性足够稳定之后,产品的分支就可以混合回Thunk(主线)里.

-->1.2 Tag (标记/标签)
版本控制系统的另一个特性是能够标记特殊的版本(例如某个发布版本),所以你可以在任何时候重新建立一个特定的构件和环境。这个过程被称作Tag(标记/标签)。
Subversion 没有专门的用于建立分支和标记的特殊命令,它所谓的分支其实就是一个拷贝,把原目录的内容拷到一个新的目录下面。但是这个拷贝的操作不是真正意义
上的拷贝,Subversion称之为"Lean copy"(便宜复制),它类似于Linux的硬链接,实际的操作是建立了一个内部链接,指向那个被建立分支的目录,只有当我们提交一个文件
的修改,才在分支目录下建立一个真实的文件,它的内容就是我们提交的内容,需要注意的时,这时候,除了这个文件以外,其余的文件仍然是作为被建立分支目录下文件的
链接存在的。
采用Lean copy技术的结果是分支和标记能迅速被创建,并且没有在版本库里占据任何额外的空间。
-->1.3 理解Branch
建立分支的情况举例:
a.Project发行版已经完成,正在继续开发Project贺岁版,计划在两个月后发行。不久,客户开始抱怨Project有问题,于是CheckOut(检出)Project的发行版,
找到几个错误,并进行代码更正。但是当前的版本是个不稳定的版本(现在正处于Project2贺岁版的开发中.....),并且在下个月才能有希望稳定下来,这样就没有办法基于
最新的代码去发行一个修复错误的版本。....这种情况下就可以创建一个Branch(分支),分支的所有文件是基于Project发行版的,可以修改这个分支而不影响到主干(即当
前正在开发的Project2贺岁版),当Project2贺岁版完成时,你可以选定是否要把这个分支同主干(Project2贺岁版)合并或继续保留在这个分支里。
b.如果我们在维护一个项目的文档,但有一天,另一个项目也要这份文档,她们有和我们不同的需求,因此要对文档做一些修改才能适应她们的需要,这时,也可以对
这个文档建立一个分支。
c.Branch可以是项目分支,也可以是私有分支。
所谓的项目分支,是指两个项目需要并发开发,它们基于同一个原始版本的拷贝,且这两个项目又互不干扰的进行独立开发,那么这两条开发线对于原来的那个原始版
本来说,就是项目分支,而原始版本就是项目主干。
所谓的私有分支,是指如果多个人共同负责一个模块,当出现两个人同时修改一个文件的同一部分的情况时,就会不断的出现冲突,开发人员就要花大量的时间解决这
些冲突,而在分支上工作避免了频繁的解决冲突,这种分支叫做私有分支,个人可以在主干上建立一个私有分支出来,当开发到一定阶段以后,可以再对自己分支与主干进
行合并,在与主干进行合并的同时,也能关注到其他人在这一段时间内对主干做的修改。
---->2.创建分支
利用TortoiseSVN有两种方法建立分支:
-->1.从本地工作拷贝到服务器建立,在本地工作拷贝右击在弹出菜单中选择“Branch/Tag”会弹出一个对话框,在其中设定要建立分支的URL即可。
-->2.从URL到URL,在Repository Explorer(版本浏览器)中右击要建立分支的目录,在弹出菜单中选择Copy to...(“复制到...”),在弹出对话框设定要建立分支的URL.
---->3.分支的合并
创建分支后一段时间可以根据需要进行Merge(合并)操作,我们既可以将主线的改动合并到分支上,也可以将分支的改动合并到主线上:
-->1.将主线的改动合并到分支上,需要在分支的工作拷贝下进行合并,合并的范围是从主线上上次合并的版本(或建立分支时候的版本)到当前主线上的最新版本,
如果主线和分支都修改了相同的文件的相同部分,合并后会出现冲突,就要进行解决冲突,如果主线修改但是分支没有修改,则主线上合并的变更内容会增加到当前工作
拷贝中,合并的结果会保存到当前的工作拷贝中,提交后改动就会体现在分支中,如果是第一次合并,则起始版本号是上次建立分支的版本号。
-->2.将分支的改动合并到主线中,需要在主线的工作拷贝下进行合并,相关事项与上雷同。
Blame (追溯)
+-------------------------------------------------
这个命令很不错,能显示文件的所有行在哪个版本中被修改及修改的时间,还可以追溯版本库等,有兴趣可以自己研究。

routine tortoisesvn operations-1

接下来, 我们讲一下tortoisesvn的常规操作. update, revert和switch的区别, 这三个命令的图标都是从服务器端到客户端的箭头, 容易搞混.

1. update是最常用的操作, 就是用来更新本地的work copy, 当然, 如果某个文档是基于svn server的最新版本做了修改,那么update并不会从svn server上download这个文档. 如果你的文档是基于svn server上的一个老版本作了修改, tortoisesvn会让启动一个向导让你解决这个conflict.

2. revert, 这个操作忽略本地work copy的任何修改, 它直接从svn server上下载被你修改了的文档.

3. switch, switch是update的一个超强版本, update是从原有的svn folder下取代码的, 而switch可以指定另一个folder的URL(但是仅限于在同一个repository之下的另一个folder), switch操作使得本地的work copy指向新的URL, 同时更新versioned file的footprint信息. 这个操作因为不需要download所有文档的内容, 所以速度很快.

4. checkout, 就是将svn上的某个directory所有文档都下载到一个本地目录,同时下载文档的footprint信息, 它要求本地目录必须是空目录. 也叫作fresh checkout.

移动和复制

当我们对源码结构进行重新组织时, 经常会对versioned file进行复制和移动操作. Tortoisesvn的move和copy命令比较隐蔽, 在tortoisesvn菜单上是找不到这些命令的, 只能是先选中要移动的folder/file, 然后按下鼠标右键, 拖拉到另一个目录, 这时候会出现一个弹出菜单, 其中就有SVN copy和SVN move.

TortoiseSVN合并和回滚操作

合并功能是Subversion最大的亮点. 它甚至是改变了我们的开发模式(和Sourcesafe的checkout-lock-checkin模式相比). 当然合并操作本身是很复杂的. 主要有3种合并模式, 需要提醒的是, 合并操作后, 结果仅仅反映到本地的work copy上了, 这时候还没有在svn server上进行merge. 如果你真的要在server上merge的话, 需要再执行commit操作.

合并模式1-(merge a range of revisions):即同一个branch不同版本的merge, 在这个向导中, 你需要指定一个URL(因为已经有个缺省值, 所以一般情况下你不需关心这个), 然后你要指定一个要merge的版本序列, 这个不好理解, 为什么是一个版本序列呢? 在一般情况下, 你只需要指定一个版本号, 并不需要merge几个版本. 但svn仍然提供了一个让你一次性merge多个版本的功能.

合并模式2-(reintegrate a branch): 这是subserver提供的一个非常有用的功能, 它可以将我们的另一个branch代码和本地的work copy进行merge.

合并模式3-(合并两个树): 这应该是三种合并模式中, 最灵活的一种, 选项也最多, 你可以指定两个repository 的URL, 以及相应的版本号码, 然后merge.

版本回滚: TortoiseSVN的rollback功能放在Show Log的界面上了, 最方便的一个回滚方法是: Revert to this revision, 这个很好理解, 就是将版本回滚到你选定的那个版本上, 点击之后, 会将work copy回滚到刚刚指定的那个版本. 需要commit之后, 才会将这个版本真正地在svn server上了.

Tuesday, July 29, 2008

Continuous Integration Tool--CC.Net

TeamCity is good tool, the free version is enough for most small-medium projects.
Cruise Control.Net is absolutely free and open source tool. Actually, it is more hard to configure.
Fortunately, there is a sourceforge project can help you.
cc-config: http://cc-config.sourceforge.net/

PS, CC.Net http://cruisecontrol.sourceforge.net/

Understand TeamCity Work Flow

首先可以将TeamCity 看作一个专门Continuous Integration(CI)的Web Portal. TeamCity可以管理多个Build Agent, ,每个Build Agent可以认为是专门用来编译代码的机器. 当然Build Agent可以和TeamCity是同一台计算机. TeamCity为什么采用多个Build Agent这样的架构呢?

多个Build Agent的优点:
  1. 主要是考虑到Build代码的过程可能能长, 另外是我们可能又想加入Unit Test的功能, 尤其是Unit Test很多的情况下(或者commit source很频繁), 一台计算机显然不行.
  2. 我们可以将Unit Test按功能分类, 将他们分配到不同的Build Agent上.
  3. 我们也可以将Unit Test按工作量进行分配, 将他们分配到不同的Build Agent上. 这其实是Load Balance
  4. 提供Pre-Tested commit特性, 详见下文.

TeamCity的特点:
  1. TeamCity由Java开发的, 所以是跨平台的, 支持Java和.Net. 它采用Tomcat作为Web Server.
  2. TeamCity支持多种Build工具, Java方面, 你可以选用Ant, Maven, Idea等. .Net方面, 你可以用MSbuild, NAnt以及Visual Studio的Solution文件.
  3. Pre-Tested Commit(Delayed commit)方式: 应该说这是TeamCity杀手级的特性(killer feature). 相信我们都遇到过这样的情况, 有的程序员嫌跑所有的单元测试太费时间, 测试了部分Unit Test就将代码提交到VCS上(或者根本没有跑Unit Test). 结果很不幸, 那些没有测试的case结果却跑不通. 你不得不将代码rollback, 其过程之痛苦不用多言. TeamCity介绍了Pre-Tested提交这个特性, 可以彻底解决这个问题. 它是为IDE提供TeamCity的插件来触发这个过程. 详细流程见下文的TeamCity的一般工作流程.
TeamCity的一般工作流程是(Workflow_Diagram):
  1. Programmer在IDE中通过TeamCity的Pre-Tested commit将代码check in到VCS Server
  2. TeamCity根据其Trigger设置, 定时从VCS Server取代码, 然后将代码发送到Build Agent中. 指派Agent 去执行响应的Build和Unit Test 操作.
  3. Build Agent完成操作后, 将结果返回给TeamCity.
  4. 如果结果正确的话, 则将Code 真正提交到VCS.

Monday, July 28, 2008

Continuous Integration Tool--TeamCity

TeamCity is another famous Continuous Integrating Tool. It is much easier to configure than CC.Net because TeamCity is allowed to configure through Web UI. The latter one has too many xml files to configure.

Product Site http://www.jetbrains.com/teamcity/
Free Version License Including
  • 3 Build Agents at no additional cost
  • 20 User Accounts
  • 20 Build Configurations

More detail Info, View the following blogs,
  1. Our Build Config--TeamCity(http://blog.eleutian.com/2008/03/07/OurBuildConfigurations.aspx)
  2. CruiseControl.NET is dead. Long live Team City!(http://blog.eleutian.com/2007/12/22/CruiseControlNETIsDeadLongLiveTeamCity.aspx)
  3. Integration of NCover into Team City for Tech Head Brothers(http://weblogs.asp.net/lkempe/archive/2008/03/30/integration-of-ncover-into-team-city-for-tech-head-brothers.aspx)
  4. Continuous Integration Revisited(http://www.zorched.net/2006/08/10/continuous-integration-revisited/)

NDbUnit Framework

NDbUnit test is a good UnitTest framework if you are familiar with C#. But this project seems not active since 2006.
Home Page: http://qualitylabs.org/projects/ndbunit/

How to increase scalability--InfoQ article

http://www.infoq.com/articles/scalability-panel

Thursday, July 24, 2008

WSE-enabled web service to authenticate by user/password

WSE is short for web service enhancement.

WSE 1 user/password authentication
1. http://www.eggheadcafe.com/articles/20021227.asp
2.http://www.devx.com/security/Article/15634/1954
3. http://www.eggheadcafe.com/articles/20021231.asp ( X509 certification )

WSE 3
1. http://geeknotes.wordpress.com/2007/03/16/a-simple-wse-30-web-service-to-authorize-by-username-and-password/
2. http://blog.nerdbank.net/2006/03/wse3-and-aspnet-membership-provider.html

version control with TortoiseSVN without SVN Server

If you only have a limited number of users, and do not need remote access, you can use Tortoise without a subversion server. This makes set-up much easier.
Steps:
1. Install TortoiseSVN software
2. Open Windows Explorer.
3. Create a empty folder as your repository, this can be local or on network drive.
4. Right click the empty folder, and then click on "Create repository here".
5. Then you can use TortoiseSVN--Repository browser to manage your repository directory. Maybe you can add such directories.
-project_a
-trunk
-branches
-tags
6. After the first 5 steps. you can do any operations just like the repository on a SVN Server.

How to arrange the svn repository layout

怎样组织svn的目录其实非常有学问,比如我们是要将多个项目放在一个svn repository下, 还是为每个项目都建立一个独立的svn repository呢? 实际上, 这两种做法各有优缺点. 

使用单一svn repository的优点是:只需要维护一个svn库,工作量自然会少了很多, 这里维护工作包括routinely backup,dump以及升级svn软件. 当然缺点也很明显: 一个项目的开发人员可以访问另一个项目的文件,另外一个严重的缺点是,每个项目的commit email list很可能不同, 但如果在一个repository下,就不能为不同的项目配置不同的email list, 这样一旦项目A被commit,项目B的也会收到commit email. 

下面是一个多项目的repository结构, 但项目的repository也可参照这个.

要说明的是trunk,branches和tags的意义, trunk里面维护着是项目的主要开发版本. branches是trunk版本的各类分支版本.tags是保存着trunk和branches的某一时刻的快照. 

/
calc/
trunk/
tags/
branches/
calendar/
trunk/
tags/
branches/
spreadsheet/
trunk/
tags/
branches/

Monday, June 30, 2008

Install Subversion server on windows computer

步骤1, 创建一个空目录, 比如d:/source
步骤2, 打开d:\source目录, 使用windows explorer的context menu中tortoisesvn/create a repository here菜单项, 在d:\source中创建必要的repository控制文件和数据库文件.
步骤3, 创建一个svn service服务, 并将服务的工作目录指向d:\source即可. 下面有详细的步骤.

We can register subversion service. the steps are easy. Windows system have a sc command tool to help you register.
If path of subversion contains space or other chars that must be escaped, then you must enclose the path to svnserve.exe with double-quotes, which themselves must be quoted using a backslash. Fortunately the syntax is similar to that on Unix platforms:


Create service methods:
1)On your computer if the path of svnserve.exe does not contain spaces
sc create subversion_local binpath= "c:\svn\bin\svnserve.exe --service -r d:\source" displayname= "Local Subversion Repository" depend= Tcpip
删除这个服务的命令是 sc delete subversion_local
2)On your computer if the path of svnserve.exe contains spaces
sc create subversion_local binpath= "\"c:\program files\subversion\bin\svnserve.exe\" --service -r d:\source" displayname= "Local Subversion Repository" depend= Tcpip

Access the subversion repository by tortoiseSVN, you need a repository url.
1)Browse the repository on local computer,the repository url is just like,
file:///d:/source/docs
2)Browse the repository on remote computer, 前提是computer22机器上的source/docs必须共享出来, 这样才能通过file协议来访问. 访问方式是:
file:///\computer22/source/docs
这是基于file协议的svn访问方式, 多人项目最好不用file协议(因为共享目录安全性较差, 无法做到精细化权限管理). 最好是用svn协议或https协议. 参见我的另一blog( svn authorization)
3)Browse the repository across internet, the repository url is just like,
https://xxxx.svn.sourceforge.net/svnroot/xxxx

more info : http://svn.collab.net/repos/svn/trunk/notes/windows-service.txt

Monday, June 23, 2008

Software dev environment

1. Version control tool
SubVersion and TortoiseSVN
2. A bug administration tool
Mantis, hosted by sourceforge, http://www.mantisbt.org/download.php
Trac, this is a open source bug admin web system. and it provides an interface to subversion, and it has an integrated Wiki and reporting facilities. http://trac.edgewall.org/
3. Continuous Build Tool
CruiseControl.net http://www.codeproject.com/KB/architecture/ContinuousIntegration.aspx

Delphi software dev environment

1. IDE Expert tools
GExpert, www.gexperts.org
DelphiSpeedUp, http://andy.jgknet.de/dspeedup/
DDevExtensions, http://andy.jgknet.de/dspeedup/index.php?page=DDevExtensions
DelforExp, Format Expert

2. Dunit and DUnitLite
http://dunit.sourceforge.net/
http://code.google.com/p/dunitlite

3. madExcept, this is an excellent exception catcher

Tuesday, June 10, 2008

Freesoftware and opensoftwares

优秀开源和免费软件下载网站有:
http://portableapps.com/
http://nirsoft.net/
http://www.pendriveapps.com/
http://www.downloadsquad.com/
http://www.osalt.com/

24 Killer Portable Apps For Your USB Flash Drive
http://www.downloadsquad.com/2008/09/02/24-killer-portable-apps-for-your-usb-flash-drive/


Database Browser(Portable Edition,支持Oracle, SQL Server,ODBC)
http://www.dbsoftlab.com/index.php/Freeware/Database-Browser.html


NetSetManagement (portable Edition) 方便切换多个IP和网关
http://www.pendriveapps.com/2008/03/31/netsetman-portable-network-settings-manager/


WinDirStatPortable(分析磁盘文件的占用情况)
http://portableapps.com/apps/utilities/windirstat_portable


PidginPortable(MSN, QQ等IM 客户端)
http://portableapps.com/apps/internet/pidgin_portable


EraserPortable(securely Delete file/folder)
http://portableapps.com/apps/utilities/eraser_portable


MyUninstaller(非常好用的卸载工具)
http://www.nirsoft.net/utils/myuninst.html


Sumatra PDF Portable (PDF Reader)
http://portableapps.com/apps/office/sumatra_pdf_portable


Keepass--Protect your password
http://keepass.info/

italc--Local network computer projector, for team learning and team computer control.
http://italc.sourceforge.net

Desktops--Virtual Windows desktop manager, just like linux multi-desktop
http://technet.microsoft.com/en-us/sysinternals/cc817881.aspx

EyeDefender--Alert you when you have used computer for a period
http://eterlab.com/

Locate--Just the linux locate command
http://locate32.webhop.org/

WordWeb--English thesaurus and dictionary lookup
http://wordweb.info/free/

Wednesday, June 4, 2008

how to change file folder in subversion

Subversion是一个非常好的Source版本控制软件.
TortoiseSVN是一个Windows平台上Subversion客户端. 操作非常方便, 但有一个问题是: 在TortoiseSVN的菜单中, 怎么也找不到Move file to...或者是change file folder这样的菜单项目, 不知道怎样才能移动文件.
google一搜, 才发现不仅仅是我有这样的困惑.
实际上TortoiseSVN已经考虑了这个问题, 只不过它不是将功能体现在菜单项上, 右键拖拉一个文件到一个versioned folder, 就会出现看到TortoiseSVN已经为你提供了移动功能.

http://svn.haxx.se/tsvnusers/archive-2006-02/0167.shtml

Monday, May 26, 2008

C_Sharp edition StringList Class

Delphi有一个常用的类StringList, 它有一个特点是你可以一行一行加String, 如果你所加的每行String都是Name=Value格式的,它会帮你提取出Name列表和Value列表, 这个类我在用Delphi时候, 常使用它. 下面是C#版的StringList类.



namespace LiuHarry.Utils.Foundation
{
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Text;

public class DelphiStrList
{
private List<string> M_Items = new List<string>();
private List<string> M_Keys = new List<string>();
private string M_KeyValueEqualmark = "=";
private List<string> M_Values = new List<string>();

public virtual void Add(string item)
{
string str;
string str2;
this.M_Items.Add(item);
DivideKeyValue(item, this.M_KeyValueEqualmark, out str2, out str);
this.M_Keys.Add(str2);
this.M_Values.Add(str);
}

public virtual void Clear()
{
this.M_Items.Clear();
this.M_Keys.Clear();
this.M_Values.Clear();
}

protected void DevideKeyValuesByItems()
{
this.M_Keys.Clear();
this.M_Values.Clear();
foreach (string str3 in this.M_Items)
{
string str;
string str2;
DivideKeyValue(str3, this.M_KeyValueEqualmark, out str2, out str);
this.M_Keys.Add(str2);
this.M_Values.Add(str);
}
}

public static void DivideKeyValue(string entireStr, string keyValueEqualmark, out string keyStr, out string valueStr)
{
int index = entireStr.IndexOf(keyValueEqualmark);
if (index > 0)
{
keyStr = entireStr.Substring(0, index);
if (index < (entireStr.Length - 1))
{
valueStr = entireStr.Substring(index + 1);
}
else
{
valueStr = "";
}
}
else
{
keyStr = entireStr;
valueStr = "";
}
}

public virtual int IndexOf(string item)
{
return this.M_Items.IndexOf(item);
}

public virtual void Insert(int index, string item)
{
string str;
string str2;
this.M_Items.Insert(index, item);
DivideKeyValue(item, this.M_KeyValueEqualmark, out str2, out str);
this.M_Keys.Insert(index, str2);
this.M_Values.Insert(index, str);
}

public string MakeItemsToText(string ItemSeperator)
{
return this.MakeStrlistToText(this.M_Items, ItemSeperator);
}

public string MakeKeyItemsToText(string ItemSeperator)
{
return this.MakeStrlistToText(this.M_Keys, ItemSeperator);
}

private string MakeStrlistToText(List<string> items, string ItemSeperator)
{
StringBuilder builder = new StringBuilder();
foreach (string str in items)
{
builder.Append(str);
builder.Append(ItemSeperator);
}
return builder.ToString();
}

public string MakeValueItemsToText(string ItemSeperator)
{
return this.MakeStrlistToText(this.M_Keys, ItemSeperator);
}

public virtual bool Remove(string item)
{
int index = this.IndexOf(item);
if (index >= 0)
{
this.M_Keys.RemoveAt(index);
this.M_Values.RemoveAt(index);
}
return this.M_Items.Remove(item);
}

public virtual void RemoveAt(int index)
{
this.M_Items.RemoveAt(index);
this.M_Keys.RemoveAt(index);
this.M_Values.RemoveAt(index);
}

public virtual void Sort()
{
this.M_Items.Sort();
this.DevideKeyValuesByItems();
}

public int Count
{
get
{
return this.M_Items.Count;
}
}

public List<string> Items
{
get
{
return this.M_Items;
}
}

public List<string> Keys
{
get
{
return this.M_Keys;
}
}

public string KeyValueEqualmark
{
get
{
return this.M_KeyValueEqualmark;
}
set
{
this.M_KeyValueEqualmark = value;
}
}

public List<string> Values
{
get
{
return this.M_Values;
}
}
}
}

How to hide or show TabPage of TabControl

C# 的TabControl功能严重不足, 尤其是你用它来制作Wizard界面时候, 下面代码会有所帮助的.


namespace LiuHarry.Utils.Components
{
using System;
using System.Windows.Forms;

public class TabControlHelper
{
private TabControl m_tabControl;

public TabControlHelper(TabControl tabCtrl)
{
this.m_tabControl = tabCtrl;
}

public void HideTabPage(TabPage tp)
{
if (this.m_tabControl.TabPages.Contains(tp))
{
this.m_tabControl.TabPages.Remove(tp);
}
}

private void InsertTabPage(TabPage tabpage, int index)
{
if ((index < 0) || (index > this.m_tabControl.TabCount))
{
throw new ArgumentException("Index out of Range.");
}
this.m_tabControl.TabPages.Add(tabpage);
if (index < (this.m_tabControl.TabCount - 1))
{
do
{
this.SwapTabPages(tabpage, this.m_tabControl.TabPages[this.m_tabControl.TabPages.IndexOf(tabpage) - 1]);
}
while (this.m_tabControl.TabPages.IndexOf(tabpage) != index);
}
this.m_tabControl.SelectedTab = tabpage;
}

public void ShowTabPage(TabPage tp)
{
this.ShowTabPage(tp, this.m_tabControl.TabPages.Count);
}

public void ShowTabPage(TabPage tp, int index)
{
if (!this.m_tabControl.TabPages.Contains(tp))
{
this.InsertTabPage(tp, index);
}
}

private void SwapTabPages(TabPage tp1, TabPage tp2)
{
if (!(this.m_tabControl.TabPages.Contains(tp1) && this.m_tabControl.TabPages.Contains(tp2)))
{
throw new ArgumentException("TabPages must be in the TabControls TabPageCollection.");
}
int index = this.m_tabControl.TabPages.IndexOf(tp1);
int num2 = this.m_tabControl.TabPages.IndexOf(tp2);
this.m_tabControl.TabPages[index] = tp2;
this.m_tabControl.TabPages[num2] = tp1;
this.m_tabControl.SelectedIndex = this.m_tabControl.SelectedIndex;
string text = tp1.Text;
string str2 = tp2.Text;
tp1.Text = str2;
tp2.Text = text;
}
}
}

DataGridViewHelper class

DataGridView这个控件在开发中经常会被用到, C#的组件总觉得不如Delphi组件那么容易使用, 怎样在Grid上选择一个Row, 怎样选择一个Cell, 我就被block了好久. 下面是代码:

namespace LiuHarry.Utils.Components
{
using System;
using System.Collections;
using System.Collections.Generic;
using System.Data;
using System.Windows.Forms;

public class DataGridViewHelper
{
private DataGridView m_DataGridView;
private SortedList<string, string> m_MapListFieldColumn = new SortedList<string, string>();
private DataView m_ShowedDataViewInGrid;

public DataGridViewHelper(DataGridView dataGridView, DataView showedDataViewInGrid)
{
this.m_DataGridView = dataGridView;
this.m_ShowedDataViewInGrid = showedDataViewInGrid;
this.InitFieldColumnMapList();
}

private string _GetGridViewColumnName(string dataFieldName)
{
for (int i = 0; i < this.m_DataGridView.Columns.Count; i++)
{
if (this.m_DataGridView.Columns[i].DataPropertyName == dataFieldName)
{
return this.m_DataGridView.Columns[i].Name;
}
}
return "";
}

private void InitFieldColumnMapList()
{
this.m_MapListFieldColumn.Clear();
for (int i = 0; i < this.m_ShowedDataViewInGrid.Table.Columns.Count; i++)
{
string columnName = this.m_ShowedDataViewInGrid.Table.Columns[i].ColumnName;
string str2 = this._GetGridViewColumnName(columnName);
this.m_MapListFieldColumn.Add(columnName, str2);
}
}

public string QuickFindGridViewColumnName(string dataFieldName)
{
int num = this.m_MapListFieldColumn.IndexOfKey(dataFieldName);
if (num < 0)
{
throw new Exception("There no a column in DataGridView corresponding DataFieldName=" + dataFieldName);
}
return this.m_MapListFieldColumn.Values[num];
}

public void SelectAndScrollToCell(string fieldName, object fieldValue)
{
this.m_DataGridView.ClearSelection();
if ((fieldName != null) && (fieldValue != null))
{
string str = this.QuickFindGridViewColumnName(fieldName);
foreach (DataGridViewRow row in (IEnumerable) this.m_DataGridView.Rows)
{
if (object.Equals(row.Cells[str].Value, fieldValue))
{
this.m_DataGridView.CurrentCell = row.Cells[str];
this.m_DataGridView.CurrentCell.Selected = true;
break;
}
}
}
}

public void SelectRow(object valueOfSortedField)
{
DataView showedDataViewInGrid = this.m_ShowedDataViewInGrid;
this.m_DataGridView.ClearSelection();
if ((showedDataViewInGrid != null) && (valueOfSortedField != null))
{
int num = showedDataViewInGrid.Find(valueOfSortedField);
if (num >= 0)
{
this.m_DataGridView.Rows[num].Selected = true;
}
}
}

public SortedList<string, string> MapListFieldColumn
{
get
{
return this.m_MapListFieldColumn;
}
}
}
}

SQL Execute Trace Helper Class

http://focuswindows.blogspot.com/2008/01/sql-executing-monitor-code.html
该版本可能不是最新的, 最新的代码应该是下面:



namespace LiuHarry.Utils.DB
{
using LiuHarry.Utils.Foundation;
using System;
using System.Data;
using System.Diagnostics;
using System.IO;
using System.Reflection;

internal class SqlExecuteTracer : IDisposable
{
private bool disposed = false;
private string m_ErrorLogFile;
private readonly string m_ErrorLogFileSuffix = "SqlError.txt";
private bool m_LogSqlExecute = false;
private string m_SqlLogFile;
private readonly string m_SqlLogFileSuffix = "SqlLog.txt";
private TextWriterTraceListener m_SqlLogListener;

public SqlExecuteTracer()
{
string assemblyFileWithoutExt = this.GetAssemblyFileWithoutExt();
this.m_ErrorLogFile = assemblyFileWithoutExt + "_" + this.m_ErrorLogFileSuffix;
this.m_SqlLogFile = assemblyFileWithoutExt + "_" + this.m_SqlLogFileSuffix;
}

public void Dispose()
{
this.Dispose(true);
GC.SuppressFinalize(this);
}

private void Dispose(bool disposing)
{
if (!this.disposed && this.m_LogSqlExecute)
{
this.m_SqlLogListener.Close();
}
this.disposed = true;
}

private string GetAssemblyFileWithoutExt()
{
string location = Assembly.GetExecutingAssembly().Location;
return (Path.GetDirectoryName(location) + Path.DirectorySeparatorChar + Path.GetFileNameWithoutExtension(location));
}

private string GetSqlFromCommand(IDbCommand cmd)
{
string commandText = cmd.CommandText;
if (cmd.Parameters.Count <= 0)
{
return commandText;
}
DelphiStrList list = new DelphiStrList();
foreach (IDbDataParameter parameter in cmd.Parameters)
{
if (parameter.Value != null)
{
list.Add(parameter.ParameterName + "=" + parameter.Value.ToString());
}
else
{
list.Add(parameter.ParameterName + "=NULL");
}
}
return (commandText + Environment.NewLine + list.MakeItemsToText(Environment.NewLine));
}

public void TraceErrorSqlToLog(IDbCommand cmd, string ErrorMessage)
{
using (TraceListener listener = new TextWriterTraceListener(this.m_ErrorLogFile))
{
string sqlFromCommand = this.GetSqlFromCommand(cmd);
this.TraceSqlToLogListener(sqlFromCommand, ErrorMessage, listener);
listener.Close();
}
}

public void TraceErrorSqlToLog(string sql, string ErrorMessage)
{
using (TraceListener listener = new TextWriterTraceListener(this.m_ErrorLogFile))
{
this.TraceSqlToLogListener(sql, ErrorMessage, listener);
listener.Close();
}
}

public void TraceSqlToLog(IDbCommand cmd)
{
if (this.m_LogSqlExecute)
{
string sqlFromCommand = this.GetSqlFromCommand(cmd);
this.TraceSqlToLogListener(sqlFromCommand, null, this.m_SqlLogListener);
}
}

public void TraceSqlToLog(string sql)
{
if (this.m_LogSqlExecute)
{
this.TraceSqlToLogListener(sql, null, this.m_SqlLogListener);
}
}

private void TraceSqlToLogListener(string sql, string ErrorInfo, TraceListener listener)
{
listener.WriteLine("===================================");
listener.WriteLine(DateTime.Now.ToString());
if (!string.IsNullOrEmpty(ErrorInfo))
{
listener.WriteLine("Error Info:");
listener.WriteLine(ErrorInfo);
listener.WriteLine("SQL:");
}
listener.WriteLine(sql);
listener.WriteLine("===================================");
listener.WriteLine("");
listener.Flush();
}

public bool LogSqlExecute
{
get
{
return this.m_LogSqlExecute;
}
set
{
this.m_LogSqlExecute = value;
if (this.m_LogSqlExecute && (this.m_SqlLogListener == null))
{
this.m_SqlLogListener = new TextWriterTraceListener(this.m_SqlLogFile);
}
}
}
}
}

SQLite Dataset Helper Class



namespace LiuHarry.Utils.DB
{
using System;
using System.Collections.Generic;
using System.Data;
using System.Data.SQLite;
using System.Runtime.InteropServices;
using System.Text;

public class SqliteDataSetHelper : IDisposable
{
private readonly string DELETED_FLAG;
private bool disposed;
private readonly string INSERTED_FLAG;
private string m_ConnectionString;
private bool m_LogSqlExecute;
private bool m_SelfBuildConnection;
private SQLiteConnection m_SqlConn;
private SqlExecuteTracer m_SqlExecuteTracer;
private readonly string MODIFIED_FLAG;
private readonly string PARAMETER_NAME_PREFIX;
private readonly string PARAMETER_PREFIX;

public SqliteDataSetHelper()
{
this.m_LogSqlExecute = false;
this.m_SelfBuildConnection = true;
this.DELETED_FLAG = "DELETED_FLAG";
this.INSERTED_FLAG = "INSERTED_FLAG";
this.MODIFIED_FLAG = "MODIFIED_FLAG";
this.PARAMETER_PREFIX = "@";
this.PARAMETER_NAME_PREFIX = "@";
this.disposed = false;
this.m_SqlExecuteTracer = new SqlExecuteTracer();
}

public SqliteDataSetHelper(SQLiteConnection conn) : this()
{
if (conn != null)
{
this.m_SelfBuildConnection = false;
this.m_SqlConn = conn;
}
}

public SqliteDataSetHelper(string connectionString) : this()
{
this.m_ConnectionString = connectionString;
}

private void ActivateConnection()
{
if (this.m_SqlConn == null)
{
this.m_SqlConn = new SQLiteConnection(this.m_ConnectionString);
this.m_SelfBuildConnection = true;
}
}

public void DeleteRows(List<DataRow> listRows, string tableName, string keyFieldName)
{
foreach (DataRow row in listRows)
{
this.SetToDeleteState(row);
this.InnerDeleteRow(row, tableName, keyFieldName);
}
}

public void DeleteRows(List<long> listRowKeyFieldValue, string tableName, string keyFieldName)
{
foreach (int num in listRowKeyFieldValue)
{
this.InnerDeleteRowByKeyField(num, tableName, keyFieldName, typeof(long));
}
}

public void DeleteRows(List<string> listRowKeyFieldValue, string tableName, string keyFieldName)
{
foreach (string str in listRowKeyFieldValue)
{
this.InnerDeleteRowByKeyField(str, tableName, keyFieldName, typeof(string));
}
}

public void DeleteRows(DataRow row, string tableName, string keyFieldName)
{
this.SetToDeleteState(row);
this.InnerDeleteRow(row, tableName, keyFieldName);
}

public void Dispose()
{
this.Dispose(true);
GC.SuppressFinalize(this);
}

private void Dispose(bool disposing)
{
if (!this.disposed)
{
this.TryCloseConnection();
}
this.disposed = true;
}

public int ExecCommand(SQLiteCommand sqlcom)
{
int num2;
this.ActivateConnection();
sqlcom.Connection = this.m_SqlConn;
try
{
this.TryOpenConnection();
this.m_SqlExecuteTracer.TraceSqlToLog(sqlcom);
num2 = sqlcom.ExecuteNonQuery();
}
catch (Exception exception)
{
this.m_SqlExecuteTracer.TraceErrorSqlToLog(sqlcom, exception.Message);
throw exception;
}
finally
{
this.TryCloseConnection();
}
return num2;
}

public int ExecCommand(string sql)
{
if (sql.EndsWith(","))
{
sql = sql.Substring(0, sql.Length - 1);
}
SQLiteCommand sqlcom = new SQLiteCommand(sql);
return this.ExecCommand(sqlcom);
}

public SQLiteDataReader ExecDataReader(SQLiteCommand sqlcom)
{
SQLiteDataReader reader;
this.ActivateConnection();
sqlcom.Connection = this.m_SqlConn;
try
{
this.TryOpenConnection();
this.m_SqlExecuteTracer.TraceSqlToLog(sqlcom);
reader = sqlcom.ExecuteReader();
}
catch (Exception exception)
{
this.m_SqlExecuteTracer.TraceErrorSqlToLog(sqlcom, exception.Message);
throw exception;
}
finally
{
this.TryCloseConnection();
}
return reader;
}

public SQLiteDataReader ExecDataReader(string sqlSelect)
{
if (sqlSelect.EndsWith(","))
{
sqlSelect = sqlSelect.Substring(0, sqlSelect.Length - 1);
}
SQLiteCommand sqlcom = new SQLiteCommand(sqlSelect);
return this.ExecDataReader(sqlcom);
}

public DataTable ExecDataTable(SQLiteCommand sqlcom)
{
DataTable table2;
DataTable dataTable = new DataTable();
SQLiteDataAdapter adapter = new SQLiteDataAdapter(sqlcom);
this.ActivateConnection();
sqlcom.Connection = this.m_SqlConn;
try
{
this.TryOpenConnection();
this.m_SqlExecuteTracer.TraceSqlToLog(sqlcom);
adapter.Fill(dataTable);
table2 = dataTable;
}
catch (Exception exception)
{
this.m_SqlExecuteTracer.TraceErrorSqlToLog(sqlcom, exception.Message);
throw exception;
}
finally
{
this.TryCloseConnection();
}
return table2;
}

public DataTable ExecDataTable(string sqlSelect)
{
if (sqlSelect.EndsWith(","))
{
sqlSelect = sqlSelect.Substring(0, sqlSelect.Length - 1);
}
SQLiteCommand sqlcom = new SQLiteCommand(sqlSelect);
return this.ExecDataTable(sqlcom);
}

public object ExecScalar(SQLiteCommand sqlcom)
{
object obj2;
this.ActivateConnection();
sqlcom.Connection = this.m_SqlConn;
try
{
this.TryOpenConnection();
this.m_SqlExecuteTracer.TraceSqlToLog(sqlcom);
obj2 = sqlcom.ExecuteScalar();
}
catch (Exception exception)
{
this.m_SqlExecuteTracer.TraceErrorSqlToLog(sqlcom, exception.Message);
throw exception;
}
finally
{
this.TryCloseConnection();
}
return obj2;
}

public object ExecScalar(string sqlSelect)
{
if (sqlSelect.EndsWith(","))
{
sqlSelect = sqlSelect.Substring(0, sqlSelect.Length - 1);
}
SQLiteCommand sqlcom = new SQLiteCommand(sqlSelect);
return this.ExecScalar(sqlcom);
}

~SqliteDataSetHelper()
{
this.Dispose(false);
}

public DateTime GetDbTime()
{
string commandText = "select datetime('NOW') ";
SQLiteCommand sqlcom = new SQLiteCommand(commandText);
return (DateTime) this.ExecScalar(sqlcom);
}

private DbType GetDbType(Type type)
{
DbType guid = DbType.String;
if (type.Equals(typeof(int)) || type.IsEnum)
{
return DbType.Int32;
}
if (type.Equals(typeof(long)))
{
return DbType.Int32;
}
if (type.Equals(typeof(double)) || type.Equals(typeof(double)))
{
return DbType.Decimal;
}
if (type.Equals(typeof(DateTime)))
{
return DbType.DateTime;
}
if (type.Equals(typeof(bool)))
{
return DbType.Boolean;
}
if (type.Equals(typeof(string)))
{
return DbType.String;
}
if (type.Equals(typeof(decimal)))
{
return DbType.Decimal;
}
if (type.Equals(typeof(byte[])))
{
return DbType.Binary;
}
if (type.Equals(typeof(Guid)))
{
guid = DbType.Guid;
}
return guid;
}

public long GetMaxID(string primaryKeyField, string tableName)
{
SQLiteCommand sqlcom = new SQLiteCommand("Select Max(" + primaryKeyField + ") from " + tableName);
return (long) this.ExecScalar(sqlcom);
}

private void InnerDeleteRow(DataRow dr, string TableName, string keyFieldName)
{
string format = "Delete from {0} where {1} =" + this.PARAMETER_PREFIX + "{1}";
DataTable table = dr.Table;
SQLiteCommand sqlcom = new SQLiteCommand(string.Format(format, TableName, keyFieldName));
IDataParameter parameter = new SQLiteParameter();
parameter.ParameterName = this.PARAMETER_NAME_PREFIX + keyFieldName;
parameter.DbType = this.GetDbType(table.Columns[keyFieldName].DataType);
parameter.Value = dr[keyFieldName];
sqlcom.Parameters.Add(parameter);
this.ExecCommand(sqlcom);
}

private void InnerDeleteRowByKeyField(object RowKeyFieldValue, string tableName, string keyFieldName, Type keyFieldType)
{
SQLiteCommand sqlcom = new SQLiteCommand(string.Format("Delete from {0} where {1} =" + this.PARAMETER_PREFIX + "{1}", tableName, keyFieldName));
IDataParameter parameter = new SQLiteParameter();
parameter.ParameterName = this.PARAMETER_NAME_PREFIX + keyFieldName;
parameter.DbType = this.GetDbType(keyFieldType);
parameter.Value = RowKeyFieldValue;
sqlcom.Parameters.Add(parameter);
this.ExecCommand(sqlcom);
}

private void InnerInsertRow(DataRow dr, string TableName, string primaryKeyField, bool primaryKeyValueIsAutoGenerated, out long primaryKeyValue)
{
string format = "Insert into {0}({1}) values ({2})";
SQLiteCommand sqlcom = new SQLiteCommand();
DataTable table = dr.Table;
StringBuilder builder = new StringBuilder();
StringBuilder builder2 = new StringBuilder();
for (int i = 0; i < dr.Table.Columns.Count; i++)
{
if (!primaryKeyValueIsAutoGenerated || !(table.Columns[i].ColumnName == primaryKeyField))
{
IDataParameter parameter = new SQLiteParameter();
parameter.ParameterName = this.PARAMETER_NAME_PREFIX + table.Columns[i].ColumnName;
parameter.DbType = this.GetDbType(table.Columns[i].DataType);
parameter.Value = dr[i];
sqlcom.Parameters.Add(parameter);
builder2.Append(table.Columns[i].ColumnName);
builder.Append(this.PARAMETER_PREFIX + table.Columns[i].ColumnName);
builder2.Append(",");
builder.Append(",");
}
}
string str2 = builder2.ToString();
str2 = str2.Substring(0, str2.Length - 1);
string str3 = builder.ToString();
str3 = str3.Substring(0, str3.Length - 1);
string str4 = string.Format(format, TableName, str2, str3);
sqlcom.CommandText = str4;
this.ExecCommand(sqlcom);
if (!primaryKeyValueIsAutoGenerated)
{
primaryKeyValue = 0L;
}
else
{
SQLiteCommand command2 = new SQLiteCommand("Select Max(" + primaryKeyField + ") from " + TableName);
object obj2 = this.ExecScalar(command2);
primaryKeyValue = (long) obj2;
}
}

private void InnerModifyRow(DataRow dr, string TableName, string keyFieldName)
{
string format = "Update {0} set {1} {2}";
string str2 = "{0}= " + this.PARAMETER_PREFIX + "{0}";
string str3 = " Where {0}=" + this.PARAMETER_PREFIX + "{0}";
StringBuilder builder = new StringBuilder();
SQLiteCommand sqlcom = new SQLiteCommand();
DataTable table = dr.Table;
for (int i = 0; i < dr.Table.Columns.Count; i++)
{
IDataParameter parameter = new SQLiteParameter();
parameter.ParameterName = this.PARAMETER_NAME_PREFIX + table.Columns[i].ColumnName;
parameter.DbType = this.GetDbType(table.Columns[i].DataType);
parameter.Value = dr[i];
sqlcom.Parameters.Add(parameter);
if (table.Columns[i].ColumnName == keyFieldName)
{
str3 = string.Format(str3, keyFieldName);
}
else
{
builder.Append(string.Format(str2, table.Columns[i].ColumnName));
builder.Append(",");
}
}
string str4 = builder.ToString();
str4 = str4.Substring(0, str4.Length - 1);
string str5 = string.Format(format, TableName, str4, str3);
sqlcom.CommandText = str5;
this.ExecCommand(sqlcom);
}

public void InsertRows(List<DataRow> listRows, string tableName)
{
foreach (DataRow row in listRows)
{
this.InsertRows(row, tableName);
}
}

public void InsertRows(DataRow row, string tableName)
{
long num;
this.SetToInsertState(row);
string primaryKeyField = "";
bool primaryKeyValueIsAutoGenerated = false;
this.InnerInsertRow(row, tableName, primaryKeyField, primaryKeyValueIsAutoGenerated, out num);
}

public void InsertRows(List<DataRow> listRows, string tableName, string primaryKeyField, bool primaryKeyValueIsAutoGenerated, out List<long> listPrimaryKeyValue)
{
listPrimaryKeyValue = new List<long>();
foreach (DataRow row in listRows)
{
long num;
this.SetToInsertState(row);
this.InnerInsertRow(row, tableName, primaryKeyField, primaryKeyValueIsAutoGenerated, out num);
listPrimaryKeyValue.Add(num);
}
}

public void InsertRows(DataRow row, string tableName, string primaryKeyField, bool primaryKeyValueIsAutoGenerated, out long primaryKeyValue)
{
this.SetToInsertState(row);
this.InnerInsertRow(row, tableName, primaryKeyField, primaryKeyValueIsAutoGenerated, out primaryKeyValue);
}

public void ModifyRows(List<DataRow> listRows, string tableName, string keyFieldName)
{
foreach (DataRow row in listRows)
{
this.SetToModifyState(row);
this.InnerModifyRow(row, tableName, keyFieldName);
}
}

public void ModifyRows(DataRow row, string tableName, string keyFieldName)
{
this.SetToModifyState(row);
this.InnerModifyRow(row, tableName, keyFieldName);
}

public void SetToDeleteState(DataRow row)
{
row.RowError = this.DELETED_FLAG;
}

public void SetToInsertState(DataRow row)
{
row.RowError = this.INSERTED_FLAG;
}

public void SetToModifyState(DataRow row)
{
row.RowError = this.MODIFIED_FLAG;
}

private void TryCloseConnection()
{
if (this.m_SelfBuildConnection && (this.m_SqlConn != null))
{
this.m_SqlConn.Close();
}
}

private void TryOpenConnection()
{
if (this.m_SelfBuildConnection)
{
this.m_SqlConn.Open();
}
}

public string ConnectionString
{
get
{
return this.m_ConnectionString;
}
set
{
this.m_ConnectionString = value;
}
}

public bool LogSqlExecute
{
get
{
return this.m_LogSqlExecute;
}
set
{
this.m_LogSqlExecute = value;
this.m_SqlExecuteTracer.LogSqlExecute = this.m_LogSqlExecute;
}
}
}
}