1 | # ***** BEGIN LICENSE BLOCK *****
|
---|
2 | # Version: MPL 1.1/GPL 2.0/LGPL 2.1
|
---|
3 | #
|
---|
4 | # The contents of this file are subject to the Mozilla Public License Version
|
---|
5 | # 1.1 (the "License"); you may not use this file except in compliance with
|
---|
6 | # the License. You may obtain a copy of the License at
|
---|
7 | # http://www.mozilla.org/MPL/
|
---|
8 | #
|
---|
9 | # Software distributed under the License is distributed on an "AS IS" basis,
|
---|
10 | # WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
|
---|
11 | # for the specific language governing rights and limitations under the
|
---|
12 | # License.
|
---|
13 | #
|
---|
14 | # The Original Code is the Python XPCOM language bindings.
|
---|
15 | #
|
---|
16 | # The Initial Developer of the Original Code is
|
---|
17 | # ActiveState Tool Corp.
|
---|
18 | # Portions created by the Initial Developer are Copyright (C) 2000, 2001
|
---|
19 | # the Initial Developer. All Rights Reserved.
|
---|
20 | #
|
---|
21 | # Contributor(s):
|
---|
22 | # Mark Hammond <MarkH@ActiveState.com> (original author)
|
---|
23 | #
|
---|
24 | # Alternatively, the contents of this file may be used under the terms of
|
---|
25 | # either the GNU General Public License Version 2 or later (the "GPL"), or
|
---|
26 | # the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
|
---|
27 | # in which case the provisions of the GPL or the LGPL are applicable instead
|
---|
28 | # of those above. If you wish to allow use of your version of this file only
|
---|
29 | # under the terms of either the GPL or the LGPL, and not to allow others to
|
---|
30 | # use your version of this file under the terms of the MPL, indicate your
|
---|
31 | # decision by deleting the provisions above and replace them with the notice
|
---|
32 | # and other provisions required by the GPL or the LGPL. If you do not delete
|
---|
33 | # the provisions above, a recipient may use your version of this file under
|
---|
34 | # the terms of any one of the MPL, the GPL or the LGPL.
|
---|
35 | #
|
---|
36 | # ***** END LICENSE BLOCK *****
|
---|
37 |
|
---|
38 | """Implementation of Python file objects for Mozilla/xpcom.
|
---|
39 |
|
---|
40 | Introduction:
|
---|
41 | This module defines various class that are implemented using
|
---|
42 | Mozilla streams. This allows you to open Mozilla URI's, and
|
---|
43 | treat them as Python file object.
|
---|
44 |
|
---|
45 | Example:
|
---|
46 | >>> file = URIFile("chrome://whatever")
|
---|
47 | >>> data = file.read(5) # Pass no arg to read everything.
|
---|
48 |
|
---|
49 | Known Limitations:
|
---|
50 | * Not all URL schemes will work from "python.exe" - most notably
|
---|
51 | "chrome://" and "http://" URLs - this is because a simple initialization of
|
---|
52 | xpcom by Python does not load up the full set of Mozilla URL handlers.
|
---|
53 | If you can work out how to correctly initialize the chrome registry and
|
---|
54 | setup a message queue.
|
---|
55 |
|
---|
56 | Known Bugs:
|
---|
57 | * Only read ("r") mode is supported. Although write ("w") mode doesnt make
|
---|
58 | sense for HTTP type URLs, it potentially does for file:// etc type ones.
|
---|
59 | * No concept of text mode vs binary mode. It appears Mozilla takes care of
|
---|
60 | this internally (ie, all "text/???" mime types are text, rest are binary)
|
---|
61 |
|
---|
62 | """
|
---|
63 |
|
---|
64 | from xpcom import components, Exception, _xpcom
|
---|
65 | import os
|
---|
66 | import threading # for locks.
|
---|
67 |
|
---|
68 | NS_RDONLY = 0x01
|
---|
69 | NS_WRONLY = 0x02
|
---|
70 | NS_RDWR = 0x04
|
---|
71 | NS_CREATE_FILE = 0x08
|
---|
72 | NS_APPEND = 0x10
|
---|
73 | NS_TRUNCATE = 0x20
|
---|
74 | NS_SYNC = 0x40
|
---|
75 | NS_EXCL = 0x80
|
---|
76 |
|
---|
77 | # A helper function that may come in useful
|
---|
78 | def LocalFileToURL(localFileName):
|
---|
79 | "Convert a filename to an XPCOM nsIFileURL object."
|
---|
80 | # Create an nsILocalFile
|
---|
81 | localFile = components.classes["@mozilla.org/file/local;1"] \
|
---|
82 | .createInstance(components.interfaces.nsILocalFile)
|
---|
83 | localFile.initWithPath(localFileName)
|
---|
84 |
|
---|
85 | # Use the IO Service to create the interface, then QI for a FileURL
|
---|
86 | io_service = components.classes["@mozilla.org/network/io-service;1"] \
|
---|
87 | .getService(components.interfaces.nsIIOService)
|
---|
88 | url = io_service.newFileURI(localFile).queryInterface(components.interfaces.nsIFileURL)
|
---|
89 | # Setting the "file" attribute causes initialization...
|
---|
90 | url.file = localFile
|
---|
91 | return url
|
---|
92 |
|
---|
93 | # A base class for file objects.
|
---|
94 | class _File:
|
---|
95 | def __init__(self, name_thingy = None, mode="r"):
|
---|
96 | self.lockob = threading.Lock()
|
---|
97 | self.inputStream = self.outputStream = None
|
---|
98 | if name_thingy is not None:
|
---|
99 | self.init(name_thingy, mode)
|
---|
100 |
|
---|
101 | def __del__(self):
|
---|
102 | self.close()
|
---|
103 |
|
---|
104 | # The Moz file streams are not thread safe.
|
---|
105 | def _lock(self):
|
---|
106 | self.lockob.acquire()
|
---|
107 | def _release(self):
|
---|
108 | self.lockob.release()
|
---|
109 | def read(self, n = -1):
|
---|
110 | assert self.inputStream is not None, "Not setup for read!"
|
---|
111 | self._lock()
|
---|
112 | try:
|
---|
113 | return str(self.inputStream.read(n))
|
---|
114 | finally:
|
---|
115 | self._release()
|
---|
116 |
|
---|
117 | def readlines(self):
|
---|
118 | # Not part of the xpcom interface, but handy for direct Python users.
|
---|
119 | # Not 100% faithful, but near enough for now!
|
---|
120 | lines = self.read().split("\n")
|
---|
121 | if len(lines) and len(lines[-1]) == 0:
|
---|
122 | lines = lines[:-1]
|
---|
123 | return [s+"\n" for s in lines ]
|
---|
124 |
|
---|
125 | def write(self, data):
|
---|
126 | assert self.outputStream is not None, "Not setup for write!"
|
---|
127 | self._lock()
|
---|
128 | try:
|
---|
129 | self.outputStream.write(data, len(data))
|
---|
130 | finally:
|
---|
131 | self._release()
|
---|
132 |
|
---|
133 | def close(self):
|
---|
134 | self._lock()
|
---|
135 | try:
|
---|
136 | if self.inputStream is not None:
|
---|
137 | self.inputStream.close()
|
---|
138 | self.inputStream = None
|
---|
139 | if self.outputStream is not None:
|
---|
140 | self.outputStream.close()
|
---|
141 | self.outputStream = None
|
---|
142 | self.channel = None
|
---|
143 | finally:
|
---|
144 | self._release()
|
---|
145 |
|
---|
146 | def flush(self):
|
---|
147 | self._lock()
|
---|
148 | try:
|
---|
149 | if self.outputStream is not None: self.outputStream.flush()
|
---|
150 | finally:
|
---|
151 | self._release()
|
---|
152 |
|
---|
153 | # A synchronous "file object" used to open a URI.
|
---|
154 | class URIFile(_File):
|
---|
155 | def init(self, url, mode="r"):
|
---|
156 | self.close()
|
---|
157 | if mode != "r":
|
---|
158 | raise ValueError("only 'r' mode supported")
|
---|
159 | io_service = components.classes["@mozilla.org/network/io-service;1"] \
|
---|
160 | .getService(components.interfaces.nsIIOService)
|
---|
161 | if hasattr(url, "queryInterface"):
|
---|
162 | url_ob = url
|
---|
163 | else:
|
---|
164 | url_ob = io_service.newURI(url, None, None)
|
---|
165 | # Mozilla asserts and starts saying "NULL POINTER" if this is wrong!
|
---|
166 | if not url_ob.scheme:
|
---|
167 | raise ValueError("The URI '%s' is invalid (no scheme)"
|
---|
168 | % (url_ob.spec,))
|
---|
169 | self.channel = io_service.newChannelFromURI(url_ob)
|
---|
170 | self.inputStream = self.channel.open()
|
---|
171 |
|
---|
172 | # A "file object" implemented using Netscape's native file support.
|
---|
173 | # Based on io.js - http://lxr.mozilla.org/seamonkey/source/xpcom/tests/utils/io.js
|
---|
174 | # You open this file using a local file name (as a string) so it really is pointless -
|
---|
175 | # you may as well be using a standard Python file object!
|
---|
176 | class LocalFile(_File):
|
---|
177 | def __init__(self, *args):
|
---|
178 | self.fileIO = None
|
---|
179 | _File.__init__(self, *args)
|
---|
180 |
|
---|
181 | def init(self, name, mode = "r"):
|
---|
182 | name = os.path.abspath(name) # Moz libraries under Linux fail with relative paths.
|
---|
183 | self.close()
|
---|
184 | file = components.classes['@mozilla.org/file/local;1'].createInstance("nsILocalFile")
|
---|
185 | file.initWithPath(name)
|
---|
186 | if mode in ["w","a"]:
|
---|
187 | self.fileIO = components.classes["@mozilla.org/network/file-output-stream;1"].createInstance("nsIFileOutputStream")
|
---|
188 | if mode== "w":
|
---|
189 | if file.exists():
|
---|
190 | file.remove(0)
|
---|
191 | moz_mode = NS_CREATE_FILE | NS_WRONLY
|
---|
192 | elif mode=="a":
|
---|
193 | moz_mode = NS_APPEND
|
---|
194 | else:
|
---|
195 | assert 0, "Can't happen!"
|
---|
196 | self.fileIO.init(file, moz_mode, -1,0)
|
---|
197 | self.outputStream = self.fileIO
|
---|
198 | elif mode == "r":
|
---|
199 | self.fileIO = components.classes["@mozilla.org/network/file-input-stream;1"].createInstance("nsIFileInputStream")
|
---|
200 | self.fileIO.init(file, NS_RDONLY, -1,0)
|
---|
201 | self.inputStream = components.classes["@mozilla.org/scriptableinputstream;1"].createInstance("nsIScriptableInputStream")
|
---|
202 | self.inputStream.init(self.fileIO)
|
---|
203 | else:
|
---|
204 | raise ValueError("Unknown mode")
|
---|
205 |
|
---|
206 | def close(self):
|
---|
207 | if self.fileIO is not None:
|
---|
208 | self.fileIO.close()
|
---|
209 | self.fileIO = None
|
---|
210 | _File.close(self)
|
---|
211 |
|
---|
212 | def read(self, n = -1):
|
---|
213 | return _File.read(self, n)
|
---|
214 |
|
---|
215 |
|
---|
216 | ##########################################################
|
---|
217 | ##
|
---|
218 | ## Test Code
|
---|
219 | ##
|
---|
220 | ##########################################################
|
---|
221 | def _DoTestRead(file, expected):
|
---|
222 | # read in a couple of chunks, just to test that our various arg combinations work.
|
---|
223 | got = file.read(3)
|
---|
224 | got = got + file.read(300)
|
---|
225 | got = got + file.read(0)
|
---|
226 | got = got + file.read()
|
---|
227 | if got != expected:
|
---|
228 | raise RuntimeError("Reading '%s' failed - got %d bytes, but expected %d bytes" % (file, len(got), len(expected)))
|
---|
229 |
|
---|
230 | def _DoTestBufferRead(file, expected):
|
---|
231 | # read in a couple of chunks, just to test that our various arg combinations work.
|
---|
232 | buffer = _xpcom.AllocateBuffer(50)
|
---|
233 | got = ''
|
---|
234 | while 1:
|
---|
235 | # Note - we need to reach into the file object so we
|
---|
236 | # can get at the native buffer supported function.
|
---|
237 | num = file.inputStream.read(buffer)
|
---|
238 | if num == 0:
|
---|
239 | break
|
---|
240 | got = got + str(buffer[:num])
|
---|
241 | if got != expected:
|
---|
242 | raise RuntimeError("Reading '%s' failed - got %d bytes, but expected %d bytes" % (file, len(got), len(expected)))
|
---|
243 |
|
---|
244 | def _TestLocalFile():
|
---|
245 | import tempfile, os
|
---|
246 | fname = tempfile.mktemp()
|
---|
247 | data = "Hello from Python"
|
---|
248 | test_file = LocalFile(fname, "w")
|
---|
249 | try:
|
---|
250 | test_file.write(data)
|
---|
251 | test_file.close()
|
---|
252 | # Make sure Python can read it OK.
|
---|
253 | f = open(fname, "r")
|
---|
254 | assert f.read() == data, "Eeek - Python could not read the data back correctly!"
|
---|
255 | f.close()
|
---|
256 | # For the sake of the test, try a re-init.
|
---|
257 | test_file.init(fname, "r")
|
---|
258 | got = str(test_file.read())
|
---|
259 | assert got == data, got
|
---|
260 | test_file.close()
|
---|
261 | # Try reading in chunks.
|
---|
262 | test_file = LocalFile(fname, "r")
|
---|
263 | got = test_file.read(10) + test_file.read()
|
---|
264 | assert got == data, got
|
---|
265 | test_file.close()
|
---|
266 | # Open the same file again for writing - this should delete the old one.
|
---|
267 | if not os.path.isfile(fname):
|
---|
268 | raise RuntimeError("The file '%s' does not exist, but we are explicitly testing create semantics when it does" % (fname,))
|
---|
269 | test_file = LocalFile(fname, "w")
|
---|
270 | test_file.write(data)
|
---|
271 | test_file.close()
|
---|
272 | # Make sure Python can read it OK.
|
---|
273 | f = open(fname, "r")
|
---|
274 | assert f.read() == data, "Eeek - Python could not read the data back correctly after recreating an existing file!"
|
---|
275 | f.close()
|
---|
276 |
|
---|
277 | # XXX - todo - test "a" mode!
|
---|
278 | finally:
|
---|
279 | os.unlink(fname)
|
---|
280 |
|
---|
281 | def _TestAll():
|
---|
282 | # A mini test suite.
|
---|
283 | # Get a test file, and convert it to a file:// URI.
|
---|
284 | # check what we read is the same as when
|
---|
285 | # we read this file "normally"
|
---|
286 | fname = components.__file__
|
---|
287 | if fname[-1] in "cCoO": # fix .pyc/.pyo
|
---|
288 | fname = fname[:-1]
|
---|
289 | expected = open(fname, "rb").read()
|
---|
290 | # convert the fname to a URI.
|
---|
291 | url = LocalFileToURL(fname)
|
---|
292 | # First try passing a URL as a string.
|
---|
293 | _DoTestRead( URIFile( url.spec), expected)
|
---|
294 | # Now with a URL object.
|
---|
295 | _DoTestRead( URIFile( url ), expected)
|
---|
296 |
|
---|
297 | _DoTestBufferRead( URIFile( url ), expected)
|
---|
298 |
|
---|
299 | # For the sake of testing, do our pointless, demo object!
|
---|
300 | _DoTestRead( LocalFile(fname), expected )
|
---|
301 |
|
---|
302 | # Now do the full test of our pointless, demo object!
|
---|
303 | _TestLocalFile()
|
---|
304 |
|
---|
305 | def _TestURI(url):
|
---|
306 | test_file = URIFile(url)
|
---|
307 | print("Opened file is", test_file)
|
---|
308 | got = test_file.read()
|
---|
309 | print("Read %d bytes of data from %r" % (len(got), url))
|
---|
310 | test_file.close()
|
---|
311 |
|
---|
312 | if __name__=='__main__':
|
---|
313 | import sys
|
---|
314 | if len(sys.argv) < 2:
|
---|
315 | print("No URL specified on command line - performing self-test")
|
---|
316 | _TestAll()
|
---|
317 | else:
|
---|
318 | _TestURI(sys.argv[1])
|
---|