1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227
|
from __future__ import unicode_literals
import os
from functools import wraps
import six
from psycopg2cffi._impl import exceptions
from psycopg2cffi._impl import consts
from psycopg2cffi._impl.libpq import libpq, ffi
INV_WRITE = 0x00020000
INV_READ = 0x00040000
def check_unmarked(func):
@wraps(func)
def check_unmarked_(self, *args, **kwargs):
if self._mark != self._conn._mark:
raise exceptions.ProgrammingError("lobject isn't valid anymore")
return func(self, *args, **kwargs)
return check_unmarked_
def check_closed(func):
@wraps(func)
def check_closed_(self, *args, **kwargs):
if self.closed:
raise exceptions.InterfaceError("lobject already closed")
return func(self, *args, **kwargs)
return check_closed_
class LargeObject(object):
def __init__(self, conn=None, oid=0, mode='', new_oid=0, new_file=None):
self._conn = conn
self._oid = oid
self._mode = self._parse_mode(mode)
self._smode = mode
self._new_oid = new_oid
self._new_file = new_file
self._fd = -1
self._mark = conn._mark
if conn.autocommit:
raise exceptions.ProgrammingError(
"can't use a lobject outside of transactions")
self._open()
@property
def oid(self):
return self._oid
@property
def mode(self):
return self._smode
@check_closed
@check_unmarked
def read(self, size=-1):
"""Read at most size bytes or to the end of the large object."""
if size < 0:
where = self.tell()
end = self.seek(0, os.SEEK_END)
self.seek(where, os.SEEK_SET)
size = end - where
binary_mode = self._mode & consts.LOBJECT_BINARY
if size == 0:
return b'' if binary_mode else ''
buf = ffi.new('char []', size)
length = libpq.lo_read(self._conn._pgconn, self._fd, buf, size)
if length < 0:
return
return ffi.buffer(buf, length)[:] if binary_mode else \
ffi.string(buf).decode(self._conn._py_enc)
@check_closed
@check_unmarked
def write(self, value):
"""Write a string to the large object."""
if isinstance(value, six.text_type):
value = value.encode(self._conn._py_enc)
length = libpq.lo_write(
self._conn._pgconn, self._fd, value, len(value))
if length < 0:
raise self._conn._create_exception()
return length
def export(self, file_name):
"""Export large object to given file."""
self._conn._begin_transaction()
if isinstance(file_name, six.text_type):
file_name = file_name.encode(self._conn._py_enc)
if libpq.lo_export(self._conn._pgconn, self._oid, file_name) < 0:
raise self._conn._create_exception()
@check_closed
@check_unmarked
def seek(self, offset, whence=0):
"""Set the lobject's current position."""
return libpq.lo_lseek(self._conn._pgconn, self._fd, offset, whence)
@check_closed
@check_unmarked
def tell(self):
"""Return the lobject's current position."""
return libpq.lo_tell(self._conn._pgconn, self._fd)
@check_closed
@check_unmarked
def truncate(self, length=0):
ret = libpq.lo_truncate(self._conn._pgconn, self._fd, length)
if ret < 0:
raise self._conn._create_exception()
return ret
def close(self):
"""Close and then remove the lobject."""
if self.closed:
return True
if self._conn.autocommit or self._conn._mark != self._mark:
return True
ret = libpq.lo_close(self._conn._pgconn, self._fd)
self._fd = -1
if ret < 0:
raise self._conn._create_exception()
else:
return True
@property
def closed(self):
return self._fd < 0 or not self._conn or self._conn.closed
def unlink(self):
self._conn._begin_transaction()
self.close()
libpq.lo_unlink(self._conn._pgconn, self._oid)
def _open(self):
conn = self._conn
conn._begin_transaction()
if self._oid == 0:
if self._new_file:
_new_file = self._new_file
if isinstance(self._new_file, six.text_type):
_new_file = self._new_file.encode(conn._py_enc)
self._oid = libpq.lo_import(conn._pgconn, _new_file)
else:
self._oid = libpq.lo_create(conn._pgconn, self._new_oid)
self._mode = \
(self._mode & ~consts.LOBJECT_READ) | consts.LOBJECT_WRITE
pgmode = 0
if self._mode & consts.LOBJECT_READ:
pgmode |= INV_READ
if self._mode & consts.LOBJECT_WRITE:
pgmode |= INV_WRITE
if pgmode:
self._fd = libpq.lo_open(conn._pgconn, self._oid, pgmode)
if self._fd < 0:
raise self._conn._create_exception()
self._smode = self._unparse_mode(self._mode)
def _parse_mode(self, smode):
"""Convert a mode string to a mode int"""
mode = 0
pos = 0
binary_default = consts.LOBJECT_TEXT if six.PY3 else \
consts.LOBJECT_BINARY
if not smode:
return consts.LOBJECT_READ | binary_default
if smode[0:2] == 'rw':
mode |= consts.LOBJECT_READ | consts.LOBJECT_WRITE
pos = 2
else:
if smode[0] == 'r':
mode |= consts.LOBJECT_READ
pos = 1
elif smode[0] == 'w':
mode |= consts.LOBJECT_WRITE
pos = 1
elif smode[0] == 'n':
pos = 1
else:
mode |= consts.LOBJECT_READ
if len(smode) > pos:
if smode[pos] == 't':
mode |= consts.LOBJECT_TEXT
pos += 1
elif smode[pos] == 'b':
mode |= consts.LOBJECT_BINARY
pos += 1
else:
mode |= binary_default
else:
mode |= binary_default
if len(smode) != pos:
raise ValueError("bad mode for lobject: '%s'", smode)
return mode
def _unparse_mode(self, mode):
"""Convert a mode int to a mode string"""
smode = ''
if mode & consts.LOBJECT_READ:
smode += 'r'
if mode & consts.LOBJECT_WRITE:
smode += 'w'
if not smode:
smode += 'n'
if mode & consts.LOBJECT_TEXT:
smode += 't'
else:
smode += 'b'
return smode
|