def test_sticky(self): s = struct.Set() # create sticky contianer on two stores with s.create("foo", [self.store1, self.store2]) as dummy: # create document on first store with Connector().create("test.format.foo", "test.ignore", [self.store1]) as w: w.commit() doc = w.getDoc() rev = w.getRev() watch1 = self.watchDoc(doc, connector.Watch.EVENT_REPLICATED) watch2 = self.watchRev(rev, connector.Watch.EVENT_REPLICATED) # add to sticky container s['dummy'] = struct.DocLink(doc) s.save() # wait for sticky replicatin to happen self.assertTrue(watch1.waitForWatch()) self.assertTrue(watch2.waitForWatch()) # check doc (with rev) to exist on all stores l = Connector().lookup_doc(doc) self.assertEqual(l.revs(), [rev]) self.assertEqual(len(l.stores(rev)), 2) self.assertTrue(self.store1 in l.stores(rev)) self.assertTrue(self.store2 in l.stores(rev)) l = Connector().lookup_rev(rev) self.assertEqual(len(l), 2) self.assertTrue(self.store1 in l) self.assertTrue(self.store2 in l)
def test_sync_merge(self): (doc, rev1, rev2) = self.createMerge("org.hotchpotch.dict", {'HPSD': struct.dumps({"a": 1})}, {'HPSD': struct.dumps({ "a": 1, "b": 2 })}, {'HPSD': struct.dumps({ "a": 1, "c": 3 })}) l = self.performSync(doc, 'merge') rev = l.revs()[0] s = Connector().stat(rev) self.assertEqual(len(s.parents()), 2) self.assertTrue(rev1 in s.parents()) self.assertTrue(rev2 in s.parents()) # all revs on all stores? l = Connector().lookup_rev(rev1) self.assertTrue(self.store1 in l) self.assertTrue(self.store2 in l) l = Connector().lookup_rev(rev2) self.assertTrue(self.store1 in l) self.assertTrue(self.store2 in l) # see if merge was ok with Connector().peek(rev) as r: hpsd = struct.loads(r.readAll('HPSD')) self.assertEqual(hpsd, {"a": 1, "b": 2, "c": 3})
def tearDown(self): CommonParts.tearDown(self) # make sure stores are unmounted to kill sync if Connector().enum().isMounted(STORE1): Connector().unmount(STORE1) if Connector().enum().isMounted(STORE2): Connector().unmount(STORE2)
def setUp(self): # make sure stores are unmounted to kill sync if Connector().enum().isMounted(STORE1): Connector().unmount(STORE1) if Connector().enum().isMounted(STORE2): Connector().unmount(STORE2) CommonParts.setUp(self)
def assertRevContent(self, rev, content): with Connector().peek(rev) as r: for (part, data) in content.items(): revData = r.readAll(part) self.assertEqual(revData, data) s = Connector().stat(rev) for part in s.parts(): self.assertTrue(part in content)
def __doCreate(self, sourceRev): info = Connector().stat(sourceRev) destStores = Connector().lookup_rev(self.rev()) with Connector().create(info.type(), info.creator(), destStores) as w: with Connector().peek(sourceRev) as r: for part in info.parts(): w.write(part, r.readAll(part)) w.commit() destDoc = w.getDoc() # add link self.model().insertLink(struct.DocLink(destDoc)) # save immediately self.save()
def __addCreateActions(self, menu): newMenu = menu.addMenu(QtGui.QIcon("icons/filenew.png"), "New document") sysStore = struct.Container( struct.DocLink(Connector().enum().sysStore())) templatesDict = struct.Container(sysStore.get("templates:")) items = templatesDict.items() items.sort(key=lambda item: item[0]) for (name, link) in items: rev = link.rev() icon = QtGui.QIcon(Registry().getIcon( Connector().stat(rev).type())) action = newMenu.addAction(icon, name) action.triggered.connect(lambda x, r=rev: self.__doCreate(r))
def __init__(self, uuid, isDoc, parent=None): super(PropertiesDialog, self).__init__(parent) mainLayout = QtGui.QVBoxLayout() mainLayout.setSizeConstraint(QtGui.QLayout.SetFixedSize) if isDoc: self.doc = uuid info = Connector().lookup_doc(uuid) mainLayout.addWidget(DocumentTab(info.stores(), "document")) self.revs = info.revs() else: self.doc = None mainLayout.addWidget( DocumentTab(Connector().lookup_rev(uuid), "revision")) self.revs = [uuid] if len(self.revs) == 0: QtGui.QMessageBox.warning( self, 'Missing document', 'The requested document was not found on any store.') sys.exit(1) tabWidget = QtGui.QTabWidget() self.annoTab = AnnotationTab(self.revs, isDoc and (len(self.revs) == 1)) QtCore.QObject.connect(self.annoTab, QtCore.SIGNAL("changed()"), self.__changed) tabWidget.addTab(self.annoTab, "Annotation") tabWidget.addTab(HistoryTab(self.revs), "History") if isDoc: tabWidget.addTab(RevisionTab(self.revs), "Revisions") else: tabWidget.addTab(RevisionTab(self.revs), "Revision") mainLayout.addWidget(tabWidget) if isDoc and (len(self.revs) == 1): self.buttonBox = QtGui.QDialogButtonBox( QtGui.QDialogButtonBox.Save | QtGui.QDialogButtonBox.Close) self.buttonBox.button( QtGui.QDialogButtonBox.Save).setEnabled(False) self.buttonBox.accepted.connect(self.__save) self.buttonBox.rejected.connect(self.reject) else: self.buttonBox = QtGui.QDialogButtonBox(QtGui.QDialogButtonBox.Ok) self.buttonBox.accepted.connect(self.accept) mainLayout.addWidget(self.buttonBox) self.setLayout(mainLayout) self.setWindowTitle("Properties of %s" % (self.annoTab.getTitle()))
def test_suspend_multi(self): c = Connector() (doc, rev1, rev_s1) = self.createSuspendDoc() with c.update(doc, rev1) as w: w.writeAll('FILE', 'forward') w.commit() rev2 = w.getRev() with c.update(doc, rev2) as w: w.writeAll('FILE', 'Hail to the king, baby!') w.suspend() rev_s2 = w.getRev() l = c.lookup_doc(doc) self.assertEqual(l.revs(), [rev2]) self.assertEqual(len(l.preRevs()), 2) self.assertTrue(rev_s1 in l.preRevs()) self.assertTrue(rev_s2 in l.preRevs()) s = c.stat(rev_s1) self.assertEqual(s.parents(), [rev1]) s = c.stat(rev_s2) self.assertEqual(s.parents(), [rev2]) self.assertRevContent(rev1, {'FILE': 'ok'}) self.assertRevContent(rev_s1, {'FILE': 'update'}) self.assertRevContent(rev2, {'FILE': 'forward'}) self.assertRevContent(rev_s2, {'FILE': 'Hail to the king, baby!'})
def test_merge(self): c = Connector() stores = [self.store1, self.store2] w = self.create("public.data", "test.ignore", stores) w.commit() doc = w.getDoc() rev = w.getRev() with c.update(doc, rev, stores=[self.store1]) as w: w.write('FILE', 'first') w.commit() rev1 = w.getRev() with c.update(doc, rev, stores=[self.store2]) as w: w.write('FILE', 'second') w.commit() rev2 = w.getRev() with c.update(doc, rev1, stores=stores) as w: w.setParents([rev1, rev2]) w.commit() rev3 = w.getRev() self.assertTrue(c.sync(doc) == rev3) l = c.lookup_doc(doc) self.assertEqual(l.revs(), [rev3]) self.assertEqual(set(c.lookup_rev(rev)), set(stores)) self.assertEqual(set(c.lookup_rev(rev1)), set(stores)) self.assertEqual(set(c.lookup_rev(rev2)), set(stores)) self.assertEqual(set(c.lookup_rev(rev3)), set(stores))
def test_bad(self): c = Connector() stores = [self.store1, self.store2] w = self.create("public.data", "test.ignore", stores) w.commit() doc = w.getDoc() rev = w.getRev() with c.update(doc, rev, stores=[self.store1]) as w: w.write('FILE', 'first') w.commit() rev1 = w.getRev() with c.update(doc, rev, stores=[self.store2]) as w: w.write('FILE', 'second') w.commit() rev2 = w.getRev() self.failIfEqual(rev, rev1) self.failIfEqual(rev, rev2) self.failIfEqual(rev1, rev2) self.assertRaises(IOError, c.sync, doc) l = c.lookup_doc(doc) self.assertEqual(len(l.revs()), 2) self.assertEqual(l.rev(self.store1), rev1) self.assertEqual(l.rev(self.store2), rev2)
def docRead(self, readWrite, handle): stat = Connector().stat(self.rev()) uti = stat.type() if readWrite: linkMap = None else: linkMap = stat.linkMap() if uti in DictModel.UTIs: model = DictModel(linkMap, self) elif uti in SetModel.UTIs: model = SetModel(linkMap, self) else: raise TypeError('Unhandled type code: %s' % (uti)) self.__setModel(model) if self.__settings: self.__applySettings(self.__settings) model.rowsInserted.connect(self._emitSaveNeeded) model.rowsRemoved.connect(self._emitSaveNeeded) model.dataChanged.connect(self.__dataChanged) model.modelReset.connect(self.__dataChanged) model.modelReset.connect(lambda: self.selectionChanged.emit()) self.listView.selectionModel().selectionChanged.connect( lambda: self.selectionChanged.emit()) autoClean = self.metaDataGetField(CollectionModel.AUTOCLEAN, False) model.doLoad(handle, readWrite, autoClean) if model.hasChanged(): self._emitSaveNeeded()
def __update(self): if self.__seen: self.__unwatch() self.__available = len(Connector().lookup_rev(self.__rev)) > 0 else: self.__available = False try: stat = Connector().stat(self.__rev) self.__mtime = stat.mtime() self.__seen = True self.__available = True self.__view._addParents(stat.parents()) self.__unwatch() except IOError: self.__watch() return
def __addReplicateActions(self, menu, link): c = Connector() try: allVolumes = set(c.lookup_rev(self.rev())) if isinstance(link, struct.DocLink): lookup = c.lookup_doc(link.doc()) curVolumes = set(lookup.stores()) try: for rev in lookup.revs(): curVolumes = curVolumes & set( c.lookup_rev(rev, curVolumes)) except IOError: curVolumes = set() else: curVolumes = set(c.lookup_rev(link.rev())) except IOError: return repVolumes = allVolumes - curVolumes for store in repVolumes: try: rev = c.lookup_doc(store).rev(store) with c.peek(rev) as r: metaData = struct.loads(r.readAll('META')) try: name = metaData["org.hotchpotch.annotation"]["title"] except: name = "Unknown store" action = menu.addAction("Replicate item to '%s'" % name) action.triggered.connect( lambda x, l=link, s=store: self.__doReplicate(l, s)) except: pass
def test_create(self): w = self.create("public.data", "test.foo", [self.store1]) w.commit() doc = w.getDoc() rev = w.getRev() s = Connector().stat(rev) self.assertEqual(s.creator(), "test.foo")
def test_suspend(self): c = Connector() (doc, rev1, rev2) = self.createSuspendDoc() l = c.lookup_doc(doc) self.assertEqual(l.revs(), [rev1]) self.assertEqual(l.preRevs(), [rev2]) self.assertRevContent(rev1, {'FILE': 'ok'}) self.assertRevContent(rev2, {'FILE': 'update'})
def _extractMetaData(self): caption = self._view.metaDataGetField(DocumentView.HPA_TITLE, "Unnamed") if not self._view.doc() and self._view.rev(): try: mtime = Connector().stat(self._view.rev()).mtime() caption = caption + " @ " + str(mtime) except IOError: pass self._main.setCaption(caption)
def __init__(self, revs, parent=None): super(HistoryTab, self).__init__(parent) # TODO: implement something nice gitk like... self.__historyList = [] self.__historyRevs = [] heads = revs[:] while len(heads) > 0: newHeads = [] for rev in heads: try: if rev not in self.__historyRevs: stat = Connector().stat(rev) mtime = str(stat.mtime()) comment = "" if 'META' in stat.parts(): try: with Connector().peek(rev) as r: metaData = struct.loads(r.readAll('META')) comment = extractMetaData( metaData, [ "org.hotchpotch.annotation", "comment" ], "") except IOError: pass self.__historyList.append(mtime + " - " + comment) self.__historyRevs.append(rev) newHeads.extend(stat.parents()) except IOError: pass heads = newHeads self.__historyListBox = QtGui.QListWidget() self.__historyListBox.setSizePolicy(QtGui.QSizePolicy.Ignored, QtGui.QSizePolicy.Ignored) self.__historyListBox.insertItems(0, self.__historyList) QtCore.QObject.connect( self.__historyListBox, QtCore.SIGNAL("itemDoubleClicked(QListWidgetItem *)"), self.__open) layout = QtGui.QVBoxLayout() layout.addWidget(self.__historyListBox) self.setLayout(layout)
def setAutoClean(self, autoClean): self.__autoClean = autoClean if autoClean and self.__mutable: removed = [x for x in self._listing if not x.isValid()] self._listing = [x for x in self._listing if x.isValid()] if len(removed) > 0: self.__changedContent = True for item in removed: Connector().unwatch(item) self.reset()
def test_create(self): c = Connector() w = self.create("test.format", "test.ignore", [self.store1]) self.assertEqual(w.getType(), "test.format") w.commit() doc = w.getDoc() rev = w.getRev() s = c.stat(rev) self.assertEqual(s.type(), "test.format")
def test_already_same(self): c = Connector() stores = [self.store1, self.store2] w = self.create("public.data", "test.ignore", stores) w.commit() doc = w.getDoc() rev = w.getRev() self.assertTrue(c.sync(doc) == rev) self.assertTrue(c.sync(doc, stores=stores) == rev)
def test_resume_wrong(self): c = Connector() (doc, rev1, rev2) = self.createSuspendDoc() self.assertRaises(IOError, c.resume, doc, rev1) l = c.lookup_doc(doc) self.assertEqual(l.revs(), [rev1]) self.assertEqual(l.preRevs(), [rev2]) self.assertRevContent(rev1, {'FILE': 'ok'}) self.assertRevContent(rev2, {'FILE': 'update'})
def __save(self): rev = self.revs[0] self.buttonBox.button(QtGui.QDialogButtonBox.Save).setEnabled(False) with Connector().peek(rev) as r: metaData = struct.loads(r.readAll('META')) setMetaData(metaData, ["org.hotchpotch.annotation", "title"], str(self.annoTab.titleEdit.text())) setMetaData(metaData, ["org.hotchpotch.annotation", "description"], str(self.annoTab.descEdit.text())) if self.annoTab.tagsEdit.hasAcceptableInput(): tagString = self.annoTab.tagsEdit.text() tagSet = set([tag.strip() for tag in str(tagString).split(',')]) tagList = list(tagSet) setMetaData(metaData, ["org.hotchpotch.annotation", "tags"], tagList) with Connector().update(self.doc, rev) as writer: writer.writeAll('META', struct.dumps(metaData)) writer.commit() self.revs[0] = writer.getRev()
def __dropFile(self, data, onto): # FIXME: find a better way than calling back to the parent stores = Connector().lookup_rev(self.__parent.rev()) urlList = data.urls() if onto.isValid(): if len(urlList) != 1: choice = QtGui.QMessageBox.question( self.__parent, "Overwrite", "Cannot overwrite: more than one dragged file. Import instead?", QtGui.QMessageBox.Yes | QtGui.QMessageBox.No, QtGui.QMessageBox.No) if choice != QtGui.QMessageBox.Yes: return False elif not os.path.isfile(str(urlList[0].toLocalFile().toUtf8())): choice = QtGui.QMessageBox.question( self.__parent, "Overwrite", "Cannot overwrite: dragged item is not a file. Import instead?", QtGui.QMessageBox.Yes | QtGui.QMessageBox.No, QtGui.QMessageBox.No) if choice != QtGui.QMessageBox.Yes: return False else: choice = QtGui.QMessageBox.question( self.__parent, "Overwrite", "Do you want to overwrite the selected item? If not, the file will be imported as new item.", QtGui.QMessageBox.Yes | QtGui.QMessageBox.No | QtGui.QMessageBox.Cancel, QtGui.QMessageBox.No) if choice == QtGui.QMessageBox.Cancel: return False elif choice == QtGui.QMessageBox.Yes: link = self.getItemLinkUser(onto) path = str(urlList[0].toLocalFile().toUtf8()) try: return importer.overwriteFile(stores, link, path) except IOError: pass except OSError: pass return False # import and add to container for url in urlList: try: path = str(url.toLocalFile().toUtf8()) handle = importer.importFile(stores, path) if handle: try: self.insertLink(struct.DocLink(handle.getDoc())) self.__parent.save() finally: handle.close() except IOError: pass return True
def removeRows(self, position, rows, parent): if not self.__mutable: return False self.__changedContent = True self.beginRemoveRows(QtCore.QModelIndex(), position, position + rows - 1) for i in range(rows): Connector().unwatch(self._listing[position]) del self._listing[position] self.endRemoveRows() return True
def test_readback(self): dataOrig = 'akjdfaqhfkjsalur\naqidahgajsoasoiga\n\nakhsfdlkaf\r\n' w = self.create("public.data", "test.foo", [self.store1]) w.writeAll('FILE', dataOrig) w.commit() doc = w.getDoc() rev = w.getRev() with Connector().peek(rev) as r: dataRead = r.readAll('FILE') self.assertEqual(dataOrig, dataRead)
def test_sync_merge_fallback(self): (doc, rev1, rev2) = self.createMerge("public.data", {}, {'FILE': "left"}, {'FILE': "right"}) l = self.performSync(doc, 'merge') rev = l.revs()[0] s = Connector().stat(rev) self.assertEqual(len(s.parents()), 2) self.assertTrue(rev1 in s.parents()) self.assertTrue(rev2 in s.parents()) self.assertRevContent(rev, {'FILE': 'left'})
def createMerge(self, type, base, left, right): w = self.create(type, "test.foo", [self.store1, self.store2]) for (part, data) in base.items(): w.writeAll(part, data) w.commit() doc = w.getDoc() rev1 = w.getRev() with Connector().update(doc, rev1, stores=[self.store1]) as w: for (part, data) in left.items(): w.writeAll(part, data) w.commit() rev2 = w.getRev() with Connector().update(doc, rev1, stores=[self.store2]) as w: for (part, data) in right.items(): w.writeAll(part, data) w.commit() rev3 = w.getRev() return (doc, rev2, rev3)
def createFastForward(self): w = self.create("public.data", "test.foo", [self.store1, self.store2]) w.commit() doc = w.getDoc() rev1 = w.getRev() with Connector().update(doc, rev1, stores=[self.store1]) as w: w.writeAll('FILE', 'forward') w.commit() rev2 = w.getRev() return (doc, rev2)
def test_transitive_keep(self): with Connector().create("test.format.foo", "test.foo", [self.store1]) as w1: with Connector().create("test.format.bar", "test.foo", [self.store1]) as w2: w2.write('FILE', 'test') w2.commit() doc2 = w2.getDoc() rev2 = w2.getRev() # create a reference from w1 to w2 w1.write('HPSD', struct.dumps([struct.DocLink(doc2)])) w1.commit() doc1 = w1.getDoc() rev1 = w1.getRev() # w2 is closed now, w1 still open, should prevent gc self.gc(self.store1) l = Connector().lookup_doc(doc1) self.assertEqual(l.revs(), [rev1]) self.assertEqual(l.preRevs(), []) self.assertEqual(Connector().lookup_rev(rev1), [self.store1]) l = Connector().lookup_doc(doc2) self.assertEqual(l.revs(), [rev2]) self.assertEqual(l.preRevs(), []) self.assertEqual(Connector().lookup_rev(rev2), [self.store1])