分类: 编程开发预览模式: 普通 | 列表

[REAL Studio]保存文件位置信息

有时候我们需要保存文件的位置信息以便在程序下次运行时找到这个文件,比如文件菜单中的“最近打开的文件”之类。如果这个文件需要让各种程序都能找到,那么只能保存文件的绝对路径。如果只要被REAL Studio程序使用,那么应该保存SaveInfo。

SaveInfo需要通过FolderItem类的GetSaveInfo方法来获取,这个方法有两个参数,第一个是相对路径(也是一个FolderItem实例),后一个是个可选的模式参数。

举例如下(假设有个全局字符串变量s):

  Dim f,d as FolderItem
  f=Volume(0).Child("Doc").Child("abc.txt")
  d=Volume(0).Child("Doc")
  s=f.GetSaveInfo(d)
  
在Windows上,f是C:\Doc\abc.txt,d是其所在的文件夹,但其实d可以是任何一个文件或文件夹。上述代码执行后,s中就保存了文件f的SaveInfo,可以将其写入程序设置文件、某个数据库、注册表或defaults中,以便于以后读取。

要从SaveInfo中解析出文件,只要这样:

  Dim f As New FolderItem
  f=f.GetRelative(s)

那么使用SaveInfo有什么好处呢?主要有两点:

1)用于通过相对路径找到文件。比如某个文件夹A中有文件b,在调用GetSaveInfo时,将A传入,即s=b.GetSaveInfo(A);随后如果你整体移动了文件夹A(b仍在A内),在试图解析出b时,通过b=A.GetRelative(s),仍然可以找到b,当然这里的A必须是指向移动后的文件夹。

2)即便不需要上面这个好处,也应该使用GetSaveInfo,它不光保存了相对、绝对路径,还保存了其他信息,使得更名或移动过的文件(主要在Unix系统上)仍能被找到。比如在Mac上,使用s=f.GetSaveInfo(Volume(0))并将s的值保存起来,以后即使文件f被改名或移动(严格的说是在当前磁盘分区,或者说卷内移动,当然在Mac上跨磁盘移动不容易,要Command+Shift+拖曳文件),仍然能通过所保存的SaveInfo解析到更名或移动后的文件。对这种用法,向GetSaveInfo中传入什么都没问题,此处传入Volume(0),是因为Volume(0)是系统盘根目录,只要不是无盘工作站之类,这个FolderItem都不会为Nil,而且它的“位置”通常极少可能会改变。

因此在REAL Studio中首选应该保存SaveInfo而不是AbsolutePath。这样至少在Mac上,不会由于文件移动或更名而找不到。大部分Mac软件的Open Recent子菜单都不会由于文件被移动而无效,但一些只追求跨平台而忽视native特性的公司的产品或许会这样,比如Adobe的一些软件。

查看更多...

分类:编程开发 | 固定链接 | 评论: 0 | 引用: 0 | 查看次数: 3578

通过编程访问Google账户和服务

本文介绍通过桌面程序访问Google账户和Google服务的方法。以Google Calendar服务为例,使用Google Calendar API 2.0版。

1、Google账户登录
通过程序登录Google账户的方法有很多,最简单的方法是使用ClientLogin这个API,在桌面程序中一般可以使用这种方式。为了测试,可以直接在浏览器中调用这个API来看结果,URL类似这样:

https://www.google.com/accounts/ClientLogin?accountType=HOSTED_OR_GOOGLE&Email=YourAccount@gmail.com&Passwd=PASSWORD&service=xapi&source=YourCompany-YourApp-Version

其中Email=YourAccount@gmail.com是你的Google账户Email地址,Passwd=PASSWORD是账户的密码。service是要访问的服务名,对于Google Calendar服务,要传递cl。source是登录来源,也就是你的程序的名称之类。注意上面的关键字是区分大小写的,比如“Email=”中的“E”一定要大写。

以REALbasic语言为例,来看看怎么用程序来处理。在这里我们使用同步模式的HTTPSecureSocket来发送请求。
Dim https As New HTTPSecureSocket
https.Yield=True // 同意让渡CPU时间,以防在发送请求过程中界面长时间停止响应
Const PostURL="https://www.google.com/accounts/ClientLogin"
Dim form As New Dictionary // 使用字典组装表单,这比用“+”运算符连接字符串来组装URL要方便
form.Value("accountType")="HOSTED_OR_GOOGLE"
form.Value("Email")=“YourAccount@gmail.com"
form.Value("Passwd")="PASSWORD"
form.Value("service")="cl" // 指定访问Google Calendar服务
form.Value("source")="REALSoftware-REALStudio-"+RBVersionString
https.SetFormData(form) // 将表单指派给https,会对特殊字符编码,比如空格会被转为“%20”。
Dim ResultValue As String
ResultValue=https.Post(PostURL,20) // POST表单到指定地址,20秒无返回则自动超时

如果成功(https.HTTPStatusCode=200),ResultValue中将得到类似这样的3行返回结果(失败的话https.HTTPStatusCode=403并会返回错误信息):
SID=DQAAAH4AAADifYYKy7p9Lg7KdT4BW3zb7RvFmKJm7iJgQ2hjpTG6CLhVnQPFybD8t_odSpU5ZgODxcXnGi50cc-Ky8u-O0KaQiCEydE9RurCkoLEywY7a6trnN6mQW5SmcgjiNCwqiIzhAvQcu-0RFrvKQNJX78GQE4fFE_fs4uX2GTYleQGQg
LSID=DQAAAIAAAABWZhQlKvg2elXVWVttR23V2AWMu57XXvOiJJbGrHlUZW-96nsaokBwCzxs8_edWSDZOU3lU71R14u8QntrODC7WAFlBMkG04GZzIND7WPqX73t--dUAkYVlDoyfwP4N7bTNrg5VsOeeYervrxs_ADg8MBpqlnw-bbjIcyIub8org
Auth=DQAAAIAAAABWZhQlKvg2elXVWVttR23V2AWMu57XXvOiJJbGrHlUZW-96nsaokBwCzxs8_edWSC5iZ0YUERmdfIogOo05uk-YFGm0na7lYS_yT-_BNOLSBZeDcwtOOp9osxbv_7oUdGHbfhOoDza6G-ISUZM4dYgkDNmWyOqjTUwwbWAbbjU4w

这里我们只需要使用最后一行“Auth=”后面的内容(称为验证令牌)。将它解析出来保存到字符串变量AuthToken中(解析过程略),然后要设置一下后续的Google账户访问时需要发送的HTTP报头:
https.SetRequestHeader("Authorization","GoogleLogin auth="+AuthToken) // 将验证令牌放入Authorization请求头中
https.SetRequestHeader("GData-Version","2") // 如本文开头所说,我们要使用2.0版的API,所以设置GData-Version头

2、调用Google Calendar API
下面这个API用于获取已登录的用户的所有日历:
https://www.google.com/calendar/feeds/default/allcalendars/full
要使用这个API,只要继续用上面那个设置好验证令牌的HTTPSecureSocket实例向这个地址发送GET命令即可。
Const AllCalendarURL="https://www.google.com/calendar/feeds/default/allcalendars/full"
dim v As String=https.Get(AllCalendarURL,20) // 这次用GET(不要用POST),超时仍然选20秒

但得到的返回值v中的多半不是XML格式的日历列表,而是像这样一个HTML页面:
- <HTML>
- <HEAD>
- <TITLE>Moved Temporarily</TITLE>
- </HEAD>
- <BODY BGCOLOR="#FFFFFF" TEXT="#000000">
- <H1>Moved Temporarily</H1>
The document has moved <A HREF="https://www.google.com/calendar/feeds/default/allcalendars/full?gsessionid=gC7NAlsQASjnE_wXPF1kxQ">here</A>.
- </BODY>
- </HTML>

此时,如果检查https的HTTPStatusCode属性,会发现它的值是302,而不是200(OK,正常)。 302在HTTP协议里是Redirect(重定向)的意思,重定向到了上面那个HTML中显示的地址。我们不需要从HTML里解析它,一则这样太麻烦,二则Google可以自己决定对于302这个状态应该返回什么样的正文文本,如果Google做了些改动,我们解析方法可能就失效了。那怎么办呢?应该想到HTTP协议返回的内容不光只有页面正文,还有页面报头。我的经验是,进行通过HTTP访问某个网站开放的API一类的开发,经常检查一下返回的页面的报头是个好习惯。可以通过HTTPSecureSocket的PageHeaders方法,或在调试器中看_pageHeaders属性来查看报头,对于上面的GET操作,我们会看到这样一组报头:
Expires: Sat, 31 Jul 2010 12:43:48 GMT
Date: Sat, 31 Jul 2010 12:43:48 GMT
Set-Cookie: S=calendar=gC7NAlsQASjnE_wXPF1kxQ;Expires=Sun, 31-Jul-2011 12:43:48 GMT;Secure
Location: https://www.google.com/calendar/feeds/default/allcalendars/full?gsessionid=gC7NAlsQASjnE_wXPF1kxQ
Content-Type: text/html; charset=UTF-8
Cache-Control: private, max-age=0
X-Content-Type-Options: nosniff
X-Frame-Options: SAMEORIGIN
X-XSS-Protection: 1; mode=block
Server: GSE

可以看到Location头的值正是跳转目标的URL,和HTML中显示的URL一致。
那么我们也进行“重定向”,向新地址发送GET命令:
Dim headers As InternetHeaders=https.PageHeaders // 读出所有InternetHeaders
Dim Result As String
If headers<>Nil Then
  Dim RedirectTo As String=headers.Value("Location") // 读取Location头,以得到重定向地址
  If RedirectTo<>"" Then Result=https.Get(RedirectTo,20) // 继续GET,向新地址请求数据
End If

但是结果很可惜,我们得到了又一个类似上面那样的HTML和一个新的302,如果继续下去你会得到永无止境的大体相同的HTML和302状态。为什么会这样呢?比较一下各次得到的正文内容或Location头,你会发现差异在URL里的gsessionid=xxxxxxxx一段。这段内容给出了会话ID(或者叫会话种子,Seed),这意味着我们必须使用这个会话才能继续执行后续的API。

要注意,HTTP协议是一种无连接协议,客户端发送请求时建立连接,服务器响应请求返回了数据后,连接立即断开。对于Google API,为了避免每执行一个API函数就进行一次登陆验证,服务器端会以会话ID为索引保存一个上下文。相应地,在客户端向服务器发送请求时也必须同时发送这个会话ID,以使服务器知道它所保存的哪个上下文和这个客户端对应。因此客户端也要保存这个会话ID和一些信息,以便在发送请求时使用,客户端所保存的东西被称为Cookie。

在REALbasic语言的HTTPSecureSocket类中怎么使用这些“曲奇饼干”呢?回过头来再看上面的那组报头,发现有一个名为Set-Cookie报头,可以看到其中也包含gsessionid的内容。这就是我们要保存并在后续访问中使用的Cookie了。所谓的“在后续访问中使用”,意思就是在后续的请求中作为报头发送给服务器。因此可以不必使用额外的变量,只要像上面保存验证令牌和GData版本号那样将Cookie缓存到https对象的RequestHeader里面就行了。现在修改上面的代码如下:
Dim headers As InternetHeaders=https.PageHeaders // 读出所有InternetHeaders
Dim Result As String
If headers<>Nil Then
  Dim Cookie As String=headers.Value("Set-Cookie") // 从Set-Cookie头读出Google要求我们使用的Cookie
  Dim RedirectTo As String=headers.Value("Location") // 从Location读出重定向地址
  If RedirectTo<>"" Then
    If Cookie<>"" Then https.SetRequestHeader("Cookie",Cookie) // 将Cookie写入请求报头中
    Result=https.Get(RedirectTo,20) // 向新地址请求数据
  End If
End If

这回Result中将收到含有日历列表的一个XML格式的字符串(实际上是Google Data格式的,也可以是RSS或Atom格式)。接下来只要用XMLDocument和相关的类进行解析,就能获取我们想要的数据了。


(转载请注明来源:http://www.3exware.com/home/default.asp?id=77

查看更多...

Tags: REALbasic Google

分类:编程开发 | 固定链接 | 评论: 0 | 引用: 0 | 查看次数: 9650

[转自上海棠盈阁软件咨询有限公司内部Wiki]

就内容而言,本文可能会包含:将RB与TortoiseSVN一同使用的注意事项、使用RB版本控制系统工程的注意事项和使用TortoiseSVN的注意事项 :-)

基本原则:使用TortoiseSVN的基本原则之一,就是所有对已由版本控制系统管理的文件的删除、重命名、移动等操作应一律使用TortoiseSVN相关的菜单,而不是资源管理器本身提供的功能来完成。

1. 新增文件或文件夹
在RB的版本控制系统(rbvcp)工程中添加了新工程项后,RB会自动在磁盘上添加一个与工程项对应的文件。如果在工程中添加了文件夹,那么RB会自动创建一个文件夹。因此在递交前要注意将这些新增文件或文件夹添加到版本控制系统中(或者说工作拷贝的仓库中),做法有二:

  • 右键单击资源管理器中的合适的目录(即含有新增文件或文件夹的目录),在弹出的右键菜单中使用TortoiseSVN子菜单中的Add命令添加所有新增项目到版本控制系统中
  • 或者,右键单击资源管理器中的合适的目录(比如整个项目的顶级目录),在弹出的右键菜单中使用Commit命令,在随后弹出的对话框中勾选左下角的“Select / deselect All”复选框,这样在递交时新增文件或文件夹会被自动先添加到版本控制系统中

注意,在使用Commit时,不要急于按下对话框上的提交按钮,要先填写好日志,检查是否要勾选“Select / deselect All”复选框,全好了之后再递交。

2. 删除文件或文件夹
从RB工程中删除新工程项(或文件夹)后,如果所删除的工程项对应的文件已被添加到版本控制系统中,那么它并不会被从版本控制系统中去除,也不会被从磁盘 上删除。此时要右键单击相应的文件或文件夹,在弹出的右键菜单中使用TortoiseSVN子菜单中的Delete命令,将它们设置为需要从版本控制系统 中删除的项目。注意,这些文件夹一般不会立即被删除,要在下次Commit后,才会被TortoiseSVN自动从磁盘上删除。

3. 删除以“.obsolete”结尾的文件或文件夹
在 从外部导入工程项(比如拖曳到RB的工程编辑器中) 并保存工程和一些其他情况下,将会产生一个.obsolete结尾的文件。比如将WinMain.rbfrm添加到另一个工程中并保存,将会产生一个 WinMain.rbfrm.obsolete备份文件,在Commit时,如果在Commit对话框上的文件列表框中发现这些文件,可以右键单击相应的 行并从随后弹出的上下文菜单中选择Delete。注意,不要递交任何.obsolete文件或文件夹,不要将它们Add到版本控制系统中。

4. Commit时忘记填log了怎么办?
可 以从TortoiseSVN子菜单中选择Show Log子菜单,在随后弹出的日志查看窗口的列表框中单击刚才递交的ChangeSet对应的行,然后在下面的文本框上右键点击,从上下文菜单中选择 “Edit Log”。TortoiseSVN会弹出日志编辑对话框以便补填日志。

5.“.obsolete”文件夹带来的递交问题的解决办法 ~NEW~
“.obsolete” 文件夹有时会带来额外的递交问题。比如工程中有一个文件夹ABC,其中有几个类或模块,当调整文件夹显示的位置(比如上移、下移)并且修改过其中的内容 后,再保存工程,磁盘上会出现一个ABC.obsolete和一个ABC文件夹。实际上RB的IDE将原来磁盘上的ABC文件夹更名为了 ABC.obsolete并重新用内存中的数据创建了ABC文件夹和其中的各个工程项文件。在递交时会遇到问题。原因分析如下:

  • 原来ABC文件夹中保存工作拷贝数据的“.svn”隐藏文件夹和其中的数据现在跑到ABC.obsolete里面去了,TortoiseSVN无法找到它们,因而ABC文件夹现在处于和尚未添加到版本控制系统的工作拷贝中的那些文件相同的状态。
  • 如果想用TortoiseSVN的Add子菜单重新添加ABC文件夹,也会失败,因为ABC曾经被添加到版本控制系统中,又没有从版本控制系统中删除的记录,因此不可能允许你再添加它

解决方法:将 ABC.obsolete里“.svn”隐藏文件夹移动到ABC文件夹中。如果看不到这个隐藏文件夹,请 先使用资源管理器的【工具】菜单的【文件夹选项】子菜单来显示隐藏文件。移动了“.svn”之后,可以试着对较顶级的文件夹(比如DocuSteward 或DocuSteward\Src)使用【TortoiseSVN】右键菜单的【Clear Up】子菜单(带扫帚图标的那个)清理下。然后删除ABC.obsolete(由于它还没被添加到版本控制系统,因此使用资源管理器或 TortoiseSVN的删除命令均可,用TortoiseSVN的ignore子菜单来忽略它也可以,反正“.obsolete”文件/文件夹里的都是 工程中不再使用的东西)。最后应该就可以正常递交了。

(未完待续)

分类:编程开发 | 固定链接 | 评论: 0 | 引用: -18 | 查看次数: 3852
    几小时前,REAL公司欧洲总部的法文客服在REAL公司内部职员邮件列表中发了封邮件,说有个法国的REAL Studio用户问如何用IDE Script将当前工程中某个项目中指定的附注保存为文本文件。
    最近一段时间没少跟应用程序自动化控制打交到,比如在Windows上是许多Windows软件都自带的VBA(MS Office,CorelDraw和AutoCAD等都支持),在Mac上则是能自动化控制所有Cocoa程序的AppleScript。因此对REAL Studio的IDEScript也有些兴趣,便稍微研究了一下。
    REAL Studio要有自己的自动化功能就需要有能跨平台的自动化控制引擎。只能在Mac上用的AppleScript显然是不行的,而且REAL Studio的Mac版暂时还是基于Carbon框架程序而不是Cocoa程序,需要被AppleScript支持就要写个庞大的AppleScript扩展,还不一定能做到完美。VBA本来的确有Mac上的实现,但是由于没法将Mac版VBA代码编译为Intel指令,MS从Office 2008 for Mac开始已经移除了VBA功能,转而支持AppleScript(在大量需要跨平台VBA实现的客户的强列要求下,MS的苹果软件事业部似乎已经动手重新增加VBA对Mac版Office的支持了)。因此REAL Studio基于自己的RBScript引擎设计并开发了专门的IDEScript自动化控制功能。
    IDEScript主要用于编写软件构建脚本,使用户能实现对程序编译、打包,甚至发布的自动化处理。当然也可以做很多别的事情,比如提供这个法国客户所需要的功能。这个客户的需求很清晰,他需要一个自动化脚本,把某个工程中某几个工程项的附注(估计这个用户习惯用附注部分来编写项目文档)导出为文本文件。
    因为他经常修改附注内容,每次手动导出附注比较麻烦,过程大抵是这样的:先进入要导出其附注部分的工程项,接着进入附注中,在代码编辑器里全选,复制,然后打开一个文本编辑器,将复制的内容贴进去,选择文件位置并进行保存,最后关闭文本编辑器。虽然不复杂,每天都要重复两三次也不是件有趣的事情。
REALbasic的IDE是没有将工程项中的例程或附注导出为文本的功能的,因此IDEScript的命令集中并没有直接可以调用的命令。我稍微研究了一下,发现要解决这个法国用户的需求并不困难,如果只要支持一个平台的话,只需2行代码(我为了保险起见写了行错误判断)。具体的想法是,先用Location命令跳转到指定的附注项,在用Text命令获取当前代码编辑器中的文本,最后用DoShellCommand调用Unix或Dos的流定向命令来将文本写出到文件中。由于法文客服只需要一个Mac版本做参考,我给就只给出了一个Mac的版本,在Linux上要稍微修改下Unix命令的部分,在Windows上也差不多,Dos的echo命令看来是从Unix上学去的(注意Dos的流输出定位指令是“>>”)。当然使用REALbasic/RBScript语言的预编译指令#if...#elseif...#endif就能编写出支持3个平台的代码了。我的脚本如下:
Location="Window1.Note1"
Dim Result As String=DoShellCommand("echo '"+Text+"' > '/Users/zhujianan/Documents/Note1.txt'",5000)
If Result<>"" Then Print "Failed to save note: "+Result
    第一行代码用于跳转到工程项Window1的名为Note1的代码项/附注项中,第二行代码执行Unix命令echo并用流定向指令符“>”将Text命令返回的文本写入到指定的文件中,第三行命令检查是否正常执行(在Mac中此Unix命令正常执行后不打印任何内容,否则在终端上打印输出错误信息),如果有错误信息就显示给用户(可能的错误比如有目录不存在,文件不可写之类)。
    然后在REAL Studio的脚本编辑器中保存这个脚本,以后就可以直接从文件菜单中选择这个脚本并执行了。

查看更多...

分类:编程开发 | 固定链接 | 评论: 0 | 引用: -34 | 查看次数: 4170

Is a specific app running?

How to know whether an app is running in REALbasic? The common idea is looping through all processes and testing with the name of the app which you want to know if it's opened.

Windows
On Windows, we can do it via declares, the Win32 APIs we need to use are EnumProcesses, EnumProcessModules, GetModuleFileNameEx, etc. I wrote a function a few days ago. You can find the source code of it in Window1 of the RB project attached. I didn't implemented it in a very smart way. It gets the names of all processes, then tests to see if a specific app name can be found. It because I wanted to demonstrate how to list the paths of all processes. You may make some changes on it to exit the For loop when the app is found.

Mac OS X
On Mac, calling AppleScript functions seems the best way. You can loop through all process objects provided by the System Events application in a tell block. However, since AppleScript supports using keyword each or plural of class name to access all objects in the direct parameter of tell statement (in this case, it's the System Events application), so that the script can be simply written like this:

on run {appname}
tell application "System Events"
if (name of each process) contains appname then
return true
else
return false
end if
end tell
end run

OR:

on run {appname}
tell application "System Events"
if (name of processes) contains appname then
return true
else
return false
end if
end tell
end run
 
On Leopard, AppleScript are updated to 2.0, many new features are introduced. As the result, it's able to test whether an app is running without launching it:
on run {appname}
if application appname is running then
return true
else
return false
end if
end run
The code above doesn't work on Tiger and earlier.
 
To use an AppleScript, drag and drop the scpt file to your project, and call it by its name showed in the listbox of project panel, as calling a global function. The AppleScript will be compiled and embedded to app file you built. You will observe that the return value of any AppleScript you get in RB is a String, rather than the date type you expected. So you should compare it with "true" or "false", not a Boolean constant. Refer to the rbp I attached if my description isn't clear to you.
 
Attachment: IsProcRunning.zip

原载本人英文Blog

 

Tags: REALbasic Declares AppleScript English

分类:编程开发 | 固定链接 | 评论: 0 | 引用: -18 | 查看次数: 5576