|  | @ -29,6 +29,8 @@ import nextcloud_client | 
			
		
	
		
		
			
				
					|  |  | import os |  |  | import os | 
			
		
	
		
		
			
				
					|  |  | import paramiko |  |  | import paramiko | 
			
		
	
		
		
			
				
					|  |  | import requests |  |  | import requests | 
			
		
	
		
		
			
				
					|  |  |  |  |  | import shutil | 
			
		
	
		
		
			
				
					|  |  |  |  |  | import subprocess | 
			
		
	
		
		
			
				
					|  |  | import tempfile |  |  | import tempfile | 
			
		
	
		
		
			
				
					|  |  | import odoo |  |  | import odoo | 
			
		
	
		
		
			
				
					|  |  | from datetime import timedelta |  |  | from datetime import timedelta | 
			
		
	
	
		
		
			
				
					|  | @ -37,6 +39,7 @@ from requests.auth import HTTPBasicAuth | 
			
		
	
		
		
			
				
					|  |  | from werkzeug import urls |  |  | from werkzeug import urls | 
			
		
	
		
		
			
				
					|  |  | from odoo import api, fields, models, _ |  |  | from odoo import api, fields, models, _ | 
			
		
	
		
		
			
				
					|  |  | from odoo.exceptions import UserError, ValidationError |  |  | from odoo.exceptions import UserError, ValidationError | 
			
		
	
		
		
			
				
					|  |  |  |  |  | from odoo.tools import find_pg_tool, exec_pg_environ | 
			
		
	
		
		
			
				
					|  |  | from odoo.http import request |  |  | from odoo.http import request | 
			
		
	
		
		
			
				
					|  |  | from odoo.service import db |  |  | from odoo.service import db | 
			
		
	
		
		
			
				
					|  |  | 
 |  |  | 
 | 
			
		
	
	
		
		
			
				
					|  | @ -616,7 +619,7 @@ class DbBackupConfigure(models.Model): | 
			
		
	
		
		
			
				
					|  |  |                     backup_file = os.path.join(rec.backup_path, |  |  |                     backup_file = os.path.join(rec.backup_path, | 
			
		
	
		
		
			
				
					|  |  |                                                backup_filename) |  |  |                                                backup_filename) | 
			
		
	
		
		
			
				
					|  |  |                     f = open(backup_file, "wb") |  |  |                     f = open(backup_file, "wb") | 
			
		
	
		
		
			
				
					
					|  |  |                     odoo.service.db.dump_db(rec.db_name, f, rec.backup_format) |  |  |                     self.dump_data(rec.db_name, f, rec.backup_format) | 
			
				
				
			
		
	
		
		
	
		
		
			
				
					|  |  |                     f.close() |  |  |                     f.close() | 
			
		
	
		
		
			
				
					|  |  |                     # Remove older backups |  |  |                     # Remove older backups | 
			
		
	
		
		
			
				
					|  |  |                     if rec.auto_remove: |  |  |                     if rec.auto_remove: | 
			
		
	
	
		
		
			
				
					|  | @ -650,7 +653,7 @@ class DbBackupConfigure(models.Model): | 
			
		
	
		
		
			
				
					|  |  |                         ftp_server.mkd(rec.ftp_path) |  |  |                         ftp_server.mkd(rec.ftp_path) | 
			
		
	
		
		
			
				
					|  |  |                         ftp_server.cwd(rec.ftp_path) |  |  |                         ftp_server.cwd(rec.ftp_path) | 
			
		
	
		
		
			
				
					|  |  |                     with open(temp.name, "wb+") as tmp: |  |  |                     with open(temp.name, "wb+") as tmp: | 
			
		
	
		
		
			
				
					
					|  |  |                         odoo.service.db.dump_db(rec.db_name, tmp, |  |  |                         self.dump_data(rec.db_name, tmp, | 
			
				
				
			
		
	
		
		
	
		
		
			
				
					|  |  |                                                 rec.backup_format) |  |  |                                                 rec.backup_format) | 
			
		
	
		
		
			
				
					|  |  |                     ftp_server.storbinary('STOR %s' % backup_filename, |  |  |                     ftp_server.storbinary('STOR %s' % backup_filename, | 
			
		
	
		
		
			
				
					|  |  |                                           open(temp.name, "rb")) |  |  |                                           open(temp.name, "rb")) | 
			
		
	
	
		
		
			
				
					|  | @ -686,8 +689,7 @@ class DbBackupConfigure(models.Model): | 
			
		
	
		
		
			
				
					|  |  |                     temp = tempfile.NamedTemporaryFile( |  |  |                     temp = tempfile.NamedTemporaryFile( | 
			
		
	
		
		
			
				
					|  |  |                         suffix='.%s' % rec.backup_format) |  |  |                         suffix='.%s' % rec.backup_format) | 
			
		
	
		
		
			
				
					|  |  |                     with open(temp.name, "wb+") as tmp: |  |  |                     with open(temp.name, "wb+") as tmp: | 
			
		
	
		
		
			
				
					
					|  |  |                         odoo.service.db.dump_db(rec.db_name, tmp, |  |  |                         self.dump_data(rec.db_name, tmp, rec.backup_format) | 
			
				
				
			
		
	
		
		
			
				
					|  |  |                                                 rec.backup_format) |  |  |  | 
			
		
	
		
		
	
		
		
			
				
					|  |  |                     try: |  |  |                     try: | 
			
		
	
		
		
			
				
					|  |  |                         sftp.chdir(rec.sftp_path) |  |  |                         sftp.chdir(rec.sftp_path) | 
			
		
	
		
		
			
				
					|  |  |                     except IOError as e: |  |  |                     except IOError as e: | 
			
		
	
	
		
		
			
				
					|  | @ -723,7 +725,7 @@ class DbBackupConfigure(models.Model): | 
			
		
	
		
		
			
				
					|  |  |                     temp = tempfile.NamedTemporaryFile( |  |  |                     temp = tempfile.NamedTemporaryFile( | 
			
		
	
		
		
			
				
					|  |  |                         suffix='.%s' % rec.backup_format) |  |  |                         suffix='.%s' % rec.backup_format) | 
			
		
	
		
		
			
				
					|  |  |                     with open(temp.name, "wb+") as tmp: |  |  |                     with open(temp.name, "wb+") as tmp: | 
			
		
	
		
		
			
				
					
					|  |  |                         odoo.service.db.dump_db(rec.db_name, tmp, |  |  |                         self.dump_data(rec.db_name, tmp, | 
			
				
				
			
		
	
		
		
	
		
		
			
				
					|  |  |                                                 rec.backup_format) |  |  |                                                 rec.backup_format) | 
			
		
	
		
		
			
				
					|  |  |                     try: |  |  |                     try: | 
			
		
	
		
		
			
				
					|  |  |                         headers = { |  |  |                         headers = { | 
			
		
	
	
		
		
			
				
					|  | @ -784,7 +786,7 @@ class DbBackupConfigure(models.Model): | 
			
		
	
		
		
			
				
					|  |  |                 temp = tempfile.NamedTemporaryFile( |  |  |                 temp = tempfile.NamedTemporaryFile( | 
			
		
	
		
		
			
				
					|  |  |                     suffix='.%s' % rec.backup_format) |  |  |                     suffix='.%s' % rec.backup_format) | 
			
		
	
		
		
			
				
					|  |  |                 with open(temp.name, "wb+") as tmp: |  |  |                 with open(temp.name, "wb+") as tmp: | 
			
		
	
		
		
			
				
					
					|  |  |                     odoo.service.db.dump_db(rec.db_name, tmp, |  |  |                     self.dump_data(rec.db_name, tmp, | 
			
				
				
			
		
	
		
		
	
		
		
			
				
					|  |  |                                             rec.backup_format) |  |  |                                             rec.backup_format) | 
			
		
	
		
		
			
				
					|  |  |                 try: |  |  |                 try: | 
			
		
	
		
		
			
				
					|  |  |                     dbx = dropbox.Dropbox( |  |  |                     dbx = dropbox.Dropbox( | 
			
		
	
	
		
		
			
				
					|  | @ -819,8 +821,7 @@ class DbBackupConfigure(models.Model): | 
			
		
	
		
		
			
				
					|  |  |                 temp = tempfile.NamedTemporaryFile( |  |  |                 temp = tempfile.NamedTemporaryFile( | 
			
		
	
		
		
			
				
					|  |  |                     suffix='.%s' % rec.backup_format) |  |  |                     suffix='.%s' % rec.backup_format) | 
			
		
	
		
		
			
				
					|  |  |                 with open(temp.name, "wb+") as tmp: |  |  |                 with open(temp.name, "wb+") as tmp: | 
			
		
	
		
		
			
				
					
					|  |  |                     odoo.service.db.dump_db(rec.db_name, tmp, |  |  |                     self.dump_data(rec.db_name, tmp, rec.backup_format) | 
			
				
				
			
		
	
		
		
			
				
					|  |  |                                             rec.backup_format) |  |  |  | 
			
		
	
		
		
	
		
		
			
				
					|  |  |                 headers = { |  |  |                 headers = { | 
			
		
	
		
		
			
				
					|  |  |                     'Authorization': 'Bearer %s' % rec.onedrive_access_token, |  |  |                     'Authorization': 'Bearer %s' % rec.onedrive_access_token, | 
			
		
	
		
		
			
				
					|  |  |                     'Content-Type': 'application/json'} |  |  |                     'Content-Type': 'application/json'} | 
			
		
	
	
		
		
			
				
					|  | @ -880,8 +881,7 @@ class DbBackupConfigure(models.Model): | 
			
		
	
		
		
			
				
					|  |  |                                 for item in nc.list(folder_path): |  |  |                                 for item in nc.list(folder_path): | 
			
		
	
		
		
			
				
					|  |  |                                     backup_file_name = item.path.split("/")[-1] |  |  |                                     backup_file_name = item.path.split("/")[-1] | 
			
		
	
		
		
			
				
					|  |  |                                     backup_date_str = \ |  |  |                                     backup_date_str = \ | 
			
		
	
		
		
			
				
					
					|  |  |                                         backup_file_name.split("_")[ |  |  |                                         backup_file_name.split("_")[2] | 
			
				
				
			
		
	
		
		
			
				
					|  |  |                                             2] |  |  |  | 
			
		
	
		
		
	
		
		
			
				
					|  |  |                                     backup_date = fields.datetime.strptime( |  |  |                                     backup_date = fields.datetime.strptime( | 
			
		
	
		
		
			
				
					|  |  |                                         backup_date_str, '%Y-%m-%d').date() |  |  |                                         backup_date_str, '%Y-%m-%d').date() | 
			
		
	
		
		
			
				
					|  |  |                                     if (fields.date.today() - backup_date).days \ |  |  |                                     if (fields.date.today() - backup_date).days \ | 
			
		
	
	
		
		
			
				
					|  | @ -915,7 +915,7 @@ class DbBackupConfigure(models.Model): | 
			
		
	
		
		
			
				
					|  |  |                             temp = tempfile.NamedTemporaryFile( |  |  |                             temp = tempfile.NamedTemporaryFile( | 
			
		
	
		
		
			
				
					|  |  |                                 suffix='.%s' % rec.backup_format) |  |  |                                 suffix='.%s' % rec.backup_format) | 
			
		
	
		
		
			
				
					|  |  |                             with open(temp.name, "wb+") as tmp: |  |  |                             with open(temp.name, "wb+") as tmp: | 
			
		
	
		
		
			
				
					
					|  |  |                                 odoo.service.db.dump_db(rec.db_name, tmp, |  |  |                                 self.dump_data(rec.db_name, tmp, | 
			
				
				
			
		
	
		
		
	
		
		
			
				
					|  |  |                                                         rec.backup_format) |  |  |                                                         rec.backup_format) | 
			
		
	
		
		
			
				
					|  |  |                             backup_file_path = temp.name |  |  |                             backup_file_path = temp.name | 
			
		
	
		
		
			
				
					|  |  |                             remote_file_path = f"/{folder_name}/{rec.db_name}_" \ |  |  |                             remote_file_path = f"/{folder_name}/{rec.db_name}_" \ | 
			
		
	
	
		
		
			
				
					|  | @ -926,7 +926,7 @@ class DbBackupConfigure(models.Model): | 
			
		
	
		
		
			
				
					|  |  |                             temp = tempfile.NamedTemporaryFile( |  |  |                             temp = tempfile.NamedTemporaryFile( | 
			
		
	
		
		
			
				
					|  |  |                                 suffix='.%s' % rec.backup_format) |  |  |                                 suffix='.%s' % rec.backup_format) | 
			
		
	
		
		
			
				
					|  |  |                             with open(temp.name, "wb+") as tmp: |  |  |                             with open(temp.name, "wb+") as tmp: | 
			
		
	
		
		
			
				
					
					|  |  |                                 odoo.service.db.dump_db(rec.db_name, tmp, |  |  |                                 self.dump_data(rec.db_name, tmp, | 
			
				
				
			
		
	
		
		
	
		
		
			
				
					|  |  |                                                         rec.backup_format) |  |  |                                                         rec.backup_format) | 
			
		
	
		
		
			
				
					|  |  |                             backup_file_path = temp.name |  |  |                             backup_file_path = temp.name | 
			
		
	
		
		
			
				
					|  |  |                             remote_file_path = f"/{folder_name}/{rec.db_name}_" \ |  |  |                             remote_file_path = f"/{folder_name}/{rec.db_name}_" \ | 
			
		
	
	
		
		
			
				
					|  | @ -986,7 +986,7 @@ class DbBackupConfigure(models.Model): | 
			
		
	
		
		
			
				
					|  |  |                             temp = tempfile.NamedTemporaryFile( |  |  |                             temp = tempfile.NamedTemporaryFile( | 
			
		
	
		
		
			
				
					|  |  |                                 suffix='.%s' % rec.backup_format) |  |  |                                 suffix='.%s' % rec.backup_format) | 
			
		
	
		
		
			
				
					|  |  |                             with open(temp.name, "wb+") as tmp: |  |  |                             with open(temp.name, "wb+") as tmp: | 
			
		
	
		
		
			
				
					
					|  |  |                                 odoo.service.db.dump_db(rec.db_name, tmp, |  |  |                                 self.dump_data(rec.db_name, tmp, | 
			
				
				
			
		
	
		
		
	
		
		
			
				
					|  |  |                                                         rec.backup_format) |  |  |                                                         rec.backup_format) | 
			
		
	
		
		
			
				
					|  |  |                             backup_file_path = temp.name |  |  |                             backup_file_path = temp.name | 
			
		
	
		
		
			
				
					|  |  |                             remote_file_path = f"{rec.aws_folder_name}/{rec.db_name}_" \ |  |  |                             remote_file_path = f"{rec.aws_folder_name}/{rec.db_name}_" \ | 
			
		
	
	
		
		
			
				
					|  | @ -1004,8 +1004,71 @@ class DbBackupConfigure(models.Model): | 
			
		
	
		
		
			
				
					|  |  |                         # field to the error message and log the error |  |  |                         # field to the error message and log the error | 
			
		
	
		
		
			
				
					|  |  |                         rec.generated_exception = error |  |  |                         rec.generated_exception = error | 
			
		
	
		
		
			
				
					|  |  |                         _logger.info('Amazon S3 Exception: %s', error) |  |  |                         _logger.info('Amazon S3 Exception: %s', error) | 
			
		
	
		
		
			
				
					
					|  |  |                         # If notify_user is enabled, send an email to the user |  |  |                         # If notify_user is enabled, email the user | 
			
				
				
			
		
	
		
		
	
		
		
			
				
					|  |  |                         # notifying them about the failed backup |  |  |                         # notifying them about the failed backup | 
			
		
	
		
		
			
				
					|  |  |                         if rec.notify_user: |  |  |                         if rec.notify_user: | 
			
		
	
		
		
			
				
					
					|  |  |                             mail_template_failed.send_mail(rec.id, |  |  |                             mail_template_failed.send_mail(rec.id, force_send=True) | 
			
				
				
			
		
	
		
		
			
				
					
					|  |  |                                                            force_send=True) |  |  | 
 | 
			
				
				
			
		
	
		
		
	
		
		
	
		
		
			
				
					|  |  |  |  |  |     def dump_data(self, db_name, stream, backup_format): | 
			
		
	
		
		
			
				
					|  |  |  |  |  |         """Dump database `db` into file-like object `stream` if stream is None | 
			
		
	
		
		
			
				
					|  |  |  |  |  |         return a file object with the dump. """ | 
			
		
	
		
		
			
				
					|  |  |  |  |  | 
 | 
			
		
	
		
		
			
				
					|  |  |  |  |  |         cron_user_id = self.env.ref('auto_database_backup.ir_cron_auto_db_backup').user_id.id | 
			
		
	
		
		
			
				
					|  |  |  |  |  |         if cron_user_id != self.env.user.id: | 
			
		
	
		
		
			
				
					|  |  |  |  |  |             _logger.error( | 
			
		
	
		
		
			
				
					|  |  |  |  |  |                 'Unauthorized database operation. Backups should only be available from the cron job.') | 
			
		
	
		
		
			
				
					|  |  |  |  |  |             raise ValidationError("Unauthorized database operation. Backups should only be available from the cron job.") | 
			
		
	
		
		
			
				
					|  |  |  |  |  | 
 | 
			
		
	
		
		
			
				
					|  |  |  |  |  |         _logger.info('DUMP DB: %s format %s', db_name, backup_format) | 
			
		
	
		
		
			
				
					|  |  |  |  |  |         cmd = [find_pg_tool('pg_dump'), '--no-owner', db_name] | 
			
		
	
		
		
			
				
					|  |  |  |  |  |         env = exec_pg_environ() | 
			
		
	
		
		
			
				
					|  |  |  |  |  |         if backup_format == 'zip': | 
			
		
	
		
		
			
				
					|  |  |  |  |  |             with tempfile.TemporaryDirectory() as dump_dir: | 
			
		
	
		
		
			
				
					|  |  |  |  |  |                 filestore = odoo.tools.config.filestore(db_name) | 
			
		
	
		
		
			
				
					|  |  |  |  |  |                 cmd.append('--file=' + os.path.join(dump_dir, 'dump.sql')) | 
			
		
	
		
		
			
				
					|  |  |  |  |  |                 subprocess.run(cmd, env=env, stdout=subprocess.DEVNULL, | 
			
		
	
		
		
			
				
					|  |  |  |  |  |                                stderr=subprocess.STDOUT, check=True) | 
			
		
	
		
		
			
				
					|  |  |  |  |  |                 if os.path.exists(filestore): | 
			
		
	
		
		
			
				
					|  |  |  |  |  |                     shutil.copytree(filestore, | 
			
		
	
		
		
			
				
					|  |  |  |  |  |                                     os.path.join(dump_dir, 'filestore')) | 
			
		
	
		
		
			
				
					|  |  |  |  |  |                 with open(os.path.join(dump_dir, 'manifest.json'), 'w') as fh: | 
			
		
	
		
		
			
				
					|  |  |  |  |  |                     db = odoo.sql_db.db_connect(db_name) | 
			
		
	
		
		
			
				
					|  |  |  |  |  |                     with db.cursor() as cr: | 
			
		
	
		
		
			
				
					|  |  |  |  |  |                         json.dump(self._dump_db_manifest(cr), fh, indent=4) | 
			
		
	
		
		
			
				
					|  |  |  |  |  |                 if stream: | 
			
		
	
		
		
			
				
					|  |  |  |  |  |                     odoo.tools.osutil.zip_dir(dump_dir, stream, | 
			
		
	
		
		
			
				
					|  |  |  |  |  |                                               include_dir=False, | 
			
		
	
		
		
			
				
					|  |  |  |  |  |                                               fnct_sort=lambda | 
			
		
	
		
		
			
				
					|  |  |  |  |  |                                                   file_name: file_name != 'dump.sql') | 
			
		
	
		
		
			
				
					|  |  |  |  |  |                 else: | 
			
		
	
		
		
			
				
					|  |  |  |  |  |                     t = tempfile.TemporaryFile() | 
			
		
	
		
		
			
				
					|  |  |  |  |  |                     odoo.tools.osutil.zip_dir(dump_dir, t, include_dir=False, | 
			
		
	
		
		
			
				
					|  |  |  |  |  |                                               fnct_sort=lambda | 
			
		
	
		
		
			
				
					|  |  |  |  |  |                                                   file_name: file_name != 'dump.sql') | 
			
		
	
		
		
			
				
					|  |  |  |  |  |                     t.seek(0) | 
			
		
	
		
		
			
				
					|  |  |  |  |  |                     return t | 
			
		
	
		
		
			
				
					|  |  |  |  |  |         else: | 
			
		
	
		
		
			
				
					|  |  |  |  |  |             cmd.append('--format=c') | 
			
		
	
		
		
			
				
					|  |  |  |  |  |             process = subprocess.Popen(cmd, env=env, stdout=subprocess.PIPE) | 
			
		
	
		
		
			
				
					|  |  |  |  |  |             stdout, _ = process.communicate() | 
			
		
	
		
		
			
				
					|  |  |  |  |  |             if stream: | 
			
		
	
		
		
			
				
					|  |  |  |  |  |                 stream.write(stdout) | 
			
		
	
		
		
			
				
					|  |  |  |  |  |             else: | 
			
		
	
		
		
			
				
					|  |  |  |  |  |                 return stdout | 
			
		
	
		
		
			
				
					|  |  |  |  |  | 
 | 
			
		
	
		
		
			
				
					|  |  |  |  |  |     def _dump_db_manifest(self, cr): | 
			
		
	
		
		
			
				
					|  |  |  |  |  |         """ This function generates a manifest dictionary for database dump.""" | 
			
		
	
		
		
			
				
					|  |  |  |  |  |         pg_version = "%d.%d" % divmod(cr._obj.connection.server_version / 100, 100) | 
			
		
	
		
		
			
				
					|  |  |  |  |  |         cr.execute( | 
			
		
	
		
		
			
				
					|  |  |  |  |  |             "SELECT name, latest_version FROM ir_module_module WHERE state = 'installed'") | 
			
		
	
		
		
			
				
					|  |  |  |  |  |         modules = dict(cr.fetchall()) | 
			
		
	
		
		
			
				
					|  |  |  |  |  |         manifest = { | 
			
		
	
		
		
			
				
					|  |  |  |  |  |             'odoo_dump': '1', | 
			
		
	
		
		
			
				
					|  |  |  |  |  |             'db_name': cr.dbname, | 
			
		
	
		
		
			
				
					|  |  |  |  |  |             'version': odoo.release.version, | 
			
		
	
		
		
			
				
					|  |  |  |  |  |             'version_info': odoo.release.version_info, | 
			
		
	
		
		
			
				
					|  |  |  |  |  |             'major_version': odoo.release.major_version, | 
			
		
	
		
		
			
				
					|  |  |  |  |  |             'pg_version': pg_version, | 
			
		
	
		
		
			
				
					|  |  |  |  |  |             'modules': modules, | 
			
		
	
		
		
			
				
					|  |  |  |  |  |         } | 
			
		
	
		
		
			
				
					|  |  |  |  |  |         return manifest | 
			
		
	
	
		
		
			
				
					|  | 
 |