Zope3宝典/同步Web代码与文件系统
Chapter 4:Synchronizing Through-The-Web Code with the Filesystem(利用 Web 编写代码 (Through-The-Web Code) 并与文件系统同步)
原文出处:The Zope 3 Developers Book - An Introduction for Python Programmers
原文作者:StephanRichter, zope.org
授权许可:创作共用协议
翻译人员:
- FireHare <[email protected]>
校对人员:Leal
适用版本:Zope 3
文章状态:草译阶段
Difficulty(难度)
Newcomer(新手)
Skills(技能)
- Zope 3 should be installed and running.
已安装和运行Zope 3 - You also need to know how to use the ZMI and create content as well as software with it.
你也需知道如何使用 ZMI 以及用它来创建内容和软件 - Some knowledge about Zope 3 and how it functions are also required for the command options to make sense.
为了理解命令行选项,还需了解一些Zope 3的知识,及其工作机制。 - Familiarity with version control systems like CVS or SVN. Optional.
熟悉版本控制系统,如 CVS 或 SVN。(非必须)
- Zope 3 should be installed and running.
Problem/Task(问题/任务)
Zope 3 heavily supports Through-The-Web (TTW) development. This means that all of the software and content is stored in the ZODB. But the ZODB is not a version control system, so that is is very hard for developers to cooperate, look and histories or work with branches. This problem has been addressed in Zope 3 by providing a tool that allows the developer to "synchronize" ZODB objects with a loss-less filesystem representation. The tool is called "zsync" The goal of the recipe is to demonstrate zsync's features by manging some TTW software.
Zope 3为通过Web(TTW)进行开发提供了强大支持。也就是说,所有软件和内容都存储在ZODB里。但是ZODB并不是一个版本控制系统,这使得开发人员之间的协作,查看代码历史或开发分支等变得非常困难。这个问题已在Zope 3中得到解决,其途径是提供一个名为”zsync“的工具,允许开发人员用一个低损耗的文件系统来“同步”ZODB对象。本诀窍旨在通过管理一些TTW软件来演示zsync的功能。
Recipe(诀窍)
zsync, the tool we will use exclusively in this recipe, is located in ZOPE3/bin/zsync. After you built Zope 3, this script should be executable. The foremost important feature at the beginning is always getting to the help of the tool. For zsync you can reach the help using:
本诀窍里,我们将只使用工具zsync,该工具位于ZOPE3/bin/zsync。在你构建 Zope 3 之后,这个脚本就可以用了。一开始最重要的任务总是先查看该工具的帮助(文档)。如下命令可以查看zsync的帮助:
/ZOPE3/bin/zsync help
This command lists all the available commands' synopsis. If you append the name of the command, like add, then the help will return more specific information about that command:
该命令列出了所有可用命令的梗概。如果你再在上述命令后加上一个命令的名字,如 add,那么该帮助将返回更多关于该命令的特定信息:
/ZOPE3/bin/zsync help add
This recipe intends to be the practical equivalent to this help system.
Authentication and Authorization(授权和许可)
Before we can access the server to retrieve and write software, we need to tell the server who we are, so that it can bef figured out what the user is allowed to do. Like for other version control systems there is a login command to authenticate with the server. The commands synopsis looks as follows:
zsync login [-u user] [URL]
If you haven't checked out anything yet or if you are outside of a checkout, you must specify the URL. Otherwise the command grabs the information from the checkout meta-data. The user option is always optional. If you do not include it, you will be prompted for the username. Before the command succeeds you will always be prompted for a password. Here is the login command I used:
$ zsync login -u srichter http://localhost:8080/ Password for srichter at localhost:8080:
The login command does not check at this time with the server that the username/password pair allows a successfull authentication, but merely stores a token locally. You know whether the login was successful, once you start making checkouts.
Now you can also use the logout command to unregister a username and password for a particular URL. The synopsis is the same as for the login command. To logout I use:
$ zsync logout -u srichter http://localhost:8080/
If you were not logged in, the command fails with the message "matching login info not found".
Creating TTW code
Before we can export/checkout any code, we have to first develop it. Here I am just going to outline the steps you have to take to create the TTW software, since these steps are the same as for the previous recipe for most parts.
(a) Create a new Site-Management Folder called "messageboard" in the root folder's Site (you can click on "Manage Site" to get the site object of the root folder).
(b) In this created folder, add a Utility Service and register it as active.
(c) Now add a Menu Service and register it.
(d) Add a Mutable Schema called "IMessage?" and add the title and body fields.
(e) Add a Content Component Definition called "Message" and tell it to implement the IMessage interface.
Export the software
You now want to create a filesystem representation of the "messageboard" Site-Management Folder. First we need to login using:
$ zsync login -u srichter http://localhost:8080/
Next we checkout the folder:
$ zsync checkout \ http://srichter@localhost:8080/++etc++site/messageboard
The URL specifies the exact object that is to be checked out; in our case the "messageboard" site-folder in software space. One can optionally specify a destination directory, but since we are already in the directory we want the checkout to be, we simply ignore the option. The output of the above command should look as follows:
N ./messageboard/ U ./messageboard/IMessage U ./messageboard/Menus U ./messageboard/Message N ./messageboard/RegistrationManager/ U ./messageboard/RegistrationManager/BrowserMenu U ./messageboard/RegistrationManager/ContentComponentDefinitionRegistration U ./messageboard/RegistrationManager/Utilities U ./messageboard/RegistrationManager/UtilityRegistration N ./messageboard/RegistrationManager/@@Zope/Annotations/UtilityRegistration/ U ./messageboard/RegistrationManager/@@Zope/Annotations/UtilityRegistration/zope.app.dublincore.ZopeDublinCore N ./messageboard/@@Zope/Annotations/RegistrationManager/ U ./messageboard/@@Zope/Annotations/RegistrationManager/zope.app.dublincore.ZopeDublinCore U ./messageboard/Utilities N ./@@Zope/Annotations/messageboard/ U ./@@Zope/Annotations/messageboard/zope.app.dublincore.ZopeDublinCore All done.
Updating a checkout
The greatest feature of the entire zsync mechanism is that you can develop on the checked out copy of the TTW code. The easiest of these tasks is to change the title of the menu item, which can be found in the file messageboard/Message. I found the entry on line 49, which had the following environment:
45 <item key="description"> 46 <string></string> 47 </item> 48 <item key="title"> 49 <unicode>Local Message</unicode> 50 </item> 51 <item key="menuId"> 52 <string>add_content</string> 53 </item>
You can see that I already changed line 49 from "Message" to "Local Message". Now we can see what we changed using the diff or di (short cut) command (which works exactely the same as the Unix diff command). Simply calling zsync diff on the entire checkout - meaning ZOPE3/fssync gives me:
Index: ./messageboard/Message 49c49 < <unicode>Message</unicode> --- > <unicode>Local Message</unicode>
Note that diff can be executed without consulting the server, which means that the command executes totally offline. The command also has a bunch of options as you might expect.
- -b - Ignore changes in the amount of white space.
- -B - Ignore changes that only insert or delete blank lines.
- --brief - Report only that changes exist, not the details of the change.
- -c - Generate a context diff.
- -C num, --context num - Set the number of lines of context information included on each side of changes to num.
- -i - Ignore changes in case; consider upper- andlower-case letters equivalent.
- -N - When the files to be diffed include files being added or deleted, perform comparisons between those files and ``/dev/null`` instead of complaining about them.
- -u - Generate a unified diff.
- -U num, --unified num - Use the unified output format, showing num (an integer) lines of context, or three if num is not given. For proper operation, patch typically needs at least two lines of context.
A summary version of the diff command is status, which shows you which files have been modified and in which way (add, edit, delete). Calling status on our checkout, I get: {{ / ./messageboard/ = ./messageboard/IMessage = ./messageboard/Menus M ./messageboard/Message / ./messageboard/RegistrationManager/ = ./messageboard/RegistrationManager/BrowserMenu = ./messageboard/RegistrationManager/ContentComponentDefinitionRegistration = ./messageboard/RegistrationManager/Utilities = ./messageboard/RegistrationManager/UtilityRegistration }} The first character of each line represents the status of the followed file/object. Here is a list of flags and their meaning:
- / - This signifies that the object is represented using a directory (so is likely to implement IContainer) and all its data is contained in this directory.
- ? - The system does not yet know about the object, but it is one that should not be ignored. At this point you are either expected to add the object to the repository or delete it from the filesystem representation.
- = - An object marked like this is in sync with the server.
- M - This object has been modified offline and differs from the server copy. If the term lost-original is appended, then the object is marked modified, since the reference copy (or original) is not found in the checkout anymore. The best solution to this problem is to update your checkout.
- A - This flag tells you that this target (file) has been added locally and is ready for submission. If the term lost is appended in parenthesis, then an object was marked for addition, but was deleted afterwards. You can resolve this error by either add a file or directory of this name in the filesystem or revert the addition.
- R - An object marked as R is considered removed. If the term reborn appears in parenthesis behind the letter, then the object is marked as removed but still exists on the filesystem. Most likely it was noticed that this object should not be deleted and was readded.
- nonexistent - The target is not registered as an object and does not exist on the filesystem. You should never receive this status from a normal checkout. This "error" can only occur, if you call the status request directly from Python and pass an invalid target.
- lost - An entry about the object exists, but the file is nowhere to be found.
- (unrecognized) - Whenever this term is appended to any of the above flags, the filetype of the filesystem representation could not be determined. Only simple files and directories are supported. This flag would appear, for example, when the target (filesystem representation file) is a soft link. Note that only non-ignored and server-existent files are considered.
Before committing the changes, you might want to revert a change or another. This allows you to totally undo modifications and de-schedule additions as well as removals. The synopsis of the revert command is as follows:
zsync revert [path ...]
The optional set of relative represents the paths to the objects that are to be reverted. If no path is specified, the entire checkout from the current path on is reverted. Now, let's say we want to revert our change to the Message file. This can be done with the command:
$ zsync revert messageboard/Message Reverted messageboard/Message
You can verify the reversion by invoking a status request again. Now let's modify the menu title again, so that we can practice committing some changes. The synopsis of commit, the command one uses to send changes to the server is:
zsync commit [commit-options] [path ...]
Optionally one can specify a list of paths to commit. Again, if no paths are specified, then all modified files are committed from the current working directory on. The options for the command are as follows:
- -F file, --file file - Set the message used for the transaction note to the contents of the file. This is similar to the -F option for cvs commit.
- -m msg, --message msg - Set the commit message used for the transaction note, which is similar to the -m option for cvs commit.
- -r, --raise-on-conflicts - Tell zsync to raise an exception on conflicts instead of simply reporting a list of paths for which conflicts are detected.
Let's now change the title of the menu again; but this time on the server via the ZMI. In the "Menu Item" tab of "http://localhost:8080/++etc++site/messageboard/Message" we change the title to "Server Message". If we now go back to our command line, we will see that the status command does not tell us that a file changed on the server, because it does not contact the server at all. The command that is used for updating is update, which simply takes an optional list of paths having the same symantics as before. There are no specific options. To update, I simply entered:
$ zsync update U ./messageboard/Message U ./messageboard/RegistrationManager/BrowserMenu U ./messageboard/RegistrationManager/Utilities U ./messageboard/RegistrationManager/UtilityRegistration All done.
You can see that all objects involved in the change were updated.
Now we would live to cause a conflict, so that we can try the conflict resolution feature of zsync. On the server, change the title to "Message" again and on the file system to "Local Message". If you execute zsync update now, you will see that the target ./messageboard/Message has the flag C, which stands for "conflict". If you go to the Message file, you have at line 49:
49 <<<<<<< ./messageboard/Message 50 <unicode>Local Message</unicode> 51 ======= 52 <unicode>Message</unicode> 53 >>>>>>> /tmp/messageboard/Message
Let's change this back to:
49 <unicode>Local Message</unicode>
While this fixed the conflict, the system does not yet know about it. Like in SVN, we have to declare the file to be resolved. This can be done using zsync resolve, which takes an optional set of paths and has no options. The successful execution of this command will exit silently. You can now commit the file again, so that the server is updated.
Inserting new files using add
Let's add a persistent module via a file first. Create a file called "adapters.py" in the "messageboard" directory on the filesystem, having the following contents (equivalent to the one in the Web GUI recipe):
01 from zope.interface import Interface 02 03 class IAllText(Interface): 04 05 def getText(): 06 """Return all text.""" 07 08 class AllText(object): 09 10 def <u>init</u>(self, context): 11 self.context = context 12 13 def getText(self): 14 return self.context.title + '\n\n' + \ 15 self.context.body
When you do a "zsync status" now, you will see the following entry at the end:
? ./messageboard/adapters.py
To add this file, you can use the add command with the following synopsis:
zsync add [add-options] [path]
where path is the path to the file to be added. The options are:
- -f factory, --factory factory - Specify the object factory to use for the object being added. The factory is the assigned factory id, like Module in our case. If no factory is specified, the system tries to find the factory based on the file ending; in this case .py.
- -t type, --type type - The type of the object to be created. Normally only factory needs to be specified.
To add the file, I simply used:
$ zsync add messageboard/adapters.py A messageboard/adapters.py
The A at the beginning of the second line stands for added. You can verify the state by using the status command. You can use commit to upload your changes.
XXX: Once commit works, show what happens if you used a wrong factory or so.
Note that the copy command works very similarly, except that it copies a file/directory from an arbitray location to a local checkout and registers it for addition. On the other hand, if the source represents an object, then all of its type, extra and annotation data is copied as well, if the source is also a checkout location. This way one can copy and move objects like s/he would copy them via the ZMI.
To add folders to the object database via the filesystem requires you to use the mkdir command. It works identical to the Unix version and has no options. This command adds a new directory, places the @@Zope metadata directory in it and finally schedules it for addition.
Furthermore, additions can be de-scheduled using the revert command. Just use zsync revert ./messageboard/adapters.py in our case.
Deleting objects
The delete command has no options, but requires at least one path. This is to protect the user from accidently deleting all of the checkout. Before you can delete an object, you have to delete the filesystem representation (file or directory) of the object first. Here is what I did to delete the adapters.py object:
$ rm messageboard/adapters.py $ zsync delete messageboard/adapters.py R messageboard/adapters.py
As usual the removal will take affect during the next commit and can be de-scheduled using the revert command.
Conclusion
Using zsync, Zope 3 offers a second viable method to develop through-the-Web code besides the traditional Web-based GUI (ZMI). But it has advantages on its own as well. If you have code on the filesystem, then you can use a revision control system during development. Furthermore, you can now make full use of your familiar command line tools, such as emacs, awk, grep and find.
Complementary to the zsync tool, there is another tool called zbundle, which allows you to package your software. While this is another powerful and extremly useful tool, it would be beyond this recipe to cover its use. Both tools are well-documented in ZOPE3/doc/zsync.