Example #1
0
class rpiImageDbxClass(rpiBaseClass):
	"""
	Implements the rpiImageDb class to manage images in a remote directory (dropbox).
	"""

	def __init__(self, name, rpi_apscheduler, rpi_events, rpi_config, cam_rpififo=None):

		### Get the Dbx error event
		#self._eventDbErr 	= rpi_events.eventErrList["DBXJob"]

		### Get the custom config parameters
		self._config = rpi_config

		### Get FIFO buffer for images from the camera (deque)
		self._imageFIFO = cam_rpififo

		### The FIFO buffer for the uploaded images (deque)
		self.imageUpldFIFO = rpififo.rpiFIFOClass([], 576)
		self.imageUpldFIFO.crtSubDir = ''

		### Init base class
		super().__init__(name, rpi_apscheduler, rpi_events)

	def __repr__(self):
		return "<%s (name=%s, rpi_apscheduler=%s, rpi_events=dict(), rpi_config=%s, dbuff_rpififo=%s)>" % (self.__class__.__name__, self.name, self._sched, self._config, self._imageFIFO)

	def __str__(self):
		msg = super().__str__()
		return "%s::: dbinfo: %s, config: %s\nimageUpldFIFO: %s\n%s" % \
				(self.name, self.dbinfo, self._config, self.imageUpldFIFO, msg)

	def __del__(self):
		### Clean base class
		super().__del__()



	#
	# Main interface methods
	#

	def jobRun(self):

		try:
			# Lock the buffer
			self._imageFIFO.acquireSemaphore()

			# Get the current images in the FIFO
			# Refresh the last remote image when available
			if len(self._imageFIFO):

				# Update remote cam image with the current (last) image
				if not (self._imageFIFO[-1] == self.crt_image_snap):
					self._putImage(self._imageFIFO[-1], self._config['image_snap'], True)
					self.crt_image_snap = self._imageFIFO[-1]
					self.numImgUpdDb += 1
					logging.info("Updated remote %s with %s" % (self._config['image_snap'], self._imageFIFO[-1]) )


				# Lock the upload buffer
				self.imageUpldFIFO.acquireSemaphore()

				# Check if a new upload sub-folder has to be used
				if not (self.imageUpldFIFO.crtSubDir == self._imageFIFO.crtSubDir):
					self.imageUpldFIFO.crtSubDir = self._imageFIFO.crtSubDir
					self.upldir = os.path.normpath(os.path.join(self._config['image_dir'], self.imageUpldFIFO.crtSubDir))
					self._mkdirImage(self.upldir)

				# Upload only images in the FIFO which have not been uploaded yet
				for img in self._imageFIFO:
					if not img in self.imageUpldFIFO:
						self._putImage(img, os.path.join(self.upldir, os.path.basename(img)))
						logging.info("Uploaded %s" % img )

				# Release the upload buffer
				self.imageUpldFIFO.releaseSemaphore()

				# Update status
				self.statusUpdate = (self.name, self.numImgUpdDb)

			else:
				# Update status
				self.statusUpdate = (self.name, ERRNONE)

				logging.info('Nothing to upload')


		# Handle exceptions, mostly HTTP/SSL related!
		except exceptions.Timeout as e:
			# Catching this error will catch both ReadTimeout and ConnectTimeout.
			raise rpiBaseClassError("%s::: jobRun(): Connect/ReadTimeoutError:\n%s" % (self.name, str(e)), ERRLEV2)

		except exceptions.ConnectionError as e:
			# A Connection error occurred.
			raise rpiBaseClassError("%s::: jobRun(): ConnectionError:\n%s" % (self.name, str(e)), ERRLEV2)

		except exceptions.HTTPError as e:
			# An HTTP error occurred.
			raise rpiBaseClassError("%s::: jobRun(): HTTPError:\n%s" % (self.name, str(e)), ERRLEV2)

		except exceptions.RequestException as e:
			# There was an ambiguous exception that occurred while handling your request.
			raise rpiBaseClassError("%s::: jobRun(): RequestException:\n%s" % (self.name, str(e)), ERRLEV2)

# 			except BadStatusLine as e:
# 				self.eventErr_set('run()')
# 				logging.debug("BadStatusLine:\n%s" % str(e))
# 				pass

		except rpiBaseClassError as e:
			if e.errval == ERRCRIT:
				self.endDayOAM()
			raise rpiBaseClassError("%s::: jobRun(): %s" % (self.name, e.errmsg), e.errval)

		except RuntimeError as e:
			self.endDayOAM()
			raise rpiBaseClassError("%s::: jobRun(): RuntimeError:\n%s" % (self.name, str(e)), ERRCRIT)

		except:
			self.endDayOAM()
			raise rpiBaseClassError("%s::: jobRun(): Unhandled Exception:\n%s" % (self.name, str(sys.exc_info())), ERRCRIT)

		finally:
			# Release the buffer
			self._imageFIFO.releaseSemaphore()


	def initClass(self):
		""""
		(re)Initialize the class.
		"""

		#self.imageDbHash = None
		self._imageDbCursor = None
		self.imageDbList = []
		self.numImgUpdDb = 0

		self.crt_image_snap = None
		self.imgid = self._imageFIFO.camID + '.jpg'
		self.upldir = os.path.normpath(os.path.join(self._config['image_dir'], self.imageUpldFIFO.crtSubDir))
		self.logfile = './upldlog.json'

		### When there are already images listed in the upload log file, then
		# make sure we don't upload them to the remote folder again
		# Else, create the file with an empty list; to be updated in endDayOAM()
		try:
			self.imageUpldFIFO.acquireSemaphore()
			self.imageUpldFIFO.clear()

			if os.path.isfile(self.logfile):
				with open(self.logfile,'r') as logf:
					upldimg = json.load(logf)

				for img in upldimg:
					self.imageUpldFIFO.append(img)

				del upldimg

				logging.info("%s::: Local log file %s found and loaded." % (self.name, self.logfile))
			else:
				with open(self.logfile,'w') as logf:
					json.dump([], logf)
					logging.info("%s::: Local log file %s initialized." % (self.name, self.logfile))

		except IOError:
			raise rpiBaseClassError("%s::: initClass(): Local log file %s was not found or could not be created." % (self.name, self.logfile), ERRCRIT)

		finally:
			# Release the upload buffer
			self.imageUpldFIFO.releaseSemaphore()

		### Init Dropbox API client
		self._token_file = self._config['token_file']
		self._dbx = None
		self.dbinfo = None
		try:
			with open(self._token_file, 'r') as token:
				self._dbx = Dropbox(token.read())

			info = self._dbx.users_get_current_account()
			# info._all_field_names_ =
			# {'account_id', 'is_paired', 'locale', 'email', 'name', 'team', 'country', 'account_type', 'referral_link'}
			self.dbinfo ={'email': info.email, 'referral_link': info.referral_link}

			logging.info("%s::: Loaded access token from ''%s''" % (self.name, self._token_file) )

			### Create remote root folder (relative to app root) if it does not exist yet
			self._mkdirImage(os.path.normpath(self._config['image_dir']))

		except rpiBaseClassError as e:
			if e.errval == ERRCRIT:
				self.endDayOAM()
			raise rpiBaseClassError("initClass(): %s" % e.errmsg, e.errval)

		except IOError:
			self.endDayOAM()
			raise rpiBaseClassError("initClass(): Token file ''%s'' could not be read." % (self.name, self._token_file), ERRCRIT)

		except AuthError as e:
			self.endDayOAM()
			raise rpiBaseClassError("initClass(): AuthError:\n%s" % e.error, ERRCRIT)

		except DropboxException as e:
			self.endDayOAM()
			raise rpiBaseClassError("initClass(): DropboxException:\n%s" %  str(e), ERRCRIT)

		except InternalServerError as e:
			self.endDayOAM()
			raise rpiBaseClassError("initClass(): InternalServerError:\n%s" % str(e.status_code),  ERRCRIT)


	def endDayOAM(self):
		"""
		End-of-Day Operation and Maintenance sequence.
		"""

		self._lsImage(self.upldir)
		logging.info("%s::: %d images in the remote folder %s" % (self.name, len(self.imageDbList), self.upldir))

		# Lock the uplaod buffer
		self.imageUpldFIFO.acquireSemaphore()

		try:
			upldimg=[]
			for img in self.imageUpldFIFO:
				upldimg.append(img)

			with open(self.logfile,'w') as logf:
				json.dump(upldimg, logf)

			del upldimg

			logging.info("%s::: Local log file %s updated." % (self.name, self.logfile))

		except IOError:
			raise rpiBaseClassError("endDayOAM(): Local log file %s was not found." % self.logfile,  ERRCRIT)

		finally:
			# Release the upload buffer
			self.imageUpldFIFO.releaseSemaphore()

#	def endOAM(self):
#		"""
#		End OAM procedure.
#		"""
	@atexit.register
	def atexitend():
		self.endDayOAM()

	def _lsImage(self,from_path):
		"""
		List the image/video files in the remote directory.
		Stores the found file names in self.imageDbList.
		"""
		try:
			if self._imageDbCursor is None:
				self.ls_ref = self._dbx.files_list_folder('/' + os.path.normpath(from_path), recursive=False, include_media_info=True )
			else:
				new_ls = self._dbx.files_list_folder_continue(self._imageDbCursor)
				if new_ls.entries == []:
					logging.debug("%s::: _lsImage():: No changes on the server." % self.name)
				else:
					self.ls_ref = new_ls

			# Select only images and only the ones for the current imgid (camid)
			foundImg = False
			for f in self.ls_ref.entries:
				if 'media_info' in f._all_field_names_ and \
					f.media_info is not None:
					if self.imgid in f.path_lower:
						img = '.%s' % f.path_lower
						foundImg = True
						if not img in self.imageDbList:
							self.imageDbList.append(img)


			if not foundImg:
				self.imageDbList = []

			### Store the hash of the folder
			self._imageDbCursor = self.ls_ref.cursor

			if len(self.imageDbList) > 0:
				logging.debug("%s::: _lsImage():: imageDbList[0..%d]: %s .. %s" % (self.name, len(self.imageDbList)-1, self.imageDbList[0], self.imageDbList[-1]) )
			else:
				logging.debug("%s::: _lsImage():: imageDbList[]: empty" % self.name)

		except ApiError as e:
			raise rpiBaseClassError("_lsImage(): %s" % e.error, ERRLEV2)


	def _putImage(self, from_path, to_path, overwrite=False):
		"""
		Copy local file to remote file.
		Stores the uploaded files names in self.imageUpldFIFO.

		Examples:
		_putImage('./path/test.jpg', '/path/dropbox-upload-test.jpg')
		"""
		try:
			mode = (WriteMode.overwrite if overwrite else WriteMode.add)

			with open(from_path, "rb") as from_file:
				self._dbx.files_upload( from_file, '/' + os.path.normpath(to_path), mode)

			if not overwrite:
				self.imageUpldFIFO.append(from_path)

			logging.debug("%s::: _putImage(): Uploaded file from %s to remote %s" % (self.name, from_path, to_path))

		except IOError:
			raise rpiBaseClassError("_putImage(): Local img file %s could not be opened." %  from_path, ERRCRIT)

		except ApiError as e:
			raise rpiBaseClassError("_putImage(): %s" % e.error, ERRLEV2)


	def _mkdirImage(self, path):
		"""
		Create a new remote directory.

		Examples:
		_mkdirImage('/dropbox_dir_test')
		"""
		try:
			self._dbx.files_create_folder('/' + os.path.normpath(path))

			logging.debug("%s::: Remote output folder /%s created." % (self.name, path))

		except ApiError as e:
			noerr = False
			# dropbox.files.CreateFolderError
			if e.error.is_path():
				# dropbox.files.WriteError
				we = e.error.get_path()
				if we.is_conflict():
					# dropbox.files.WriteConflictError
					wce = we.get_conflict()
					# union tag is 'folder'
					if wce.is_folder():
						logging.info("%s::: Remote output folder /%s already exist!" % (self.name, path))
						noerr = True

			if not noerr:
				raise rpiBaseClassError("_mkdirImage(): Remote output folder /%s was not created! %s" % (path, e.error), ERRCRIT)
			else:
				pass


	def _mvImage(self, from_path, to_path):
		"""
		Move/rename a remote file or directory.

		Examples:
		_mvImage('./path1/dropbox-move-test.jpg', '/path2/dropbox-move-test.jpg')
		"""
		try:
			self._dbx.files_move( '/' + os.path.normpath(from_path), '/' +  os.path.normpath(to_path) )

			logging.debug("%s::: _mvImage(): Moved file from %s to %s" % (self.name, from_path, to_path))

		except ApiError as e:
			raise rpiBaseClassError("_mvImage(): Image %s could not be moved to %s! %s" % (from_path, to_path, e.error), ERRLEV2)