class_docstr: OLE container object
  
  This class encapsulates the interface to an OLE 2 structured
  storage file.  Use the listdir and openstream methods to
  access the contents of this file.
  
  Object names are given as a list of strings, one for each subentry
  level.  The root entry should be omitted.  For example, the following
  code extracts all image streams from a Microsoft Image Composer file::
  
      ole = OleFileIO("fan.mic")
  
      for entry in ole.listdir():
          if entry[1:2] == "Image":
              fin = ole.openstream(entry)
              fout = open(entry[0:1], "wb")
              while True:
                  s = fin.read(8192)
                  if not s:
                      break
                  fout.write(s)
  
  You can use the viewer application provided with the Python Imaging
  Library to view the resulting files (which happens to be standard
  TIFF files).
class_docstr_tok: ["OLE","container","object","This","class","encapsulates","the","interface","to","an","OLE","2","structured","storage","file",".","Use","the","listdir","and","openstream","methods","to","access","the","contents","of","this","file",".","Object","names","are","given","as","a","list","of","strings",",","one","for","each","subentry","level",".","The","root","entry","should","be","omitted",".","For","example",",","the","following","code","extracts","all","image","streams","from","a","Microsoft","Image","Composer","file",":",":","ole","=","OleFileIO","(","``","fan.mic","''",")","for","entry","in","ole.listdir","(",")",":","if","entry","[","1:2","]","==","``","Image","''",":","fin","=","ole.openstream","(","entry",")","fout","=","open","(","entry","[","0:1","]",",","``","wb","''",")","while","True",":","s","=","fin.read","(","8192",")","if","not","s",":","break","fout.write","(","s",")","You","can","use","the","viewer","application","provided","with","the","Python","Imaging","Library","to","view","the","resulting","files","(","which","happens","to","be","standard","TIFF","files",")","."]
docstr: Return properties described in substream.
  
  :param filename: path of stream in storage tree (see openstream for syntax)
  :param convert_time: bool, if True timestamps will be converted to Python datetime
  :param no_conversion: None or list of int, timestamps not to be converted
      (for example total editing time is not a real timestamp)
  
  :returns: a dictionary of values indexed by id (integer)
docstr_tok: ["Return","properties","described","in","substream",".",":","param","filename",":","path","of","stream","in","storage","tree","(","see","openstream","for","syntax",")",":","param","convert_time",":","bool",",","if","True","timestamps","will","be","converted","to","Python","datetime",":","param","no_conversion",":","None","or","list","of","int",",","timestamps","not","to","be","converted","(","for","example","total","editing","time","is","not","a","real","timestamp",")",":","returns",":","a","dictionary","of","values","indexed","by","id","(","integer",")"]
code: def getproperties(
      self, filename, convert_time=False, no_conversion=None
  ):
      """
      Return properties described in substream.
  
      :param filename: path of stream in storage tree (see openstream for syntax)
      :param convert_time: bool, if True timestamps will be converted to Python datetime
      :param no_conversion: None or list of int, timestamps not to be converted
          (for example total editing time is not a real timestamp)
  
      :returns: a dictionary of values indexed by id (integer)
      """
      # REFERENCE: [MS-OLEPS] https://msdn.microsoft.com/en-us/library/dd942421.aspx
      # make sure no_conversion is a list, just to simplify code below:
      if no_conversion == None:
          no_conversion = []
      # stream path as a string to report exceptions:
      streampath = filename
      if not isinstance(streampath, str):
          streampath = "/".join(streampath)
  
      fp = self.openstream(filename)
  
      data = {}
  
      try:
          # header
          s = fp.read(28)
          clsid = _clsid(s[8:24])
  
          # format id
          s = fp.read(20)
          fmtid = _clsid(s[:16])
          fp.seek(i32(s, 16))
  
          # get section
          s = b"****" + fp.read(i32(fp.read(4)) - 4)
          # number of properties:
          num_props = i32(s, 4)
      except BaseException as exc:
          # catch exception while parsing property header, and only raise
          # a DEFECT_INCORRECT then return an empty dict, because this is not
          # a fatal error when parsing the whole file
          msg = (
              "Error while parsing properties header in stream %s: %s"
              % (repr(streampath), exc)
          )
          self._raise_defect(DEFECT_INCORRECT, msg, type(exc))
          return data
  
      # clamp num_props based on the data length
      num_props = min(num_props, int(len(s) / 8))
  
      for i in iterrange(num_props):
          property_id = 0  # just in case of an exception
          try:
              property_id = i32(s, 8 + i * 8)
              offset = i32(s, 12 + i * 8)
              property_type = i32(s, offset)
  
              log.debug(
                  "property id=%d: type=%d offset=%X"
                  % (property_id, property_type, offset)
              )
  
              # test for common types first (should perhaps use
              # a dictionary instead?)
  
              if property_type == VT_I2:  # 16-bit signed integer
                  value = i16(s, offset + 4)
                  if value >= 32768:
                      value = value - 65536
              elif property_type == VT_UI2:  # 2-byte unsigned integer
                  value = i16(s, offset + 4)
              elif property_type in (VT_I4, VT_INT, VT_ERROR):
                  # VT_I4: 32-bit signed integer
                  # VT_ERROR: HRESULT, similar to 32-bit signed integer,
                  # see https://msdn.microsoft.com/en-us/library/cc230330.aspx
                  value = i32(s, offset + 4)
              elif property_type in (
                  VT_UI4,
                  VT_UINT,
              ):  # 4-byte unsigned integer
                  value = i32(s, offset + 4)  # FIXME
              elif property_type in (VT_BSTR, VT_LPSTR):
                  # CodePageString, see https://msdn.microsoft.com/en-us/library/dd942354.aspx
                  # size is a 32 bits integer, including the null terminator, and
                  # possibly trailing or embedded null chars
                  # TODO: if codepage is unicode, the string should be converted as such
                  count = i32(s, offset + 4)
                  value = s[offset + 8 : offset + 8 + count - 1]
                  # remove all null chars:
                  value = value.replace(b"\x00", b"")
              elif property_type == VT_BLOB:
                  # binary large object (BLOB)
                  # see https://msdn.microsoft.com/en-us/library/dd942282.aspx
                  count = i32(s, offset + 4)
                  value = s[offset + 8 : offset + 8 + count]
              elif property_type == VT_LPWSTR:
                  # UnicodeString
                  # see https://msdn.microsoft.com/en-us/library/dd942313.aspx
                  # "the string should NOT contain embedded or additional trailing
                  # null characters."
                  count = i32(s, offset + 4)
                  value = self._decode_utf16_str(
                      s[offset + 8 : offset + 8 + count * 2]
                  )
              elif property_type == VT_FILETIME:
                  value = long(i32(s, offset + 4)) + (
                      long(i32(s, offset + 8)) << 32
                  )
                  # FILETIME is a 64-bit int: "number of 100ns periods
                  # since Jan 1,1601".
                  if convert_time and property_id not in no_conversion:
                      log.debug(
                          "Converting property #%d to python datetime, value=%d=%fs"
                          % (
                              property_id,
                              value,
                              float(value) / 10000000,
                          )
                      )
                      # convert FILETIME to Python datetime.datetime
                      # inspired from https://code.activestate.com/recipes/511425-filetime-to-datetime/
                      _FILETIME_null_date = datetime.datetime(
                          1601, 1, 1, 0, 0, 0
                      )
                      log.debug(
                          "timedelta days=%d"
                          % (value // (10 * 1000000 * 3600 * 24))
                      )
                      value = _FILETIME_null_date + datetime.timedelta(
                          microseconds=value // 10
                      )
                  else:
                      # legacy code kept for backward compatibility: returns a
                      # number of seconds since Jan 1,1601
                      value = value // 10000000  # seconds
              elif property_type == VT_UI1:  # 1-byte unsigned integer
                  value = i8(s[offset + 4])
              elif property_type == VT_CLSID:
                  value = _clsid(s[offset + 4 : offset + 20])
              elif property_type == VT_CF:
                  # PropertyIdentifier or ClipboardData??
                  # see https://msdn.microsoft.com/en-us/library/dd941945.aspx
                  count = i32(s, offset + 4)
                  value = s[offset + 8 : offset + 8 + count]
              elif property_type == VT_BOOL:
                  # VARIANT_BOOL, 16 bits bool, 0x0000=Fals, 0xFFFF=True
                  # see https://msdn.microsoft.com/en-us/library/cc237864.aspx
                  value = bool(i16(s, offset + 4))
              else:
                  value = None  # everything else yields "None"
                  log.debug(
                      "property id=%d: type=%d not implemented in parser yet"
                      % (property_id, property_type)
                  )
  
              # missing: VT_EMPTY, VT_NULL, VT_R4, VT_R8, VT_CY, VT_DATE,
              # VT_DECIMAL, VT_I1, VT_I8, VT_UI8,
              # see https://msdn.microsoft.com/en-us/library/dd942033.aspx
  
              # FIXME: add support for VT_VECTOR
              # VT_VECTOR is a 32 uint giving the number of items, followed by
              # the items in sequence. The VT_VECTOR value is combined with the
              # type of items, e.g. VT_VECTOR|VT_BSTR
              # see https://msdn.microsoft.com/en-us/library/dd942011.aspx
  
              # print("%08x" % property_id, repr(value), end=" ")
              # print("(%s)" % VT[i32(s, offset) & 0xFFF])
  
              data[property_id] = value
          except BaseException as exc:
              # catch exception while parsing each property, and only raise
              # a DEFECT_INCORRECT, because parsing can go on
              msg = (
                  "Error while parsing property id %d in stream %s: %s"
                  % (property_id, repr(streampath), exc)
              )
              self._raise_defect(DEFECT_INCORRECT, msg, type(exc))
  
      return data
  
imports: from __future__ import print_function
  import io
  import sys
  import struct, array, os.path, datetime, logging
label_desc: Use image node_id 35 for calling the OleFileIO obj's underlying member method code with example usage: obj.getproperties(filename, convert_time, no_conversion) and returns: data, data