You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

backups.py 7.7 KiB

14 years ago
13 years ago
13 years ago
13 years ago
13 years ago
13 years ago
13 years ago
13 years ago
13 years ago
13 years ago
14 years ago
13 years ago
13 years ago
13 years ago
13 years ago
13 years ago
13 years ago
14 years ago
13 years ago
13 years ago
13 years ago
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243
  1. # Copyright (c) 2012 Web Notes Technologies Pvt Ltd (http://erpnext.com)
  2. #
  3. # MIT License (MIT)
  4. #
  5. # Permission is hereby granted, free of charge, to any person obtaining a
  6. # copy of this software and associated documentation files (the "Software"),
  7. # to deal in the Software without restriction, including without limitation
  8. # the rights to use, copy, modify, merge, publish, distribute, sublicense,
  9. # and/or sell copies of the Software, and to permit persons to whom the
  10. # Software is furnished to do so, subject to the following conditions:
  11. #
  12. # The above copyright notice and this permission notice shall be included in
  13. # all copies or substantial portions of the Software.
  14. #
  15. # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
  16. # INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
  17. # PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
  18. # HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
  19. # CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE
  20. # OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
  21. #
  22. from __future__ import unicode_literals
  23. """
  24. This module handles the On Demand Backup utility
  25. To setup in defs set:
  26. backup_path: path where backups will be taken (for eg /backups)
  27. backup_link_path: download link for backups (eg /var/www/wnframework/backups)
  28. backup_url: base url of the backup folder (eg http://mysite.com/backups)
  29. """
  30. #Imports
  31. import os, webnotes
  32. from datetime import datetime
  33. #Global constants
  34. verbose = 0
  35. import conf
  36. #-------------------------------------------------------------------------------
  37. class BackupGenerator:
  38. """
  39. This class contains methods to perform On Demand Backup
  40. To initialize, specify (db_name, user, password, db_file_name=None)
  41. If specifying db_file_name, also append ".sql.gz"
  42. """
  43. def __init__(self, db_name, user, password):
  44. self.db_name = db_name
  45. self.user = user
  46. self.password = password
  47. self.backup_file_name = self.get_backup_file_name()
  48. self.backup_file_path = os.path.join(get_backup_path(), self.backup_file_name)
  49. def get_backup_file_name(self):
  50. import random
  51. todays_date = "".join(str(datetime.date(datetime.today())).split("-"))
  52. random_number = str(int(random.random()*99999999))
  53. #Generate a random name using today's date and a 8 digit random number
  54. random_name = todays_date + "_" + random_number + ".sql.gz"
  55. return random_name
  56. def take_dump(self):
  57. """
  58. Dumps a db via mysqldump
  59. """
  60. import webnotes.utils
  61. # escape reserved characters
  62. args = dict([item[0], webnotes.utils.esc(item[1], '$ ')]
  63. for item in self.__dict__.copy().items())
  64. cmd_string = """mysqldump -u %(user)s -p%(password)s %(db_name)s | gzip -c > %(backup_file_path)s""" % args
  65. err, out = webnotes.utils.execute_in_shell(cmd_string)
  66. def get_recipients(self):
  67. """
  68. Get recepient's email address
  69. """
  70. #import webnotes.db
  71. #webnotes.conn = webnotes.db.Database(use_default=1)
  72. recipient_list = webnotes.conn.sql(\
  73. """SELECT parent FROM tabUserRole
  74. WHERE role='System Manager'
  75. AND parent!='Administrator'
  76. AND parent IN
  77. (SELECT email FROM tabProfile WHERE enabled=1)""")
  78. return [i[0] for i in recipient_list]
  79. def send_email(self, backup_file):
  80. """
  81. Sends the link to backup file located at erpnext/backups
  82. """
  83. backup_url = webnotes.conn.get_value('Website Settings',
  84. 'Website Settings', 'subdomain') or ''
  85. backup_url = os.path.join('http://' + backup_url, 'backups')
  86. file_url = os.path.join(backup_url, backup_file)
  87. from webnotes.utils.email_lib import sendmail
  88. recipient_list = self.get_recipients()
  89. msg = """<a href="%(file_url)s">Click here to begin downloading\
  90. your backup</a>
  91. This link will be valid for 24 hours.
  92. Also, a new backup will be available for download (if requested)\
  93. only after 24 hours.""" % {"file_url":file_url}
  94. backup_file_path = os.path.join(get_backup_path(), backup_file)
  95. datetime_str = datetime.fromtimestamp(os.stat(backup_file_path).st_ctime)
  96. subject = datetime_str.strftime("%d/%m/%Y %H:%M:%S") + """ - Backup ready to be downloaded"""
  97. sendmail(recipients=recipient_list, msg=msg, subject=subject)
  98. return recipient_list
  99. def get_backup(self):
  100. """
  101. Takes a new dump if existing file is old
  102. and sends the link to the file as email
  103. """
  104. #Check if file exists and is less than a day old
  105. #If not Take Dump
  106. backup_file = recent_backup_exists()
  107. if not backup_file:
  108. self.take_dump()
  109. backup_file = self.backup_file_name
  110. #Email Link
  111. recipient_list = self.send_email(backup_file)
  112. return recipient_list
  113. @webnotes.whitelist()
  114. def get_backup():
  115. """
  116. This function is executed when the user clicks on
  117. Toos > Download Backup
  118. """
  119. #if verbose: print webnotes.conn.cur_db_name + " " + conf.db_password
  120. delete_temp_backups()
  121. odb = BackupGenerator(webnotes.conn.cur_db_name, webnotes.conn.cur_db_name,\
  122. webnotes.get_db_password(webnotes.conn.cur_db_name))
  123. recipient_list = odb.get_backup()
  124. webnotes.msgprint("""A download link to your backup will be emailed \
  125. to you shortly on the following email address:
  126. %s""" % (', '.join(recipient_list)))
  127. def scheduled_backup():
  128. """this function is called from scheduler
  129. deletes backups older than 7 days
  130. takes backup"""
  131. delete_temp_backups(older_than=168)
  132. odb = BackupGenerator(webnotes.conn.cur_db_name, webnotes.conn.cur_db_name,\
  133. webnotes.get_db_password(webnotes.conn.cur_db_name))
  134. odb.take_dump()
  135. from webnotes.utils import now
  136. print "backup taken -", odb.backup_file_name, "- on", now()
  137. def recent_backup_exists():
  138. file_list = os.listdir(get_backup_path())
  139. for this_file in file_list:
  140. this_file_path = os.path.join(get_backup_path(), this_file)
  141. if not is_file_old(this_file_path):
  142. return this_file
  143. return None
  144. def delete_temp_backups(older_than=24):
  145. """
  146. Cleans up the backup_link_path directory by deleting files older than 24 hours
  147. """
  148. file_list = os.listdir(get_backup_path())
  149. for this_file in file_list:
  150. this_file_path = os.path.join(get_backup_path(), this_file)
  151. if is_file_old(this_file_path, older_than):
  152. os.remove(this_file_path)
  153. def is_file_old(db_file_name, older_than=24):
  154. """
  155. Checks if file exists and is older than specified hours
  156. Returns ->
  157. True: file does not exist or file is old
  158. False: file is new
  159. """
  160. if os.path.isfile(db_file_name):
  161. from datetime import timedelta
  162. import time
  163. #Get timestamp of the file
  164. file_datetime = datetime.fromtimestamp\
  165. (os.stat(db_file_name).st_ctime)
  166. if datetime.today() - file_datetime >= timedelta(hours = older_than):
  167. if verbose: print "File is old"
  168. return True
  169. else:
  170. if verbose: print "File is recent"
  171. return False
  172. else:
  173. if verbose: print "File does not exist"
  174. return True
  175. backup_path = None
  176. def get_backup_path():
  177. global backup_path
  178. if not backup_path:
  179. import os, conf
  180. backup_path = os.path.join(os.path.dirname(os.path.abspath(conf.__file__)),
  181. conf.backup_path)
  182. return backup_path
  183. #-------------------------------------------------------------------------------
  184. if __name__ == "__main__":
  185. """
  186. is_file_old db_name user password
  187. get_backup db_name user password
  188. """
  189. import sys
  190. cmd = sys.argv[1]
  191. if cmd == "is_file_old":
  192. odb = BackupGenerator(sys.argv[2], sys.argv[3], sys.argv[4])
  193. is_file_old(odb.db_file_name)
  194. if cmd == "get_backup":
  195. odb = BackupGenerator(sys.argv[2], sys.argv[3], sys.argv[4])
  196. odb.get_backup()
  197. if cmd == "take_dump":
  198. odb = BackupGenerator(sys.argv[2], sys.argv[3], sys.argv[4])
  199. odb.take_dump()
  200. if cmd == "send_email":
  201. odb = BackupGenerator(sys.argv[2], sys.argv[3], sys.argv[4])
  202. odb.send_email("abc.sql.gz")
  203. if cmd == "delete_temp_backups":
  204. delete_temp_backups()