7 this file is part of the project scolasync
9 Copyright (C) 2010-2014 Georges Khaznadar <georgesk@ofset.org>
11 This program is free software: you can redistribute it and/or modify
12 it under the terms of the GNU General Public License as published by
13 the Free Software Foundation, either version3 of the License, or
14 (at your option) any later version.
16 This program is distributed in the hope that it will be useful,
17 but WITHOUT ANY WARRANTY; without even the implied warranty of
18 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 GNU General Public License for more details.
21 You should have received a copy of the GNU General Public License
22 along with this program. If not, see <http://www.gnu.org/licenses/>.
29 import ownedUsbDisk, help, copyToDialog1, chooseInSticks, usbThread
30 import diskFull, preferences, checkBoxDialog
31 import os.path, operator, subprocess, dbus, re, time, copy
32 from notification
import Notification
33 from usbDisk2
import safePath
37 from globaldef
import logFileName, _dir
54 global pastCommands, lastCommand
55 if cmd
in pastCommands:
56 pastCommands[cmd].append(partition.owner)
58 pastCommands[cmd]=[partition.owner]
66 checkAllSignal=pyqtSignal()
67 checkToggleSignal=pyqtSignal()
68 checkNoneSignal=pyqtSignal()
69 shouldNameDrive=pyqtSignal()
70 pushCmdSignal=pyqtSignal(str, str)
71 popCmdSignal=pyqtSignal(str, str)
79 QMainWindow.__init__(self)
80 QWidget.__init__(self, parent)
82 from Ui_mainWindow
import Ui_MainWindow
83 self.
ui = Ui_MainWindow()
85 QIcon.setThemeName(
"Tango")
88 self.
movefromIcon=QIcon.fromTheme(
"movefrom",QIcon(
"/usr/share/scolasync/images/movefrom.png"))
100 self.
namesFullTip=QApplication.translate(
"MainWindow",
"<br />Des noms sont disponibles pour renommer les prochains baladeurs que vous brancherez",
None)
101 self.
namesEmptyTip=QApplication.translate(
"MainWindow",
"<br />Cliquez sur ce bouton pour préparer une liste de noms afin de renommer les prochains baladeurs que vous brancherez",
None)
109 self.
proxy.setSourceModel(self.
t.model())
115 self.
ui.helpButton.clicked.connect(self.
help)
116 self.
ui.umountButton.clicked.connect(self.
umount)
117 self.
ui.toButton.clicked.connect(self.
copyTo)
118 self.
ui.fromButton.clicked.connect(self.
copyFrom)
119 self.
ui.delButton.clicked.connect(self.
delFiles)
120 self.
ui.redoButton.clicked.connect(self.
redoCmd)
121 self.
ui.namesButton.clicked.connect(self.
namesCmd)
122 self.
ui.preferenceButton.clicked.connect(self.
preference)
129 qApp.available.addHook(
'object-added', self.
cbAdded())
130 qApp.available.addHook(
'object-removed', self.
cbRemoved())
146 icon.addPixmap(QIcon.fromTheme(name).pixmap(32))
148 icon.addPixmap(
"images/icons32/"+name+
".png")
158 global activeThreads, pastCommands, lastCommand
159 if owner
in activeThreads:
160 activeThreads[owner].append(cmd)
162 activeThreads[owner]=[cmd]
163 self.
tm.updateOwnerColumn()
172 global activeThreads, pastCommands, lastCommand
173 if owner
in activeThreads:
174 cmd0=activeThreads[owner].pop()
176 msg=cmd.replace(cmd0,
"")+
"\n"
177 logFile=open(os.path.expanduser(logFileName),
"a")
181 raise Exception((
"mismatched commands\n%s\n%s" %(cmd,cmd0)))
182 if len(activeThreads[owner])==0:
183 activeThreads.pop(owner)
185 raise Exception(
"End of command without a begin.")
186 self.
tm.updateOwnerColumn()
187 if len(activeThreads)==0 :
197 index0=model.createIndex(0,0)
198 index1=model.createIndex(len(model.donnees)-1,0)
199 srange=QItemSelectionRange(index0,index1)
200 for i
in srange.indexes():
201 checked=bool(i.model().data(i,Qt.DisplayRole))
202 model.setData(i, boolFunc(checked),Qt.EditRole)
231 hint=db.readStudent(disk.serial, disk.uuid, ownedUsbDisk.tattooInDir(disk.mp))
238 driveIdent=(stickId, uuid, tattoo))
248 def _cbAdded(man, obj):
249 if qApp.available.modified:
254 qApp.available.modified=
False
262 def _cbRemoved(man, obj):
263 if qApp.available.modified:
265 if path
in qApp.available.targets:
269 qApp.available.modified=
False
295 self.
iconRedo.addPixmap(QIcon.fromTheme(
"go-jump").pixmap(32), QIcon.Normal, QIcon.Off)
297 self.
iconStop.addPixmap(QIcon.fromTheme(
"stop").pixmap(32), QIcon.Normal, QIcon.Off)
299 self.
redoToolTip=QApplication.translate(
"MainWindow",
"Refaire à nouveau",
None)
300 self.
redoStatusTip=QApplication.translate(
"MainWindow",
"Refaire à nouveau la dernière opération réussie, avec les baladeurs connectés plus récemment",
None)
301 self.
stopToolTip=QApplication.translate(
"MainWindow",
"Arrêter les opérations en cours",
None)
302 self.
stopStatusTip=QApplication.translate(
"MainWindow",
"Essaie d'arrêter les opérations en cours. À faire seulement si celles-ci durent trop longtemps",
None)
313 self.
header=ownedUsbDisk.uDisk2.headers()
328 connectedCount=int(qApp.available)
329 self.
ui.lcdNumber.display(connectedCount)
330 self.
t.resizeColumnsToContents()
348 mappedIdx=self.
proxy.mapFromSource(idx)
358 cmd=
"xdg-open '%s'" %idx.data()
359 subprocess.call(cmd, shell=
True)
360 elif "capacity" in h:
361 mount=idx.model().partition(idx).mountPoint()
362 dev,total,used,remain,pcent,path = self.
diskSizeData(mount)
363 pcent=int(pcent[:-1])
367 QMessageBox.warning(
None,
368 QApplication.translate(
"Dialog",
"Double-clic non pris en compte",
None),
369 QApplication.translate(
"Dialog",
"pas d'action pour l'attribut {a}",
None).format(a=h))
385 if type(rowOrDev)==type(0):
386 path=qApp.available[rowOrDev][self.
header.index(
"1mp")]
390 dfOutput=subprocess.Popen(cmd, shell=
True, stdout=subprocess.PIPE).communicate()[0]
391 dfOutput=str(dfOutput.split(b
"\n")[-2])
392 m = re.match(
"(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+).*", dfOutput).groups()
405 s=db.readStudent(d.stickid, d.uuid, d.tattoo())
408 elif s==
None and defaultDisk==
None :
419 student=
"%s" %self.
tm.data(idx,Qt.DisplayRole).value()
421 ownedUsbDisk.editRecord(self.
diskFromOwner(student), hint=student)
439 self.
ui.namesButton.setIcon(icon)
440 self.
ui.namesButton.setToolTip(msg)
441 self.
ui.namesButton.setStatusTip(msg.replace(
"<br />",
""))
453 global activeThreads, lastCommand
454 active = len(qApp.available)>0
455 for button
in (self.
ui.toButton,
458 self.
ui.umountButton):
459 button.setEnabled(active)
469 if len(activeThreads) > 0:
473 self.
ui.redoButton.setEnabled(
True)
479 self.
ui.redoButton.setEnabled(lastCommand!=
None)
480 l=self.
namesDialog.ui.listWidget.findItems(
"*",Qt.MatchWildcard)
491 pref.setValues(db.readPrefs())
494 if pref.result()==QDialog.Accepted:
495 db.writePrefs(pref.values())
503 titre1=QApplication.translate(
"Dialog",
"Choix de fichiers à supprimer",
None)
504 titre2=QApplication.translate(
"Dialog",
"Choix de fichiers à supprimer (jokers autorisés)",
None)
508 pathList=d.pathList()
509 buttons=QMessageBox.Ok|QMessageBox.Cancel
510 defaultButton=QMessageBox.Cancel
511 reply=QMessageBox.warning(
513 QApplication.translate(
"Dialog",
"Vous allez effacer plusieurs baladeurs",
None),
514 QApplication.translate(
"Dialog",
"Etes-vous certain de vouloir effacer : "+
"\n".join(pathList),
None),
515 buttons, defaultButton)
516 if reply == QMessageBox.Ok:
517 cmd=
"usbThread.threadDeleteInUSB(p,{paths},subdir='Travail', logfile='{log}', parent=self)".format(paths=pathList,log=logFileName)
518 for p
in qApp.available:
519 if not p.selected:
continue
527 msgBox=QMessageBox.warning(
529 QApplication.translate(
"Dialog",
"Aucun fichier sélectionné",
None),
530 QApplication.translate(
"Dialog",
"Veuillez choisir au moins un fichier",
None))
540 cmd=
"usbThread.threadCopyToUSB(p,{selected},subdir='{subdir}', logfile='{logfile}', parent=self)".format(selected=list(d.selectedList()), subdir=self.
workdir, logfile=logFileName)
542 for p
in qApp.available:
543 if not p.selected:
continue
551 msgBox=QMessageBox.warning(
553 QApplication.translate(
"Dialog",
"Aucun fichier sélectionné",
None),
554 QApplication.translate(
"Dialog",
"Veuillez choisir au moins un fichier",
None))
561 titre1=QApplication.translate(
"Dialog",
"Choix de fichiers à copier",
None)
562 titre2=QApplication.translate(
"Dialog",
"Choix de fichiers à copier depuis les baladeurs",
None)
563 okPrompt=QApplication.translate(
"Dialog",
"Choix de la destination ...",
None)
567 msgBox=QMessageBox.warning(
None,
568 QApplication.translate(
"Dialog",
"Aucun fichier sélectionné",
None),
569 QApplication.translate(
"Dialog",
"Veuillez choisir au moins un fichier",
None))
572 pathList=d.pathList()
573 mp=d.selectedDiskMountPoint()
574 initialPath=os.path.expanduser(
"~")
575 destDir = QFileDialog.getExistingDirectory(
577 QApplication.translate(
"Dialog",
"Choisir un répertoire de destination",
None),
579 if destDir
and len(destDir)>0 :
581 cmd=
"""usbThread.threadMoveFromUSB(
582 p,{paths},subdir=self.workdir,
583 rootPath='{mp}', dest='{dest}', logfile='{log}',
584 parent=self)""".format(paths=pathList, mp=mp, dest=destDir, log=logFileName)
586 cmd=
"""usbThread.threadCopyFromUSB(
587 p,{paths},subdir=self.workdir,
588 rootPath='{mp}', dest='{dest}', logfile='{log}',
589 parent=self)""".format(paths=pathList, mp=mp, dest=destDir, log=logFileName)
591 for p
in qApp.available:
592 if not p.selected:
continue
604 buttons=QMessageBox.Ok|QMessageBox.Cancel
605 defaultButton=QMessageBox.Cancel
606 if QMessageBox.question(
608 QApplication.translate(
"Dialog",
"Voir les copies",
None),
609 QApplication.translate(
"Dialog",
"Voulez-vous voir les fichiers copiés ?",
None),
610 buttons, defaultButton)==QMessageBox.Ok:
611 subprocess.call(
"xdg-open '%s'" %destDir,shell=
True)
614 msgBox=QMessageBox.warning(
616 QApplication.translate(
"Dialog",
"Destination manquante",
None),
617 QApplication.translate(
"Dialog",
"Veuillez choisir une destination pour la copie des fichiers",
None))
625 global lastCommand, pastCommands, activeThreads
626 if len(activeThreads)>0:
630 thread._Thread__stop()
631 print (str(thread.getName()) +
' is terminated')
633 print (str(thread.getName()) +
' could not be terminated')
635 if lastCommand==
None:
637 if QMessageBox.question(
639 QApplication.translate(
"Dialog",
"Réitérer la dernière commande",
None),
640 QApplication.translate(
"Dialog",
"La dernière commande était<br>{cmd}<br>Voulez-vous la relancer avec les nouveaux baladeurs ?",
None).format(cmd=lastCommand))==QMessageBox.Cancel:
642 for p
in qApp.available:
643 if p.owner
in pastCommands[lastCommand] :
continue
644 exec(compile(lastCommand,
'<string>',
'exec'))
648 pastCommands[lastCommand].append(p.owner)
669 buttons=QMessageBox.Ok|QMessageBox.Cancel
670 defaultButton=QMessageBox.Cancel
671 button=QMessageBox.question (
673 QApplication.translate(
"Main",
"Démontage des baladeurs",
None),
674 QApplication.translate(
"Main",
"Êtes-vous sûr de vouloir démonter tous les baladeurs cochés de la liste ?",
None),
675 buttons,defaultButton)
676 if button!=QMessageBox.Ok:
678 for d
in qApp.available.disks_ud():
679 for partition
in qApp.available.parts_ud(d.path):
681 cmd=
"umount {0}".format(partition.mp)
682 subprocess.call(cmd, shell=
True)
683 cmd=
"udisks --detach {0}".format(d.devStuff)
684 subprocess.call(cmd, shell=
True)
696 if h
in ownedUsbDisk.uDisk2._itemNames:
697 self.
visibleheader.append(self.tr(ownedUsbDisk.uDisk2._itemNames[h]))
701 self.
t.setModel(self.
tm)
705 self.
proxy.setSourceModel(self.
t.model())
712 return len(one.targets) == len(two.targets)
and \
713 set([p.uniqueId()
for p
in one]) == set([p.uniqueId()
for p
in two])
725 def __init__(self, parent=None, header=[], donnees=None):
726 QAbstractTableModel.__init__(self,parent)
736 self.dataChanged.emit(self.index(0,column), self.index(len(self.
donnees)-1, column))
737 self.
pere.t.viewport().update()
752 if index.column()==0:
753 self.
donnees[index.row()].selected=value
756 return QAbstractTableModel.setData(self, index, role)
763 return self.
donnees[index.row()][-1]
766 if not index.isValid():
768 elif role==Qt.ToolTipRole:
770 h=self.
pere.header[c]
772 return QApplication.translate(
"Main",
"Cocher ou décocher cette case en cliquant.<br><b>Double-clic</b> pour agir sur plusieurs baladeurs.",
None)
774 return QApplication.translate(
"Main",
"Propriétaire de la clé USB ou du baladeur ;<br><b>Double-clic</b> pour modifier.",
None)
776 return QApplication.translate(
"Main",
"Point de montage de la clé USB ou du baladeur ;<br><b>Double-clic</b> pour voir les fichiers.",
None)
777 elif "capacity" in h:
778 return QApplication.translate(
"Main",
"Capacité de la clé USB ou du baladeur en kO ;<br><b>Double-clic</b> pour voir la place occupée.",
None)
780 return QApplication.translate(
"Main",
"Fabricant de la clé USB ou du baladeur.",
None)
782 return QApplication.translate(
"Main",
"Modèle de la clé USB ou du baladeur.",
None)
784 return QApplication.translate(
"Main",
"Numéro de série de la clé USB ou du baladeur.",
None)
787 elif role != Qt.DisplayRole:
789 if index.row()<len(self.
donnees):
791 return QVariant(self.
donnees[index.row()][index.column()])
793 print(
"Le bug du retrait de clé non détecté a encore frappé, quand sera-t-il éliminé ?")
794 self.
pere.findAllDisks()
801 if orientation == Qt.Horizontal
and role == Qt.DisplayRole:
802 return QVariant(self.
header[section])
803 elif orientation == Qt.Vertical
and role == Qt.DisplayRole:
804 return QVariant(section+1)
811 def sort(self, Ncol, order=Qt.DescendingOrder):
812 self.layoutAboutToBeChanged.emit()
813 self.
donnees = sorted(self.
donnees, key=operator.itemgetter(Ncol))
814 if order == Qt.DescendingOrder:
816 self.layoutChanged.emit()
824 check_box_style_option=QStyleOptionButton()
825 check_box_rect = QApplication.style().subElementRect(QStyle.SE_CheckBoxIndicator,check_box_style_option)
826 check_box_point=QPoint(view_item_style_options.rect.x() + view_item_style_options.rect.width() / 2 - check_box_rect.width() / 2, view_item_style_options.rect.y() + view_item_style_options.rect.height() / 2 - check_box_rect.height() / 2)
827 return QRect(check_box_point, check_box_rect.size())
831 QStyledItemDelegate.__init__(self,parent)
833 def paint(self, painter, option, index):
834 checked = bool(index.model().data(index, Qt.DisplayRole))
835 check_box_style_option=QStyleOptionButton()
836 check_box_style_option.state |= QStyle.State_Enabled
838 check_box_style_option.state |= QStyle.State_On
840 check_box_style_option.state |= QStyle.State_Off
842 QApplication.style().drawControl(QStyle.CE_CheckBox, check_box_style_option, painter)
845 if ((event.type() == QEvent.MouseButtonRelease)
or (event.type() == QEvent.MouseButtonDblClick)):
846 if (event.button() != Qt.LeftButton
or not CheckBoxRect(option).contains(event.pos())):
848 if (event.type() == QEvent.MouseButtonDblClick):
850 elif (event.type() == QEvent.KeyPress):
851 if event.key() != Qt.Key_Space
and event.key() != Qt.Key_Select:
855 checked = bool(index.model().data(index, Qt.DisplayRole))
856 result = model.setData(index,
not checked, Qt.EditRole)
867 QStyledItemDelegate.__init__(self,parent)
868 self.
okPixmap=QPixmap(
"/usr/share/icons/Tango/16x16/status/weather-clear.png")
869 self.
busyPixmap=QPixmap(
"/usr/share/icons/Tango/16x16/actions/view-refresh.png")
871 def paint(self, painter, option, index):
873 text = index.model().data(index, Qt.DisplayRole).value()
874 rect0=QRect(option.rect)
875 rect1=QRect(option.rect)
878 rect0.setSize(QSize(h,h))
880 rect1.setSize(QSize(w-h,h))
881 QApplication.style().drawItemText (painter, rect1, Qt.AlignLeft+Qt.AlignVCenter, option.palette,
True, text)
882 QApplication.style().drawItemText (painter, rect0, Qt.AlignCenter, option.palette,
True,
"O")
883 if text
in activeThreads:
884 QApplication.style().drawItemPixmap (painter, rect0, Qt.AlignCenter, self.
busyPixmap)
886 QApplication.style().drawItemPixmap (painter, rect0, Qt.AlignCenter, self.
okPixmap)
895 QStyledItemDelegate.__init__(self,parent)
898 def paint(self, painter, option, index):
899 v=index.model().data(index, Qt.DisplayRole)
902 rect0=QRect(option.rect)
903 rect1=QRect(option.rect)
904 rect0.translate(2,(rect0.height()-16)/2)
905 rect0.setSize(QSize(16,16))
906 rect1.translate(20,0)
907 rect1.setWidth(rect1.width()-20)
908 QApplication.style().drawItemText (painter, rect1, Qt.AlignLeft+Qt.AlignVCenter, option.palette,
True, text)
910 mount=index.model().partition(index).mountPoint()
911 dev,total,used,remain,pcent,path = self.parent().diskSizeData(mount)
912 pcent=int(pcent[:-1])
913 painter.setBrush(QBrush(QColor(
"slateblue")))
914 painter.drawPie(rect0,0,16*360*pcent/100)
920 suffixes=[
"B",
"KB",
"MB",
"GB",
"TB"]
923 while val > 1024
and i < len(suffixes):
926 return "%4.1f %s" %(val, suffixes[i])