partition_demo.py 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328
  1. import sys, parted
  2. from PyQt5.QtCore import *
  3. from PyQt5.QtGui import *
  4. from PyQt5.QtWidgets import *
  5. ####################################################################
  6. def main():
  7. app = QApplication(sys.argv)
  8. app.setApplicationName('Disk Partitioning Demo')
  9. w = PartioningDemo()
  10. w.show()
  11. sys.exit(app.exec_())
  12. ####################################################################
  13. class PartioningDemo(QWidget):
  14. def __init__(self, *args):
  15. QWidget.__init__(self, *args)
  16. self.device_list = parted.getAllDevices()
  17. device_list_widget = QWidget()
  18. device_list_layout = QHBoxLayout()
  19. self.device_selection_combobox = QComboBox()
  20. self.refresh_button = QPushButton("Refresh")
  21. self.refresh_button.pressed.connect(self.refresh_device_list)
  22. for disk in self.device_list:
  23. try:
  24. if parted.Disk(disk).type == "msdos":
  25. self.device_selection_combobox.addItem(
  26. "{} {} GB ({})".format(disk.model, format(disk.getSize(unit="GB"), '.2f'), disk.path),
  27. userData=disk.path)
  28. except parted.DiskLabelException:
  29. disk = parted.freshDisk(disk, 'msdos')
  30. # For discarding CD-ROM like devices
  31. try:
  32. disk.commit()
  33. except parted.IOException:
  34. pass
  35. else:
  36. disk = disk.device
  37. self.device_selection_combobox.addItem(
  38. "{} {} GB ({})".format(disk.model, format(disk.getSize(unit="GB"), '.2f'), disk.path),
  39. userData=disk.path)
  40. self.device_selection_combobox.currentIndexChanged.connect(self.selected_device_changed)
  41. if self.device_selection_combobox.currentData():
  42. self.current_device = parted.getDevice(self.device_selection_combobox.currentData())
  43. self.current_disk = parted.Disk(self.current_device)
  44. self.partition_list_widget = QListWidget()
  45. for part in self.current_disk.partitions:
  46. part_info = self.get_part_info(part, "GB")
  47. part_item = QListWidgetItem(
  48. "{}\t\t{} GB\t{}\t{}".format(part_info["path"], part_info["size"], part_info["file_system_type"],
  49. part_info["flags"]))
  50. part_item.setData(Qt.UserRole, part_info["order"])
  51. if part_info["type"] == parted.PARTITION_NORMAL:
  52. part_item.setIcon(QIcon("images/primary.xpm"))
  53. elif part_info["type"] == parted.PARTITION_EXTENDED:
  54. part_item.setIcon(QIcon("images/extended.xpm"))
  55. elif part_info["type"] == parted.PARTITION_LOGICAL:
  56. part_item.setIcon(QIcon("images/logical.xpm"))
  57. self.partition_list_widget.addItem(part_item)
  58. for free_space in self.current_disk.getFreeSpacePartitions():
  59. total_size = 0
  60. part_info = self.get_part_info(free_space, "GB")
  61. if float(part_info["size"]) > 0:
  62. if part_info["type"] == 5:
  63. extended_unallocated_item = QListWidgetItem(
  64. "{}\t{} GB".format("Unallocated", part_info["size"]))
  65. extended_unallocated_item.setIcon(QIcon("images/blank.xpm"))
  66. extended_unallocated_item.setData(Qt.UserRole, "unallocated")
  67. self.partition_list_widget.addItem(extended_unallocated_item)
  68. if part_info["type"] == parted.PARTITION_FREESPACE:
  69. total_size = total_size + float(part_info["size"])
  70. unallocated_item = QListWidgetItem("{}\t{} GB".format("Unallocated", total_size))
  71. unallocated_item.setIcon(QIcon("images/blank.xpm"))
  72. unallocated_item.setData(Qt.UserRole, "unallocated")
  73. self.partition_list_widget.addItem(unallocated_item)
  74. device_list_layout.addWidget(self.device_selection_combobox)
  75. device_list_layout.addWidget(self.refresh_button)
  76. device_list_widget.setLayout(device_list_layout)
  77. layout = QVBoxLayout()
  78. layout.addWidget(device_list_widget)
  79. layout.addWidget(self.partition_list_widget)
  80. partition_type_index_widget = QLabel()
  81. partition_type_index_widget.setPixmap(QPixmap("images/lejant.png"))
  82. partition_type_index_widget.setAlignment(Qt.AlignCenter)
  83. layout.addWidget(partition_type_index_widget)
  84. self.partition_list_widget.itemClicked.connect(self.on_part_select)
  85. self.partition_list_widget.itemDoubleClicked.connect(self.change_partition_format)
  86. op_widget = QWidget()
  87. op_buttons_layout = QHBoxLayout()
  88. self.new_part_btn = QPushButton("Add new partition")
  89. self.new_part_btn.pressed.connect(self.add_new_part)
  90. self.delete_part_btn = QPushButton("Delete partition")
  91. self.delete_part_btn.pressed.connect(self.delete_part)
  92. self.save_btn = QPushButton("Save")
  93. self.save_btn.pressed.connect(self.save)
  94. op_buttons_layout.addWidget(self.new_part_btn)
  95. op_buttons_layout.addWidget(self.delete_part_btn)
  96. op_buttons_layout.addWidget(self.save_btn)
  97. op_widget.setLayout(op_buttons_layout)
  98. layout.addWidget(op_widget)
  99. self.delete_part_btn.setEnabled(False)
  100. self.setLayout(layout)
  101. def save(self):
  102. self.current_disk.commit()
  103. def change_partition_format(self, selected_part):
  104. if selected_part.data(Qt.UserRole) != "unallocated":
  105. for part in self.current_disk.partitions:
  106. if part.number == selected_part.data(Qt.UserRole):
  107. print(part.path)
  108. def get_part_info(self, part, size_unit):
  109. part_info = {}
  110. part_info["path"] = part.path
  111. if size_unit == "GB":
  112. part_info["size"] = format(part.getSize(unit=size_unit), '.2f')
  113. else:
  114. part_info["size"] = part.getSize(unit=size_unit)
  115. part_info["file_system_type"] = "Unknown"
  116. if part.fileSystem:
  117. if part.fileSystem.type.startswith('linux-swap'):
  118. part_info["file_system_type"] = "swap"
  119. else:
  120. part_info["file_system_type"] = part.fileSystem.type
  121. try:
  122. part_info["flags"] = part.getFlagsAsString()
  123. except:
  124. pass
  125. part_info["order"] = part.number
  126. part_info["type"] = part.type
  127. return part_info
  128. def refresh_device_list(self):
  129. self.device_selection_combobox.clear()
  130. self.device_list = parted.getAllDevices()
  131. for disk in self.device_list:
  132. try:
  133. if parted.Disk(disk).type == "msdos":
  134. self.device_selection_combobox.addItem(
  135. "{} {} GB ({})".format(disk.model, format(disk.getSize(unit="GB"), '.2f'), disk.path),
  136. userData=disk.path)
  137. except parted.DiskLabelException:
  138. disk = parted.freshDisk(disk, 'msdos')
  139. # For discarding CD-ROM like devices
  140. try:
  141. disk.commit()
  142. except parted.IOException:
  143. pass
  144. else:
  145. disk = disk.device
  146. self.device_selection_combobox.addItem(
  147. "{} {} GB ({})".format(disk.model, format(disk.getSize(unit="GB"), '.2f'), disk.path),
  148. userData=disk.path)
  149. def selected_device_changed(self):
  150. if self.device_selection_combobox.currentData():
  151. self.current_device = parted.getDevice(self.device_selection_combobox.currentData())
  152. self.current_disk = parted.Disk(self.current_device)
  153. self.refresh_partition_list()
  154. def refresh_partition_list(self):
  155. self.partition_list_widget.clear()
  156. for part in self.current_disk.partitions:
  157. part_info = self.get_part_info(part, "GB")
  158. item = QListWidgetItem(
  159. "{}\t\t{} GB\t{}\t{}".format(part_info["path"], part_info["size"], part_info["file_system_type"],
  160. part_info["flags"]))
  161. item.setData(Qt.UserRole, part_info["order"])
  162. if part_info["type"] == parted.PARTITION_NORMAL:
  163. item.setIcon(QIcon("images/primary.xpm"))
  164. elif part_info["type"] == parted.PARTITION_EXTENDED:
  165. item.setIcon(QIcon("images/extended.xpm"))
  166. elif part_info["type"] == parted.PARTITION_LOGICAL:
  167. item.setIcon(QIcon("images/logical.xpm"))
  168. self.partition_list_widget.addItem(item)
  169. for free_part in self.current_disk.getFreeSpacePartitions():
  170. total_size = 0
  171. part_info = self.get_part_info(free_part, "GB")
  172. if float(part_info["size"]) > 0:
  173. if part_info["type"] == 5:
  174. extended_unallocated_item = QListWidgetItem("{}\t{} GB".format("Unallocated", part_info["size"]))
  175. extended_unallocated_item.setIcon(QIcon("images/blank.xpm"))
  176. extended_unallocated_item.setData(Qt.UserRole, "unallocated")
  177. self.partition_list_widget.addItem(extended_unallocated_item)
  178. if part_info["type"] == parted.PARTITION_FREESPACE:
  179. total_size = total_size + float(part_info["size"])
  180. unallocated_item = QListWidgetItem("{}\t{} GB".format("Unallocated", total_size))
  181. unallocated_item.setIcon(QIcon("images/blank.xpm"))
  182. unallocated_item.setData(Qt.UserRole, "unallocated")
  183. self.partition_list_widget.addItem(unallocated_item)
  184. def on_part_select(self, selected_part):
  185. if selected_part.data(Qt.UserRole) != "unallocated":
  186. self.delete_part_btn.setEnabled(True)
  187. else:
  188. self.delete_part_btn.setEnabled(False)
  189. def delete_part(self):
  190. part_order = self.partition_list_widget.currentItem().data(Qt.UserRole)
  191. for part in self.current_disk.partitions:
  192. if part.number == part_order:
  193. try:
  194. self.current_disk.deletePartition(part)
  195. self.refresh_partition_list()
  196. except parted.PartitionException:
  197. QMessageBox.warning(self, "Warning",
  198. "Extended partitions cannot be deleted before logical partitions.")
  199. self.partition_list_widget.setCurrentRow(self.partition_list_widget.count() - 2)
  200. def add_new_part(self):
  201. if self.get_max_unallocated_region():
  202. region = self.get_max_unallocated_region()
  203. number_of_primary_parts = len(self.current_disk.getPrimaryPartitions())
  204. number_of_extended_parts = ext_count = 1 if self.current_disk.getExtendedPartition() else 0
  205. parts_avail = self.current_disk.maxPrimaryPartitionCount - (
  206. number_of_primary_parts + number_of_extended_parts)
  207. if not parts_avail and not ext_count:
  208. QMessageBox.warning(self,
  209. "Warning",
  210. """You cannot create more than 4 primary partition.
  211. If you want more partitions,
  212. delete one of primary partition and create extended one. """
  213. )
  214. else:
  215. if parts_avail:
  216. if not number_of_extended_parts and parts_avail > 1:
  217. self.create_part(region, parted.PARTITION_NORMAL)
  218. self.refresh_partition_list()
  219. elif parts_avail == 1:
  220. self.create_part(region, parted.PARTITION_EXTENDED)
  221. self.refresh_partition_list()
  222. if number_of_extended_parts:
  223. ext_part = self.current_disk.getExtendedPartition()
  224. try:
  225. region = ext_part.geometry.intersect(region)
  226. except ArithmeticError:
  227. QMessageBox.critical(self, "Error",
  228. "There is no free space for new partition!"
  229. " Try increase size for extended partitions.")
  230. else:
  231. self.create_part(region, parted.PARTITION_LOGICAL)
  232. self.refresh_partition_list()
  233. else:
  234. QMessageBox.critical(self, "There is no free space for new partition!")
  235. def get_max_unallocated_region(self):
  236. max_size = -1
  237. region = None
  238. alignment = self.current_device.optimumAlignment
  239. for _region in self.current_disk.getFreeSpaceRegions():
  240. if _region.length > max_size and _region.length > alignment.grainSize:
  241. region = _region
  242. max_size = _region.length
  243. return region
  244. def create_part(self, region, part_type):
  245. if part_type == parted.PARTITION_NORMAL or part_type == parted.PARTITION_EXTENDED:
  246. for free_part in self.current_disk.getFreeSpacePartitions():
  247. part_info = self.get_part_info(free_part, "GB")
  248. if part_info["type"] == parted.PARTITION_FREESPACE:
  249. max_size = float(part_info["size"])
  250. elif part_type == part_type == parted.PARTITION_LOGICAL:
  251. for free_part in self.current_disk.getFreeSpacePartitions():
  252. part_info = self.get_part_info(free_part, "GB")
  253. if part_info["type"] == 5:
  254. max_size = float(part_info["size"])
  255. alignment = self.current_device.optimalAlignedConstraint
  256. constraint = self.current_device.getConstraint()
  257. data = {
  258. 'start': constraint.startAlign.alignUp(region, region.start),
  259. 'end': constraint.endAlign.alignDown(region, region.end),
  260. }
  261. selected_size, ok = QInputDialog().getDouble(self, 'Create partition', 'Size in GB:', min=0.001, value=1,
  262. max=max_size, decimals=3)
  263. if ok:
  264. data["end"] = int(data["start"]) + int(
  265. parted.sizeToSectors(float(selected_size), "GiB", self.current_device.sectorSize))
  266. try:
  267. geometry = parted.Geometry(device=self.current_device, start=int(data["start"]), end=int(data["end"]))
  268. partition = parted.Partition(
  269. disk=self.current_disk,
  270. type=part_type,
  271. geometry=geometry,
  272. )
  273. self.current_disk.addPartition(partition=partition, constraint=constraint)
  274. except (parted.PartitionException, parted.GeometryException, parted.CreateException) as e:
  275. # GeometryException raised when incorrect start/end values applied (e.g. start < end),
  276. # CreateException is raised when the partition doesn't fit on the disk, etc.
  277. # PartedException is raised on generic errors (e.g. start/end values out of range)
  278. raise RuntimeError(e.message)
  279. if __name__ == "__main__":
  280. main()