diff --git a/source/mDriveOOo/Options.xcs b/source/mDriveOOo/Options.xcs index 9d11ca75..ed421f2e 100644 --- a/source/mDriveOOo/Options.xcs +++ b/source/mDriveOOo/Options.xcs @@ -37,6 +37,7 @@ + diff --git a/source/mDriveOOo/Options.xcu b/source/mDriveOOo/Options.xcu index 9f45cee1..090d7d7e 100644 --- a/source/mDriveOOo/Options.xcu +++ b/source/mDriveOOo/Options.xcu @@ -29,6 +29,9 @@ xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:oor="http://openoffice.org/2001/registry" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> + + false + true @@ -40,7 +43,7 @@ Documents partagΓ©s - 600 + 60 CLIENT_IS_MASTER diff --git a/source/mDriveOOo/dialogs/OptionsDialog_en_US.properties b/source/mDriveOOo/dialogs/OptionsDialog_en_US.properties index 8273a614..d9a7a7a3 100644 --- a/source/mDriveOOo/dialogs/OptionsDialog_en_US.properties +++ b/source/mDriveOOo/dialogs/OptionsDialog_en_US.properties @@ -1,27 +1,29 @@ OptionsDialog.HelpText= OptionsDialog.Title= OptionsDialog.FixedLine1.HelpText= -OptionsDialog.FixedLine1.Label=OneDriveOOo settings +OptionsDialog.FixedLine1.Label=mDriveOOo settings OptionsDialog.Label1.HelpText= OptionsDialog.Label1.Label=Datasource: OptionsDialog.CommandButton1.HelpText= OptionsDialog.CommandButton1.Label=View DataBase OptionsDialog.Label2.HelpText= OptionsDialog.Label2.Label=Replication: -OptionsDialog.OptionButton1.HelpText= +OptionsDialog.OptionButton1.HelpText=Replication of your Microsoft oneDrive files then your local files OptionsDialog.OptionButton1.Label=Remote then local -OptionsDialog.OptionButton2.HelpText= +OptionsDialog.OptionButton2.HelpText=Replication of your local files then your Microsoft oneDrive files OptionsDialog.OptionButton2.Label=Local then remote -OptionsDialog.OptionButton3.HelpText= +OptionsDialog.OptionButton3.HelpText=Replication is suspended OptionsDialog.OptionButton3.Label=None +OptionsDialog.CheckBox1.HelpText=Reset sync for all users on next replication +OptionsDialog.CheckBox1.Label=Reset OptionsDialog.Label3.HelpText= OptionsDialog.Label3.Label=Interval (minutes): OptionsDialog.NumericField1.HelpText=Replication interval in minutes OptionsDialog.Label4.HelpText= OptionsDialog.Label4.Label=Transfer: -OptionsDialog.OptionButton4.HelpText= +OptionsDialog.OptionButton4.HelpText=Download options OptionsDialog.OptionButton4.Label=Download -OptionsDialog.OptionButton5.HelpText= +OptionsDialog.OptionButton5.HelpText=Upload options OptionsDialog.OptionButton5.Label=Upload OptionsDialog.Label5.HelpText= OptionsDialog.Label5.Label=Chunk: @@ -43,8 +45,8 @@ OptionsDialog.Label7.HelpText= OptionsDialog.Label7.Label=Retry: OptionsDialog.NumericField6.HelpText=Number of download retry in case of failure OptionsDialog.NumericField7.HelpText=Number of upload retry in case of failure -OptionsDialog.CheckBox1.HelpText= -OptionsDialog.CheckBox1.Label=Handle shared drives in folder: +OptionsDialog.CheckBox2.HelpText= +OptionsDialog.CheckBox2.Label=Handle shared drives in folder: OptionsDialog.TextField1.HelpText= OptionsDialog.TextField1.Text= OptionsDialog.Label8.HelpText= diff --git a/source/mDriveOOo/dialogs/OptionsDialog_fr_FR.properties b/source/mDriveOOo/dialogs/OptionsDialog_fr_FR.properties index 7f26f59e..b80ba067 100644 --- a/source/mDriveOOo/dialogs/OptionsDialog_fr_FR.properties +++ b/source/mDriveOOo/dialogs/OptionsDialog_fr_FR.properties @@ -1,27 +1,29 @@ OptionsDialog.HelpText= OptionsDialog.Title= OptionsDialog.FixedLine1.HelpText= -OptionsDialog.FixedLine1.Label=Options OneDriveOOo +OptionsDialog.FixedLine1.Label=Options mDriveOOo OptionsDialog.Label1.HelpText= OptionsDialog.Label1.Label=Donnιes: OptionsDialog.CommandButton1.HelpText= OptionsDialog.CommandButton1.Label=Voir la base de donnιes OptionsDialog.Label2.HelpText= OptionsDialog.Label2.Label=Rιplication: -OptionsDialog.OptionButton1.HelpText= +OptionsDialog.OptionButton1.HelpText=Rιplication de vos fichiers Microsoft oneDrive puis de vos fichiers locaux OptionsDialog.OptionButton1.Label=Distant puis local -OptionsDialog.OptionButton2.HelpText= +OptionsDialog.OptionButton2.HelpText=Rιplication de vos fichiers locaux puis de vos fichiers Microsoft oneDrive OptionsDialog.OptionButton2.Label=Local puis distant -OptionsDialog.OptionButton3.HelpText= +OptionsDialog.OptionButton3.HelpText=La rιplication est suspendue OptionsDialog.OptionButton3.Label=Sans +OptionsDialog.CheckBox1.HelpText=Rιinitialiser la synchronisation pour tous les utilisateurs lors de la prochaine rιplication +OptionsDialog.CheckBox1.Label=Rιinitialiser OptionsDialog.Label3.HelpText= OptionsDialog.Label3.Label=Intervalle (minutes): OptionsDialog.NumericField1.HelpText=Intervalle de rιplication en minutes OptionsDialog.Label4.HelpText= OptionsDialog.Label4.Label=Transfert: -OptionsDialog.OptionButton4.HelpText= +OptionsDialog.OptionButton4.HelpText=Options du tιlιchargement OptionsDialog.OptionButton4.Label=Tιlιchargement -OptionsDialog.OptionButton5.HelpText= +OptionsDialog.OptionButton5.HelpText=Options de la mise ΰ jour OptionsDialog.OptionButton5.Label=Mise ΰ jour OptionsDialog.Label5.HelpText= OptionsDialog.Label5.Label=Tampon: @@ -43,8 +45,8 @@ OptionsDialog.Label7.HelpText= OptionsDialog.Label7.Label=Essai: OptionsDialog.NumericField6.HelpText=Nombre de tentatives de tιlιchargement en cas d'ιchec OptionsDialog.NumericField7.HelpText=Nombre de tentatives de mise ΰ jour en cas d'ιchec -OptionsDialog.CheckBox1.HelpText= -OptionsDialog.CheckBox1.Label=Gιrer les lecteurs partagιs dans le dossier: +OptionsDialog.CheckBox2.HelpText= +OptionsDialog.CheckBox2.Label=Gιrer les lecteurs partagιs dans le dossier: OptionsDialog.TextField1.HelpText= OptionsDialog.TextField1.Text= OptionsDialog.Label8.HelpText= diff --git a/source/mDriveOOo/service/pythonpath/mdrive/listener.py b/source/mDriveOOo/service/pythonpath/mdrive/listener.py new file mode 120000 index 00000000..7716d4ff --- /dev/null +++ b/source/mDriveOOo/service/pythonpath/mdrive/listener.py @@ -0,0 +1 @@ +../../../../../uno/lib/uno/ucb/listener.py \ No newline at end of file diff --git a/source/mDriveOOo/service/pythonpath/mdrive/provider.py b/source/mDriveOOo/service/pythonpath/mdrive/provider.py index 10d90555..3973f227 100644 --- a/source/mDriveOOo/service/pythonpath/mdrive/provider.py +++ b/source/mDriveOOo/service/pythonpath/mdrive/provider.py @@ -30,6 +30,8 @@ import uno import unohelper +from com.sun.star.logging.LogLevel import INFO + from com.sun.star.rest.ParameterType import JSON from com.sun.star.rest.ParameterType import REDIRECT @@ -66,86 +68,50 @@ class Provider(ProviderBase): @property - def Name(self): - return g_provider + def BaseUrl(self): + return g_url @property def Host(self): return g_host @property - def BaseUrl(self): - return g_url + def Name(self): + return g_provider @property def UploadUrl(self): return g_upload + # Must be implemented method + def getDocumentLocation(self, user, item): + url = None + parameter = self.getRequestParameter(user.Request, 'getDocumentLocation', item) + response = user.Request.execute(parameter) + if response.Ok and response.hasHeader('Location'): + url = response.getHeader('Location') + response.close() + return url + def getFirstPullRoots(self, user): return (user.RootId, ) - def initUser(self, database, user, token): - # FIXME: Some APIs like Microsoft oneDrive allow to have the token during the firstPull - #token = self.getUserToken(user) - if database.updateToken(user.Id, token): - user.setToken(token) - def getUser(self, source, request, name): user = self._getUser(source, request, name) root = self._getRoot(source, request, name) return user, root - def initSharedDocuments(self, user, datetime): - itemid = generateUuid() - timestamp = currentUnoDateTime() - user.DataBase.createSharedFolder(user, itemid, self.SharedFolderName, g_ucpfolder, timestamp, datetime) - parameter = self.getRequestParameter(user.Request, 'getSharedFolderContent') - iterator = self._parseSharedFolder(user.Request, parameter, itemid, timestamp) - user.DataBase.pullItems(iterator, user.Id, datetime, 0) - - def _parseSharedFolder(self, request, parameter, parentid, timestamp): - parents = [parentid, ] - trashed = rename = readonly = versionable = False - addchild = True - while parameter.hasNextPage(): - response = request.execute(parameter) - if response.Ok: - events = ijson.sendable_list() - parser = ijson.parse_coro(events) - iterator = response.iterContent(g_chunk, False) - while iterator.hasMoreElements(): - parser.send(iterator.nextElement().value) - for prefix, event, value in events: - if (prefix, event) == ('value.item', 'start_map'): - itemid = name = None - created = modified = timestamp - mimetype = g_ucpfolder - link = '' - size = 0 - elif (prefix, event) == ('value.item.remoteItem.id', 'string'): - itemid = value - elif (prefix, event) == ('value.item.remoteItem.parentReference.driveId', 'string'): - link = value - elif (prefix, event) == ('value.item.remoteItem.name', 'string'): - name = value - elif (prefix, event) == ('value.item.remoteItem.size', 'number'): - size = value - elif (prefix, event) == ('value.item.createdDateTime', 'string'): - created = self.parseDateTime(value) - elif (prefix, event) == ('value.item.lastModifiedDateTime', 'string'): - modified = self.parseDateTime(value) - elif (prefix, event) == ('value.item.remoteItem.file.mimeType', 'string'): - mimetype = value - elif (prefix, event) == ('value.item', 'end_map'): - if itemid and name: - yield itemid, name, created, modified, mimetype, size, link, trashed, addchild, rename, readonly, versionable, parents, None - del events[:] - parser.close() - response.close() + def mergeNewFolder(self, user, oldid, response): + newid = None + items = self._parseNewFolder(response) + if all(items): + newid = user.DataBase.updateNewItemId(user.Id, oldid, *items) + return newid - def parseRootFolder(self, parameter, content): + def parseFolder(self, parameter, content): return self.parseItems(content.User.Request, parameter, content.User.RootId, content.Link) def parseItems(self, request, parameter, rootid, link=''): readonly = versionable = False - addchild = rename = True + addchild = canrename = True + path = None while parameter.hasNextPage(): response = request.execute(parameter) if response.Ok: @@ -166,7 +132,7 @@ def parseItems(self, request, parameter, rootid, link=''): mimetype = g_ucpfolder size = 0 trashed = False - parents = [] + parents = (rootid, ) elif (prefix, event) == ('value.item.id', 'string'): itemid = value elif (prefix, event) == ('value.item.name', 'string'): @@ -182,68 +148,72 @@ def parseItems(self, request, parameter, rootid, link=''): elif (prefix, event) == ('value.item.size', 'number'): size = value elif (prefix, event) == ('value.item.parentReference.id', 'string'): - parents.append(value) + parents = (value, ) elif (prefix, event) == ('value.item', 'end_map'): if itemid and name: - yield itemid, name, created, modified, mimetype, size, link, trashed, addchild, rename, readonly, versionable, parents, None - del events[:] - parser.close() - response.close() - - def parseChanges(self, request, parameter): - while parameter.hasNextPage(): - response = request.execute(parameter) - if response.Ok: - events = ijson.sendable_list() - parser = ijson.parse_coro(events) - iterator = response.iterContent(g_chunk, False) - while iterator.hasMoreElements(): - parser.send(iterator.nextElement().value) - for prefix, event, value in events: - if (prefix, event) == ('@odata.nextLink', 'string'): - parameter.setNextPage('', value, REDIRECT) - elif (prefix, event) == ('@odata.deltaLink', 'string'): - parameter.SyncToken = value - elif (prefix, event) == ('value.item', 'start_map'): - itemid = name = modified = None - trashed = False - elif (prefix, event) == ('value.item.removed', 'boolean'): - trashed = value - elif (prefix, event) == ('value.item.fileId', 'string'): - itemid = value - elif (prefix, event) == ('value.item.time', 'string'): - modified = self.parseDateTime(value) - elif (prefix, event) == ('value.item.file.name', 'string'): - name = value - elif (prefix, event) == ('value.item', 'end_map'): - pass - #yield itemid, trashed, name, modified + yield {'Id': itemid, + 'Name': name, + 'DateCreated': created, + 'DateModified': modified, + 'MediaType': mimetype, + 'Size': size, + 'Link': link, + 'Trashed': trashed, + 'CanAddChild': addchild, + 'CanRename': canrename, + 'IsReadOnly': readonly, + 'IsVersionable': versionable, + 'Parents': parents, + 'Path': path} del events[:] parser.close() response.close() - def getDocumentLocation(self, content): - parameter = self.getRequestParameter(content.User.Request, 'getDocumentLocation', content) - response = content.User.Request.execute(parameter) - print("Provider.getDocumentContent() Status: %s - IsOk: %s - Reason: %s" % (response.StatusCode, response.Ok, response.Reason)) - url = self._parseDocumentLocation(response) - print("Provider.getDocumentContent() Url: %s" % url) - return url - - def _parseDocumentLocation(self, response): - url = None - if response.Ok and response.hasHeader('Location'): - url = response.getHeader('Location') + def parseUploadLocation(self, response): + url = response.getJson().getString('uploadUrl') response.close() return url - def mergeNewFolder(self, user, oldid, response): - newid = None - items = self._parseNewFolder(response) - if all(items): - newid = user.DataBase.updateNewItemId(user.Id, oldid, *items) + def updateItemId(self, user, oldid, response): + newid = response.getJson().getString('id') + response.close() + if oldid != newid: + user.DataBase.updateItemId(user.Id, newid, oldid) + self.updateNewItemId(oldid, newid) return newid + # Can be rewrited method + def initSharedDocuments(self, user, datetime): + itemid = generateUuid() + timestamp = currentUnoDateTime() + user.DataBase.createSharedFolder(user, itemid, self.SharedFolderName, g_ucpfolder, timestamp, datetime) + parameter = self.getRequestParameter(user.Request, 'getSharedFolderContent') + iterator = self._parseSharedFolder(user.Request, parameter, itemid, timestamp) + user.DataBase.pullItems(iterator, user.Id, datetime, 0) + + # Private method + def _getRoot(self, source, request, name): + parameter = self.getRequestParameter(request, 'getRoot') + response = request.execute(parameter) + if not response.Ok: + msg = self._logger.resolveString(571, parameter.Name, response.StatusCode, response.Text) + self._logger.logp(INFO, 'Provider', '_getRoot()', msg) + raise IllegalIdentifierException(msg, source) + root = self._parseRoot(response) + response.close() + return root + + def _getUser(self, source, request, name): + parameter = self.getRequestParameter(request, 'getUser') + response = request.execute(parameter) + if not response.Ok: + msg = self._logger.resolveString(561, parameter.Name, response.StatusCode, response.Text) + self._logger.logp(INFO, 'Provider', '_getUser()', msg) + raise IllegalIdentifierException(msg, source) + user = self._parseUser(response) + response.close() + return user + def _parseNewFolder(self, response): newid = created = modified = None if response.Ok: @@ -264,44 +234,6 @@ def _parseNewFolder(self, response): response.close() return newid, created, modified - def _getUser(self, source, request, name): - parameter = self.getRequestParameter(request, 'getUser') - response = request.execute(parameter) - if not response.Ok: - msg = self._logger.resolveString(403, name) - raise IllegalIdentifierException(msg, source) - user = self._parseUser(response) - response.close() - return user - - def _getRoot(self, source, request, name): - parameter = self.getRequestParameter(request, 'getRoot') - response = request.execute(parameter) - if not response.Ok: - msg = self._logger.resolveString(403, name) - raise IllegalIdentifierException(msg, source) - root = self._parseRoot(response) - response.close() - return root - - def _parseUser(self, response): - userid = name = displayname = None - events = ijson.sendable_list() - parser = ijson.parse_coro(events) - iterator = response.iterContent(g_chunk, False) - while iterator.hasMoreElements(): - parser.send(iterator.nextElement().value) - for prefix, event, value in events: - if (prefix, event) == ('id', 'string'): - userid = value - elif (prefix, event) == ('userPrincipalName', 'string'): - name = value - elif (prefix, event) == ('displayName', 'string'): - displayname = value - del events[:] - parser.close() - return userid, name, displayname - def _parseRoot(self, response): events = ijson.sendable_list() parser = ijson.parse_coro(events) @@ -319,30 +251,62 @@ def _parseRoot(self, response): parser.close() return rootid, created, modified - def parseUploadLocation(self, response): - url = None - events = ijson.sendable_list() - parser = ijson.parse_coro(events) - iterator = response.iterContent(g_chunk, False) - while iterator.hasMoreElements(): - parser.send(iterator.nextElement().value) - for prefix, event, value in events: - if (prefix, event) == ('uploadUrl', 'string'): - url = value - del events[:] - parser.close() - response.close() - return url - - def updateItemId(self, database, oldid, response): - newid = self._parseNewId(response) - if newid and oldid != newid: - database.updateItemId(newid, oldid) - self.updateNewItemId(oldid, newid) - return newid + def _parseSharedFolder(self, request, parameter, parentid, timestamp): + parents = (parentid, ) + trashed = canrename = readonly = versionable = False + addchild = True + path = None + while parameter.hasNextPage(): + response = request.execute(parameter) + if response.Ok: + events = ijson.sendable_list() + parser = ijson.parse_coro(events) + iterator = response.iterContent(g_chunk, False) + while iterator.hasMoreElements(): + parser.send(iterator.nextElement().value) + for prefix, event, value in events: + if (prefix, event) == ('value.item', 'start_map'): + itemid = name = None + created = modified = timestamp + mimetype = g_ucpfolder + link = '' + size = 0 + elif (prefix, event) == ('value.item.remoteItem.id', 'string'): + itemid = value + elif (prefix, event) == ('value.item.remoteItem.parentReference.driveId', 'string'): + link = value + elif (prefix, event) == ('value.item.remoteItem.name', 'string'): + name = value + elif (prefix, event) == ('value.item.remoteItem.size', 'number'): + size = value + elif (prefix, event) == ('value.item.createdDateTime', 'string'): + created = self.parseDateTime(value) + elif (prefix, event) == ('value.item.lastModifiedDateTime', 'string'): + modified = self.parseDateTime(value) + elif (prefix, event) == ('value.item.remoteItem.file.mimeType', 'string'): + mimetype = value + elif (prefix, event) == ('value.item', 'end_map'): + if itemid and name: + yield {'Id': itemid, + 'Name': name, + 'DateCreated': created, + 'DateModified': modified, + 'MediaType': mimetype, + 'Size': size, + 'Link': link, + 'Trashed': trashed, + 'CanAddChild': addchild, + 'CanRename': canrename, + 'IsReadOnly': readonly, + 'IsVersionable': versionable, + 'Parents': parents, + 'Path': path} + del events[:] + parser.close() + response.close() - def _parseNewId(self, response): - newid = None + def _parseUser(self, response): + userid = name = displayname = None events = ijson.sendable_list() parser = ijson.parse_coro(events) iterator = response.iterContent(g_chunk, False) @@ -350,19 +314,20 @@ def _parseNewId(self, response): parser.send(iterator.nextElement().value) for prefix, event, value in events: if (prefix, event) == ('id', 'string'): - newid = value + userid = value + elif (prefix, event) == ('userPrincipalName', 'string'): + name = value + elif (prefix, event) == ('displayName', 'string'): + displayname = value del events[:] parser.close() - response.close() - return newid - - def updateDrive(self, database, user, token): - if database.updateToken(user.get('UserId'), token): - user['Token'] = token + return userid, name, displayname + # Requests get Parameter method def getRequestParameter(self, request, method, data=None): parameter = request.getRequestParameter(method) parameter.Url = self.BaseUrl + if method == 'getUser': parameter.Url += '/me' parameter.setQuery('$select', g_userfields) @@ -397,15 +362,15 @@ def getRequestParameter(self, request, method, data=None): parameter.setQuery('$top', g_pages) elif method == 'getDocumentLocation': - if data.Link: - url = '/drives/%s/items/%s/content' % (data.Link, data.Id) + if data.get('Link'): + url = '/drives/%s/items/%s/content' % (data.get('Link'), data.get('Id')) else: - url = '/me/drive/items/%s/content' % data.Id + url = '/me/drive/items/%s/content' % data.get('Id') parameter.Url += url print("Provider.getRequestParameter() Name: %s - Url: %s" % (parameter.Name, parameter.Url)) parameter.NoRedirect = True - elif method == 'getDocumentContent': + elif method == 'downloadFile': parameter.Url = data parameter.NoAuth = True @@ -484,5 +449,6 @@ def getRequestParameter(self, request, method, data=None): else: url = '/me/drive/items/%s:/%s:/content' % (data.get('ParentId'), data.get('Title')) parameter.Url += url + return parameter diff --git a/uno/.gitrepo b/uno/.gitrepo index 60fcffd4..5724425a 100644 --- a/uno/.gitrepo +++ b/uno/.gitrepo @@ -5,8 +5,8 @@ ; [subrepo] remote = https://github.com/prrvchr/uno.git - branch = main - commit = c52477bd0abfa9de802a5c38224da5ffcce970cb - parent = f9102cba929d8567f622288115d2dfb98912c313 + branch = master + commit = d8cb318dfb8fe8e0c1a5319ea48172efdd3ec2c2 method = merge cmdver = 0.4.3 + parent = 5844cfaa56ed1111cc354e346764793fcebaba29 diff --git a/uno/dialog/ucb/OptionsDialog.xdl b/uno/dialog/ucb/OptionsDialog.xdl index bd06455a..68382e32 100644 --- a/uno/dialog/ucb/OptionsDialog.xdl +++ b/uno/dialog/ucb/OptionsDialog.xdl @@ -47,42 +47,43 @@ + - + - + - + - - - + + + - + - + - + - - + + - - - + + + - + diff --git a/uno/lib/uno/dbtool/__init__.py b/uno/lib/uno/dbtool/__init__.py index a1f0f0f1..e6a9e25c 100644 --- a/uno/lib/uno/dbtool/__init__.py +++ b/uno/lib/uno/dbtool/__init__.py @@ -4,7 +4,7 @@ """ ╔════════════════════════════════════════════════════════════════════════════════════╗ β•‘ β•‘ -β•‘ Copyright (c) 2020 https://prrvchr.github.io β•‘ +β•‘ Copyright (c) 2020-24 https://prrvchr.github.io β•‘ β•‘ β•‘ β•‘ Permission is hereby granted, free of charge, to any person obtaining β•‘ β•‘ a copy of this software and associated documentation files (the "Software"), β•‘ diff --git a/uno/lib/uno/dbtool/array.py b/uno/lib/uno/dbtool/array.py index c3bc0218..5fd2a91f 100644 --- a/uno/lib/uno/dbtool/array.py +++ b/uno/lib/uno/dbtool/array.py @@ -4,7 +4,7 @@ """ ╔════════════════════════════════════════════════════════════════════════════════════╗ β•‘ β•‘ -β•‘ Copyright (c) 2020 https://prrvchr.github.io β•‘ +β•‘ Copyright (c) 2020-24 https://prrvchr.github.io β•‘ β•‘ β•‘ β•‘ Permission is hereby granted, free of charge, to any person obtaining β•‘ β•‘ a copy of this software and associated documentation files (the "Software"), β•‘ diff --git a/uno/lib/uno/dbtool/dbinit.py b/uno/lib/uno/dbtool/dbinit.py index 97b4c9fe..74486d93 100644 --- a/uno/lib/uno/dbtool/dbinit.py +++ b/uno/lib/uno/dbtool/dbinit.py @@ -4,7 +4,7 @@ """ ╔════════════════════════════════════════════════════════════════════════════════════╗ β•‘ β•‘ -β•‘ Copyright (c) 2020 https://prrvchr.github.io β•‘ +β•‘ Copyright (c) 2020-24 https://prrvchr.github.io β•‘ β•‘ β•‘ β•‘ Permission is hereby granted, free of charge, to any person obtaining β•‘ β•‘ a copy of this software and associated documentation files (the "Software"), β•‘ diff --git a/uno/lib/uno/dbtool/dbtool.py b/uno/lib/uno/dbtool/dbtool.py index 15dd49d8..3aac9210 100644 --- a/uno/lib/uno/dbtool/dbtool.py +++ b/uno/lib/uno/dbtool/dbtool.py @@ -4,7 +4,7 @@ """ ╔════════════════════════════════════════════════════════════════════════════════════╗ β•‘ β•‘ -β•‘ Copyright (c) 2020 https://prrvchr.github.io β•‘ +β•‘ Copyright (c) 2020-24 https://prrvchr.github.io β•‘ β•‘ β•‘ β•‘ Permission is hereby granted, free of charge, to any person obtaining β•‘ β•‘ a copy of this software and associated documentation files (the "Software"), β•‘ diff --git a/uno/lib/uno/dbtool/object.py b/uno/lib/uno/dbtool/object.py index cb906414..7ec30438 100644 --- a/uno/lib/uno/dbtool/object.py +++ b/uno/lib/uno/dbtool/object.py @@ -4,7 +4,7 @@ """ ╔════════════════════════════════════════════════════════════════════════════════════╗ β•‘ β•‘ -β•‘ Copyright (c) 2020 https://prrvchr.github.io β•‘ +β•‘ Copyright (c) 2020-24 https://prrvchr.github.io β•‘ β•‘ β•‘ β•‘ Permission is hereby granted, free of charge, to any person obtaining β•‘ β•‘ a copy of this software and associated documentation files (the "Software"), β•‘ diff --git a/uno/lib/uno/options/ucb/__init__.py b/uno/lib/uno/options/ucb/__init__.py index 81e0c433..6512086e 100644 --- a/uno/lib/uno/options/ucb/__init__.py +++ b/uno/lib/uno/options/ucb/__init__.py @@ -4,7 +4,7 @@ """ ╔════════════════════════════════════════════════════════════════════════════════════╗ β•‘ β•‘ -β•‘ Copyright (c) 2020 https://prrvchr.github.io β•‘ +β•‘ Copyright (c) 2020-24 https://prrvchr.github.io β•‘ β•‘ β•‘ β•‘ Permission is hereby granted, free of charge, to any person obtaining β•‘ β•‘ a copy of this software and associated documentation files (the "Software"), β•‘ diff --git a/uno/lib/uno/options/ucb/optionsmanager.py b/uno/lib/uno/options/ucb/optionsmanager.py index 6ef47fbc..a6a4c704 100644 --- a/uno/lib/uno/options/ucb/optionsmanager.py +++ b/uno/lib/uno/options/ucb/optionsmanager.py @@ -4,7 +4,7 @@ """ ╔════════════════════════════════════════════════════════════════════════════════════╗ β•‘ β•‘ -β•‘ Copyright (c) 2020 https://prrvchr.github.io β•‘ +β•‘ Copyright (c) 2020-24 https://prrvchr.github.io β•‘ β•‘ β•‘ β•‘ Permission is hereby granted, free of charge, to any person obtaining β•‘ β•‘ a copy of this software and associated documentation files (the "Software"), β•‘ @@ -64,8 +64,8 @@ def loadSetting(self): self._logger.logprb(INFO, 'OptionsManager', 'loadSetting()', 161) def saveSetting(self): - share, name, index, timeout, download, upload = self._view.getViewData() - option = self._model.setViewData(share, name, index, timeout, download, upload) + reset, share, name, index, timeout, download, upload = self._view.getViewData() + option = self._model.setViewData(reset, share, name, index, timeout, download, upload) changed = self._logmanager.saveSetting() if changed: OptionsManager._restart = True @@ -76,7 +76,7 @@ def enableShare(self, enabled): self._view.enableShare(enabled) def enableSync(self, enabled): - self._view.enableSync(enabled, OptionsManager._restart) + self._view.enableSync(enabled, OptionsManager._restart, self._model.hasDataBase()) def viewData(self): url = self._model.getDatasourceUrl() diff --git a/uno/lib/uno/options/ucb/optionsmodel.py b/uno/lib/uno/options/ucb/optionsmodel.py index 51c720db..441f6040 100644 --- a/uno/lib/uno/options/ucb/optionsmodel.py +++ b/uno/lib/uno/options/ucb/optionsmodel.py @@ -4,7 +4,7 @@ """ ╔════════════════════════════════════════════════════════════════════════════════════╗ β•‘ β•‘ -β•‘ Copyright (c) 2020 https://prrvchr.github.io β•‘ +β•‘ Copyright (c) 2020-24 https://prrvchr.github.io β•‘ β•‘ β•‘ β•‘ Permission is hereby granted, free of charge, to any person obtaining β•‘ β•‘ a copy of this software and associated documentation files (the "Software"), β•‘ @@ -47,15 +47,18 @@ class OptionsModel(): def __init__(self, ctx): - self._ctx = ctx self._config = getConfiguration(ctx, g_identifier, True) folder = g_folder + g_ucbseparator + g_scheme location = getResourceLocation(ctx, g_identifier, folder) self._url = location + '.odb' + self._exist = getSimpleFile(ctx).exists(self._url) self._policies = {'SERVER_IS_MASTER': 1, 'CLIENT_IS_MASTER': 2, 'NONE_IS_MASTER': 3} self._factors = {'Timeout': 60, 'Chunk': 1024} @property + def _ResetSync(self): + return self._config.getByName('ResetSync') + @property def _IsShared(self): return self._config.getByName('SharedDocuments') @property @@ -83,20 +86,23 @@ def _SupportShare(self): # OptionsModel getter methods def getInitData(self): - hasdata = getSimpleFile(self._ctx).exists(self._url) resumable = self._config.getByName('ResumableUpload') - return hasdata, resumable + return self._exist, resumable + + def hasDataBase(self): + return self._exist def getViewData(self, restart): - return (self._SupportShare, self._IsShared, self._ShareName, - self._Policy, self._Timeout, - self._Download, self._Upload, restart) + return (self._exist, self._ResetSync, self._SupportShare, + self._IsShared, self._ShareName, self._Policy, + self._Timeout, self._Download, self._Upload, restart) def getDatasourceUrl(self): return self._url # OptionsModel setter methods - def setViewData(self, share, name, index, timeout, download, upload): + def setViewData(self, reset, share, name, index, timeout, download, upload): + self._setReset(reset) self._setShared(share) self._setShare(name) self._setPolicy(index) @@ -124,6 +130,10 @@ def _getSetting(self, config): return setting # OptionsModel private setter methods + def _setReset(self, enabled): + if enabled != self._ResetSync: + self._config.replaceByName('ResetSync', enabled) + def _setShared(self, enabled): if enabled != self._IsShared: self._config.replaceByName('SharedDocuments', enabled) diff --git a/uno/lib/uno/options/ucb/optionsview.py b/uno/lib/uno/options/ucb/optionsview.py index 65254348..4ae2b5a0 100644 --- a/uno/lib/uno/options/ucb/optionsview.py +++ b/uno/lib/uno/options/ucb/optionsview.py @@ -4,7 +4,7 @@ """ ╔════════════════════════════════════════════════════════════════════════════════════╗ β•‘ β•‘ -β•‘ Copyright (c) 2020 https://prrvchr.github.io β•‘ +β•‘ Copyright (c) 2020-24 https://prrvchr.github.io β•‘ β•‘ β•‘ β•‘ Permission is hereby granted, free of charge, to any person obtaining β•‘ β•‘ a copy of this software and associated documentation files (the "Software"), β•‘ @@ -35,18 +35,20 @@ def __init__(self, window, exist, resumable): self._window = window if exist: self._disableShare() + self._getReset().Model.Enabled = exist self._getDatasource().Model.Enabled = exist self._getUpload().Model.Enabled = resumable # OptionsView getter methods def getViewData(self): + reset = bool(self._getReset().State) share = bool(self._getShare().State) name = self._getShareName().Text index = self._getOptionIndex() timeout = int(self._getTimeout().Value) download = self._getSetting(0) upload = self._getSetting(1) - return share, name, index, timeout, download, upload + return reset, share, name, index, timeout, download, upload def getChunk(self, index): return int(self._getChunk(index).Value) @@ -58,7 +60,8 @@ def setStep(self, step, restart): # XXX: because it was lost (ie: after setting the new step everything is visible). self.setRestart(restart) - def setViewData(self, support, share, name, index, timeout, download, upload, restart): + def setViewData(self, exist, reset, support, share, name, index, timeout, download, upload, restart): + self._getReset().State = int(reset) if support: self._getShare().State = int(share) self._getShareName().Text = name @@ -69,7 +72,7 @@ def setViewData(self, support, share, name, index, timeout, download, upload, re self._getShareName().Text = name self.enableShare(False) self._getOption(index).State = 1 - self.enableSync(index != 3, restart) + self.enableSync(index != 3, restart, exist) self._getTimeout().Value = timeout self._setSetting(download, 0) self._setSetting(upload, 1) @@ -78,7 +81,8 @@ def setViewData(self, support, share, name, index, timeout, download, upload, re def enableShare(self, enabled): self._getShareName().Model.Enabled = enabled - def enableSync(self, enabled, restart): + def enableSync(self, enabled, restart, exist): + self._getReset().Model.Enabled = enabled and exist self._getTimeoutLabel().Model.Enabled = enabled self._getTimeout().Model.Enabled = enabled self._enableUpload(enabled, restart) @@ -124,9 +128,12 @@ def _enableUpload(self, enabled, restart): control.Model.Enabled = enabled # OptionsView private control methods - def _getShare(self): + def _getReset(self): return self._window.getControl('CheckBox1') + def _getShare(self): + return self._window.getControl('CheckBox2') + def _getShareName(self): return self._window.getControl('TextField1') diff --git a/uno/lib/uno/ucb/__init__.py b/uno/lib/uno/ucb/__init__.py index 543b015f..2872a4d6 100644 --- a/uno/lib/uno/ucb/__init__.py +++ b/uno/lib/uno/ucb/__init__.py @@ -4,7 +4,7 @@ """ ╔════════════════════════════════════════════════════════════════════════════════════╗ β•‘ β•‘ -β•‘ Copyright (c) 2020 https://prrvchr.github.io β•‘ +β•‘ Copyright (c) 2020-24 https://prrvchr.github.io β•‘ β•‘ β•‘ β•‘ Permission is hereby granted, free of charge, to any person obtaining β•‘ β•‘ a copy of this software and associated documentation files (the "Software"), β•‘ diff --git a/uno/lib/uno/ucb/contentprovider.py b/uno/lib/uno/ucb/contentprovider.py index c033acbb..6ba4a3e3 100644 --- a/uno/lib/uno/ucb/contentprovider.py +++ b/uno/lib/uno/ucb/contentprovider.py @@ -4,7 +4,7 @@ """ ╔════════════════════════════════════════════════════════════════════════════════════╗ β•‘ β•‘ -β•‘ Copyright (c) 2020 https://prrvchr.github.io β•‘ +β•‘ Copyright (c) 2020-24 https://prrvchr.github.io β•‘ β•‘ β•‘ β•‘ Permission is hereby granted, free of charge, to any person obtaining β•‘ β•‘ a copy of this software and associated documentation files (the "Software"), β•‘ @@ -51,6 +51,8 @@ from .unotool import checkVersion from .unotool import getExtensionVersion +from .unotool import getUrlTransformer +from .unotool import parseUrl from .dbtool import getConnectionUrl @@ -86,6 +88,7 @@ def __init__(self, ctx, logger, authority, arguments): self._authority = authority self._cls = f'{arguments}ContentProvider' self._services = ('com.sun.star.ucb.ContentProvider', f'{g_identifier}.ContentProvider') + self._transformer = getUrlTransformer(ctx) self._logger = logger self._logger.logprb(INFO, self._cls, '__init__()', 201, arguments) @@ -99,14 +102,14 @@ def _datasource(self): # XContentIdentifierFactory def createContentIdentifier(self, url): - identifier = Identifier(url) + identifier = Identifier(self._getPresentationUrl(url)) self._logger.logprb(INFO, self._cls, 'createContentIdentifier()', 211, url, identifier.getContentIdentifier()) return identifier # XContentProvider def queryContent(self, identifier): try: - url = identifier.getContentIdentifier() + url = self._getPresentationUrl(identifier.getContentIdentifier()) content = self._datasource.queryContent(self, self._authority, url) self._logger.logprb(INFO, self._cls, 'queryContent()', 231, url) return content @@ -119,8 +122,8 @@ def queryContent(self, identifier): print(msg) def compareContentIds(self, id1, id2): - uri1 = self._datasource.parseIdentifier(id1) - uri2 = self._datasource.parseIdentifier(id2) + uri1 = self._datasource.parseUrl(self._getPresentationUrl(id1.getContentIdentifier())) + uri2 = self._datasource.parseUrl(self._getPresentationUrl(id2.getContentIdentifier())) auth1 = uri1.getAuthority() if uri1.hasAuthority() else self._datasource.getDefaultUser() auth2 = uri2.getAuthority() if uri2.hasAuthority() else self._datasource.getDefaultUser() if (auth1 != auth2 or uri1.getPath() != uri2.getPath()): @@ -172,6 +175,14 @@ def _getDataSource(self): return DataSource(self._ctx, self._logger, database) return None + def _getPresentationUrl(self, url): + # FIXME: Sometimes the url can end with a dot or a slash, it must be deleted + url = url.rstrip('/.') + uri = parseUrl(self._transformer, url) + if uri is not None: + url = self._transformer.getPresentation(uri, True) + return url + def _getExceptionMessage(self, method, code, extension, *args): return getExceptionMessage(self._ctx, self._logger, self._cls, method, code, extension, *args) diff --git a/uno/lib/uno/ucb/database.py b/uno/lib/uno/ucb/database.py index 5fbd5700..b4562b99 100644 --- a/uno/lib/uno/ucb/database.py +++ b/uno/lib/uno/ucb/database.py @@ -4,7 +4,7 @@ """ ╔════════════════════════════════════════════════════════════════════════════════════╗ β•‘ β•‘ -β•‘ Copyright (c) 2020 https://prrvchr.github.io β•‘ +β•‘ Copyright (c) 2020-24 https://prrvchr.github.io β•‘ β•‘ β•‘ β•‘ Permission is hereby granted, free of charge, to any person obtaining β•‘ β•‘ a copy of this software and associated documentation files (the "Software"), β•‘ @@ -91,21 +91,20 @@ def isUptoDate(self): return checkVersion(self._version, g_version) # Procedures called by the DataSource - def getDataSource(self): - return self.Connection.getParent().DatabaseDocument.DataSource - - def storeDataBase(self, url): - self.Connection.getParent().DatabaseDocument.storeAsURL(url, ()) - def addCloseListener(self, listener): - self.Connection.Parent.DatabaseDocument.addCloseListener(listener) + self.Connection.getParent().DatabaseDocument.addCloseListener(listener) def shutdownDataBase(self, compact=False): if compact: query = getSqlQuery(self._ctx, 'shutdownCompact') - else: - query = getSqlQuery(self._ctx, 'shutdown') - self._statement.execute(query) + self._statement.execute(query) + #else: + # query = getSqlQuery(self._ctx, 'shutdown') + #self._statement.execute(query) + self.dispose() + + def dispose(self): + self._statement.close() def createUser(self, name, password): return createUser(self.Connection, name, password, g_role) @@ -138,13 +137,6 @@ def selectUser(self, name): select.close() return user - def getDefaultUserTimeStamp(self): - dtz = DateTimeWithTimezone() - dtz.DateTimeInTZ.Year = 1970 - dtz.DateTimeInTZ.Month = 1 - dtz.DateTimeInTZ.Day = 1 - return dtz - def insertUser(self, user, root): data = None timestamp = currentDateTimeInTZ() @@ -241,7 +233,8 @@ def _getChildrenColumns(self, properties): def updateConnectionMode(self, userid, itemid, value): update = self._getCall('updateConnectionMode') update.setShort(1, value) - update.setString(2, itemid) + update.setString(2, userid) + update.setString(3, itemid) update.executeUpdate() update.close() return value @@ -272,7 +265,8 @@ def updateContent(self, userid, itemid, property, value): update = self._getCall('updateName') update.setObject(1, timestamp) update.setString(2, value) - update.setString(3, itemid) + update.setString(3, userid) + update.setString(4, itemid) updated = update.execute() == 0 update.close() clear = True @@ -282,27 +276,20 @@ def updateContent(self, userid, itemid, property, value): update.setObject(1, timestamp) update.setLong(2, value) update.setTimestamp(3, currentUnoDateTime()) - update.setString(4, itemid) + update.setString(4, userid) + update.setString(5, itemid) updated = update.execute() == 0 update.close() elif property == 'Trashed': update = self._getCall('updateTrashed') update.setObject(1, timestamp) update.setBoolean(2, value) - update.setString(3, itemid) + update.setString(3, userid) + update.setString(4, itemid) updated = update.execute() == 0 update.close() return updated, clear - def getNewTitle(self, title, parentid): - call = self._getCall('getNewTitle') - call.setString(1, title) - call.setString(2, parentid) - call.execute() - newtitle = call.getString(3) - call.close() - return newtitle - def insertNewContent(self, userid, item, timestamp): try: inserted = False @@ -369,9 +356,8 @@ def updateToken(self, userid, token): update = self._getCall('updateToken') update.setString(1, token) update.setString(2, userid) - updated = update.executeUpdate() == 1 + update.executeUpdate() update.close() - return updated # Identifier counting procedure def countIdentifier(self, userid): @@ -400,6 +386,20 @@ def insertIdentifier(self, iterator, userid): return count # Pull procedure + def pullItem(self, userid, item, timestamp, mode=1): + call1 = self._getCall('mergeItem') + call2 = self._getCall('mergeParent') + call1.setString(1, userid) + call2.setString(1, userid) + call1.setInt(2, mode) + call1.setObject(3, timestamp) + if self._mergeItem(call1, call2, item, timestamp): + call1.executeBatch() + call2.executeBatch() + call1.close() + call2.close() + return 1 + def pullItems(self, iterator, userid, timestamp, mode=1): count = 0 call1 = self._getCall('mergeItem') @@ -417,30 +417,6 @@ def pullItems(self, iterator, userid, timestamp, mode=1): call2.close() return count - def pullChanges(self, iterator, userid, timestamp): - call = self._getCall('pullChanges') - count = 0 - for item in iterator: - call.setString(1, userid) - call.setString(2, item[0]) - call.setBoolean(3, item[1]) - call.setNull(4, VARCHAR) if item[2] is None else call.setString(4, item[2]) - call.setTimestamp(5, item[3]) - call.setObject(6, timestamp) - call.addBatch() - count += 1 - if count: - call.executeBatch() - call.close() - return count - - def updateUserSyncMode(self, userid, mode): - update = self._getCall('updateUserSyncMode') - update.setInt(1, mode) - update.setString(2, userid) - update.executeUpdate() - update.close() - # Procedure to retrieve all the UPDATE AND INSERT in the 'Capabilities' table def getPushItems(self, userid, start, end): select = self._getCall('getPushItems') @@ -468,14 +444,12 @@ def getPushProperties(self, userid, itemid, start, end): return properties def updatePushItems(self, user, itemids): - # XXX: We push items only if needed (ie: not empty) - if itemids: - call = self._getCall('updatePushItems') - call.setString(1, user.Id) - call.setArray(2, Array('VARCHAR', itemids)) - call.execute() - user.TimeStamp = call.getObject(3, None) - call.close() + call = self._getCall('updatePushItems') + call.setString(1, user.Id) + call.setArray(2, Array('VARCHAR', itemids)) + call.execute() + user.TimeStamp = call.getObject(3, None) + call.close() def getItemParentIds(self, itemid, metadata, start, end): call = self._getCall('getItemParentIds') @@ -489,43 +463,44 @@ def getItemParentIds(self, itemid, metadata, start, end): metadata.insertValue('ParentToAdd', set(new) - set(old)) metadata.insertValue('ParentToRemove', set(old) - set(new)) - def updateItemId(self, newid, oldid): + def updateItemId(self, userid, newid, oldid): print("DataBase.updateItemId () NewId: %s - OldId: %s" % (newid, oldid)) update = self._getCall('updateItemId') update.setString(1, newid) - update.setString(2, oldid) + update.setString(2, userid) + update.setString(3, oldid) update.executeUpdate() update.close() # Procedures called internally def _mergeItem(self, call1, call2, item, timestamp): - itemid = item[0] + itemid = item.get('Id') call1.setString(4, itemid) - call1.setString(5, item[1]) - call1.setTimestamp(6, item[2]) - call1.setTimestamp(7, item[3]) - call1.setString(8, item[4]) - size = item[5] + call1.setString(5, item.get('Name')) + call1.setTimestamp(6, item.get('DateCreated')) + call1.setTimestamp(7, item.get('DateModified')) + call1.setString(8, item.get('MediaType')) + size = item.get('Size') if os.name == 'nt': mx = 2 ** 32 / 2 -1 if size > mx: size = min(size, mx) - self._logger.logprb(SEVERE, 'DataBase', '_mergeItem()', 402, size, item[5]) + self._logger.logprb(SEVERE, 'DataBase', '_mergeItem()', 402, size, item.get('Size')) call1.setLong(9, size) - call1.setString(10, item[6]) - call1.setBoolean(11, item[7]) - call1.setBoolean(12, item[8]) - call1.setBoolean(13, item[9]) - call1.setBoolean(14, item[10]) - call1.setBoolean(15, item[11]) + call1.setString(10, item.get('Link')) + call1.setBoolean(11, item.get('Trashed')) + call1.setBoolean(12, item.get('CanAddChild')) + call1.setBoolean(13, item.get('CanRename')) + call1.setBoolean(14, item.get('IsReadOnly')) + call1.setBoolean(15, item.get('IsVersionable')) call1.addBatch() self._mergeParent(call2, item, itemid, timestamp) return 1 def _mergeParent(self, call, item, itemid, timestamp): call.setString(2, itemid) - call.setArray(3, Array('VARCHAR', item[12])) - path = item[13] + call.setArray(3, Array('VARCHAR', item.get('Parents'))) + path = item.get('Path') if path is None: call.setNull(4, VARCHAR) else: diff --git a/uno/lib/uno/ucb/datasource.py b/uno/lib/uno/ucb/datasource.py index a328e921..e3a1bd1a 100644 --- a/uno/lib/uno/ucb/datasource.py +++ b/uno/lib/uno/ucb/datasource.py @@ -4,7 +4,7 @@ """ ╔════════════════════════════════════════════════════════════════════════════════════╗ β•‘ β•‘ -β•‘ Copyright (c) 2020 https://prrvchr.github.io β•‘ +β•‘ Copyright (c) 2020-24 https://prrvchr.github.io β•‘ β•‘ β•‘ β•‘ Permission is hereby granted, free of charge, to any person obtaining β•‘ β•‘ a copy of this software and associated documentation files (the "Software"), β•‘ @@ -27,12 +27,6 @@ β•šβ•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β• """ -import uno -import unohelper - -from com.sun.star.util import XCloseListener -from com.sun.star.util import CloseVetoException - from com.sun.star.logging.LogLevel import INFO from com.sun.star.logging.LogLevel import SEVERE @@ -40,9 +34,7 @@ from .oauth2 import getOAuth2UserName -from .unotool import getUrlTransformer from .unotool import getUriFactory -from .unotool import parseUrl from .ucp import User from .ucp import getExceptionMessage @@ -51,6 +43,8 @@ from .replicator import Replicator +from .listener import CloseListener + from .configuration import g_extension from threading import Event @@ -58,8 +52,7 @@ import traceback -class DataSource(unohelper.Base, - XCloseListener): +class DataSource(): def __init__(self, ctx, logger, database): self._ctx = ctx self._default = '' @@ -69,22 +62,25 @@ def __init__(self, ctx, logger, database): self._sync = Event() self._lock = Lock() self._urifactory = getUriFactory(ctx) - self._transformer = getUrlTransformer(ctx) - database.addCloseListener(self) self._provider = Provider(ctx, logger) self.Replicator = Replicator(ctx, database.Url, self._provider, self._users, self._sync, self._lock) self.DataBase = database + database.addCloseListener(CloseListener(self)) self._logger.logprb(INFO, 'DataSource', '__init__()', 301) - # DataSource - def getDefaultUser(self): - return self._default - - def parseIdentifier(self, identifier): - url = self._getPresentationUrl(identifier.getContentIdentifier()) - return self._urifactory.parse(url) - - # FIXME: Get called from ParameterizedProvider.queryContent() + # called from XCloseListener + def dispose(self): + try: + if self.Replicator.is_alive(): + self.Replicator.cancel() + for user in self._users.values(): + user.dispose() + self.DataBase.shutdownDataBase(self.Replicator.fullPull()) + self._logger.logprb(INFO, 'DataSource', 'dispose()', 341, self._provider.Scheme) + except Exception as e: + self._logger.logprb(SEVERE, 'DataSource', 'dispose()', 342, e, traceback.format_exc()) + + # Get called from ContentProvider.queryContent() def queryContent(self, source, authority, url): user, uri = self._getUser(source, authority, url) if uri is None: @@ -97,24 +93,17 @@ def queryContent(self, source, authority, url): raise IllegalIdentifierException(msg, source) return content - # XCloseListener - def queryClosing(self, source, ownership): - if ownership: - raise CloseVetoException('cant close', self) - if self.Replicator.is_alive(): - self.Replicator.cancel() - self.Replicator.join() - self.DataBase.shutdownDataBase(self.Replicator.fullPull()) - self._logger.logprb(INFO, 'DataSource', 'queryClosing()', 341, self._provider.Scheme) - def notifyClosing(self, source): - pass - def disposing(self, source): - pass + # Get called from ContentProvider.compareContentIds() + def getDefaultUser(self): + return self._default + + def parseUrl(self, url): + return self._urifactory.parse(url) # Private methods def _getUser(self, source, authority, url): default = False - uri = self._urifactory.parse(self._getPresentationUrl(url)) + uri = self.parseUrl(url) if uri is None: msg = self._logger.resolveString(321, url) raise IllegalIdentifierException(msg, source) @@ -129,31 +118,24 @@ def _getUser(self, source, authority, url): else: name = self._getUserName(source, url) default = True - # User never change... we can cache it... + # XXX: User never change... we can cache it... if name in self._users: user = self._users[name] if not user.Request.isAuthorized(): - # The user's OAuth2 configuration has been deleted and - # the OAuth2 configuration wizard has been canceled. + # XXX: The user's OAuth2 configuration has been deleted and + # XXX: the OAuth2 configuration wizard has been canceled. msg = self._getExceptionMessage('_getUser()', 324, name) raise IllegalIdentifierException(msg, source) else: user = User(self._ctx, source, self._logger, self.DataBase, self._provider, self._sync, name) self._users[name] = user - # FIXME: if the user has been instantiated then we can consider it as the default user + # XXX: If the user has been requested and instantiated + # XXX: then we can consider it as the default user if default: self._default = name return user, uri - def _getPresentationUrl(self, url): - # FIXME: Sometimes the url can end with a dot or a slash, it must be deleted - url = url.rstrip('/.') - uri = parseUrl(self._transformer, url) - if uri is not None: - uri = self._transformer.getPresentation(uri, True) - return uri if uri else url - def _getUserName(self, source, url): name = getOAuth2UserName(self._ctx, self, self._provider.Scheme) if not name: diff --git a/uno/lib/uno/ucb/dbconfig.py b/uno/lib/uno/ucb/dbconfig.py index 3bb732c7..b51df6e8 100644 --- a/uno/lib/uno/ucb/dbconfig.py +++ b/uno/lib/uno/ucb/dbconfig.py @@ -4,7 +4,7 @@ """ ╔════════════════════════════════════════════════════════════════════════════════════╗ β•‘ β•‘ -β•‘ Copyright (c) 2020 https://prrvchr.github.io β•‘ +β•‘ Copyright (c) 2020-24 https://prrvchr.github.io β•‘ β•‘ β•‘ β•‘ Permission is hereby granted, free of charge, to any person obtaining β•‘ β•‘ a copy of this software and associated documentation files (the "Software"), β•‘ diff --git a/uno/lib/uno/ucb/dbinit.py b/uno/lib/uno/ucb/dbinit.py index 20ec89e5..d7d31ddd 100644 --- a/uno/lib/uno/ucb/dbinit.py +++ b/uno/lib/uno/ucb/dbinit.py @@ -4,7 +4,7 @@ """ ╔════════════════════════════════════════════════════════════════════════════════════╗ β•‘ β•‘ -β•‘ Copyright (c) 2020 https://prrvchr.github.io β•‘ +β•‘ Copyright (c) 2020-24 https://prrvchr.github.io β•‘ β•‘ β•‘ β•‘ Permission is hereby granted, free of charge, to any person obtaining β•‘ β•‘ a copy of this software and associated documentation files (the "Software"), β•‘ @@ -164,8 +164,8 @@ def _getViews(catalog=g_catalog, schema=g_schema): yield catalog, schema, name def _getProcedures(): - for name in ('GetItem', 'GetNewTitle', 'UpdatePushItems', 'GetPushItems', 'GetPushProperties', - 'GetItemParentIds', 'InsertUser', 'InsertSharedFolder', 'MergeItem', 'MergeParent', - 'InsertItem', 'PullChanges', 'UpdateNewItemId'): + for name in ('GetItem', 'UpdatePushItems', 'GetPushItems', 'GetPushProperties', + 'GetItemParentIds', 'InsertUser', 'InsertSharedFolder', 'MergeItem', + 'MergeParent', 'InsertItem', 'UpdateNewItemId'): yield name diff --git a/uno/lib/uno/ucb/dbqueries.py b/uno/lib/uno/ucb/dbqueries.py index 7f3568d2..e7f4cefe 100644 --- a/uno/lib/uno/ucb/dbqueries.py +++ b/uno/lib/uno/ucb/dbqueries.py @@ -4,7 +4,7 @@ """ ╔════════════════════════════════════════════════════════════════════════════════════╗ β•‘ β•‘ -β•‘ Copyright (c) 2020 https://prrvchr.github.io β•‘ +β•‘ Copyright (c) 2020-24 https://prrvchr.github.io β•‘ β•‘ β•‘ β•‘ Permission is hereby granted, free of charge, to any person obtaining β•‘ β•‘ a copy of this software and associated documentation files (the "Software"), β•‘ @@ -39,55 +39,83 @@ def getSqlQuery(ctx, name, format=None): -# Select queries for creating table, index, foreignkey and privileges from static table -# Create Function Queries - if name == 'createGetIsFolder': - query = '''\ -CREATE FUNCTION "GetIsFolder"(IN MIMETYPE VARCHAR(100)) - RETURNS BOOLEAN - SPECIFIC "GetIsFolder_1" - CONTAINS SQL - BEGIN ATOMIC - RETURN MIMETYPE = '%(UcpFolder)s'; - END; -GRANT EXECUTE ON SPECIFIC ROUTINE "GetIsFolder_1" TO "%(Role)s";''' % format +# Select Queries + if name == 'getUser': + query = 'SELECT "UserId", "UserName", "RootId", "Token", "DateCreated", "DateModified", "TimeStamp" FROM "Users" WHERE "UserName" = ?;' - elif name == 'createGetContentType': - query = '''\ -CREATE FUNCTION "GetContentType"(IN ISFOLDER BOOLEAN) - RETURNS VARCHAR(100) - SPECIFIC "GetContentType_1" - CONTAINS SQL - BEGIN ATOMIC - IF ISFOLDER THEN - RETURN '%(UcbFolder)s'; - ELSE - RETURN '%(UcbFile)s'; - END IF; - END; -GRANT EXECUTE ON SPECIFIC ROUTINE "GetContentType_1" TO "%(Role)s";''' % format + elif name == 'getChildren': + query = 'SELECT %(Columns)s FROM %(Children)s AS C WHERE C."UserId" = ? AND C."Path" = ? AND (C."IsFolder" = TRUE OR C."ConnectionMode" >= ?);' % format - elif name == 'createGetUniqueName': - query = '''\ -CREATE FUNCTION "GetUniqueName"(IN NAME VARCHAR(100), - IN NUMBER INTEGER) - RETURNS VARCHAR(110) - SPECIFIC "GetUniqueName_1" - CONTAINS SQL - BEGIN ATOMIC - DECLARE HINT VARCHAR(10); - DECLARE DOT BIGINT DEFAULT 0; - SET DOT = POSITION('.' IN REVERSE(NAME)); - SET HINT = '%(Prefix)s' || NUMBER || '%(Suffix)s'; - IF DOT != 0 AND DOT < 5 THEN - RETURN INSERT(NAME, CHAR_LENGTH(NAME) - DOT + 1, 0, HINT); - ELSE - RETURN NAME || HINT; - END IF; - END; -GRANT EXECUTE ON SPECIFIC ROUTINE "GetUniqueName_1" TO "%(Role)s";''' % format + elif name == 'getChildId': + query = 'SELECT "ItemId" FROM "Children" WHERE "ParentId" = ? AND "Path" = ? AND "Title" = ?;' + + elif name == 'getNewIdentifier': + query = 'SELECT "ItemId" FROM "Identifiers" WHERE "UserId" = ? ORDER BY "TimeStamp","ItemId" LIMIT 1;' + + elif name == 'countNewIdentifier': + query = 'SELECT COUNT("ItemId") "Ids" FROM "Identifiers" WHERE "UserId" = ?;' + + elif name == 'hasTitle': + query = 'SELECT COUNT("Name") > 0 FROM "Child" WHERE "UserId" = ? AND "ParentId" = ? AND "Name" = ?;' -# Create View Command +# Insert Queries + elif name == 'insertNewIdentifier': + query = 'INSERT INTO "Identifiers"("UserId", "ItemId") VALUES (?, ?);' + +# Update Queries + elif name == 'updateToken': + query = 'UPDATE "Users" SET "Token"=? WHERE "UserId"=?;' + + elif name == 'updateName': + query = 'UPDATE "Items" SET "TimeStamp"=?, "Name"=?, "SyncMode"=2 WHERE "UserId"=? AND "ItemId"=?;' + + elif name == 'updateSize': + query = 'UPDATE "Items" SET "TimeStamp"=?, "Size"=?, "DateModified"=?, "SyncMode"=2 WHERE "UserId"=? AND "ItemId"=?;' + + elif name == 'updateTrashed': + query = 'UPDATE "Items" SET "TimeStamp"=?, "Trashed"=?, "SyncMode"=2 WHERE "UserId"=? AND "ItemId"=?;' + + elif name == 'updateConnectionMode': + query = 'UPDATE "Items" SET "ConnectionMode"=? WHERE "UserId"=? AND "ItemId"=?;' + + elif name == 'updateItemId': + query = 'UPDATE "Items" SET "ItemId"=? WHERE "UserId"=? AND "ItemId"=?;' + +# Delete Queries + elif name == 'deleteNewIdentifier': + query = 'DELETE FROM "Identifiers" WHERE "UserId"=? AND "ItemId"=?;' + +# Call Procedure Query + elif name == 'getItem': + query = 'CALL "GetItem"(?,?,?)' + elif name == 'updatePushItems': + query = 'CALL "UpdatePushItems"(?,?,?)' + elif name == 'getPushItems': + query = 'CALL "GetPushItems"(?,?,?)' + elif name == 'getPushProperties': + query = 'CALL "GetPushProperties"(?,?,?,?)' + elif name == 'getItemParentIds': + query = 'CALL "GetItemParentIds"(?,?,?,?,?)' + elif name == 'insertUser': + query = 'CALL "InsertUser"(?,?,?,?,?,?,?)' + elif name == 'insertSharedFolder': + query = 'CALL "InsertSharedFolder"(?,?,?,?,?,?,?,?,?,?,?,?)' + elif name == 'mergeItem': + query = 'CALL "MergeItem"(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)' + elif name == 'mergeParent': + query = 'CALL "MergeParent"(?,?,?,?,?)' + elif name == 'insertItem': + query = 'CALL "InsertItem"(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)' + elif name == 'updateNewItemId': + query = 'CALL "UpdateNewItemId"(?,?,?,?,?)' + +# ShutDown Queries + elif name == 'shutdown': + query = 'SHUTDOWN;' + elif name == 'shutdownCompact': + query = 'SHUTDOWN COMPACT;' + +# Create View Command called by dbinit.py elif name == 'getChildViewCommand': query = '''\ SELECT I."UserId", P."ParentId", I."ItemId", I."Name", I."DateCreated", I."DateModified" @@ -134,64 +162,54 @@ def getSqlQuery(ctx, name, format=None): INNER JOIN %(Child)s AS C ON P."ItemId" = C."ItemId" AND P."ParentId" = C."ParentId" INNER JOIN %(Items)s AS I ON C."ItemId" = I."ItemId";''' % format -# Select Queries - elif name == 'getUser': +# Create Function Queries called by dbinit.py + elif name == 'createGetIsFolder': query = '''\ -SELECT "UserId", "UserName", "RootId", "Token", "SyncMode", "DateCreated", "DateModified", "TimeStamp" -FROM "Users" -WHERE "UserName" = ?;''' +CREATE FUNCTION "GetIsFolder"(IN MIMETYPE VARCHAR(100)) + RETURNS BOOLEAN + SPECIFIC "GetIsFolder_1" + CONTAINS SQL + BEGIN ATOMIC + RETURN MIMETYPE = '%(UcpFolder)s'; + END; +GRANT EXECUTE ON SPECIFIC ROUTINE "GetIsFolder_1" TO "%(Role)s";''' % format - elif name == 'getChildren': + elif name == 'createGetContentType': query = '''\ -SELECT %(Columns)s -FROM %(Children)s AS C -WHERE C."UserId" = ? AND C."Path" = ? AND (C."IsFolder" = TRUE OR C."ConnectionMode" >= ?); -''' % format +CREATE FUNCTION "GetContentType"(IN ISFOLDER BOOLEAN) + RETURNS VARCHAR(100) + SPECIFIC "GetContentType_1" + CONTAINS SQL + BEGIN ATOMIC + IF ISFOLDER THEN + RETURN '%(UcbFolder)s'; + ELSE + RETURN '%(UcbFile)s'; + END IF; + END; +GRANT EXECUTE ON SPECIFIC ROUTINE "GetContentType_1" TO "%(Role)s";''' % format - elif name == 'getChildId': + elif name == 'createGetUniqueName': query = '''\ -SELECT "ItemId" FROM "Children" WHERE "ParentId" = ? AND "Path" = ? AND "Title" = ?;''' - - elif name == 'getNewIdentifier': - query = 'SELECT "ItemId" FROM "Identifiers" WHERE "UserId" = ? ORDER BY "TimeStamp","ItemId" LIMIT 1;' - - elif name == 'countNewIdentifier': - query = 'SELECT COUNT("ItemId") "Ids" FROM "Identifiers" WHERE "UserId" = ?;' - - elif name == 'hasTitle': - query = 'SELECT COUNT("Name") > 0 FROM "Child" WHERE "UserId" = ? AND "ParentId" = ? AND "Name" = ?;' - -# Insert Queries - elif name == 'insertNewIdentifier': - query = 'INSERT INTO "Identifiers"("UserId", "ItemId") VALUES (?, ?);' - -# Update Queries - elif name == 'updateToken': - query = 'UPDATE "Users" SET "Token"=? WHERE "UserId"=?;' - - elif name == 'updateUserSyncMode': - query = 'UPDATE "Users" SET "SyncMode"=? WHERE "UserId"=?;' - - elif name == 'updateName': - query = 'UPDATE "Items" SET "TimeStamp"=?, "Name"=?, "SyncMode"=2 WHERE "ItemId"=?;' - - elif name == 'updateSize': - query = 'UPDATE "Items" SET "TimeStamp"=?, "Size"=?, "DateModified"=?, "SyncMode"=2 WHERE "ItemId"=?;' - - elif name == 'updateTrashed': - query = 'UPDATE "Items" SET "TimeStamp"=?, "Trashed"=?, "SyncMode"=2 WHERE "ItemId"=?;' - - elif name == 'updateConnectionMode': - query = 'UPDATE "Items" SET "ConnectionMode"=? WHERE "ItemId"=?;' - - elif name == 'updateItemId': - query = 'UPDATE "Items" SET "ItemId"=? WHERE "ItemId"=?;' - -# Delete Queries - elif name == 'deleteNewIdentifier': - query = 'DELETE FROM "Identifiers" WHERE "UserId"=? AND "ItemId"=?;' +CREATE FUNCTION "GetUniqueName"(IN NAME VARCHAR(100), + IN NUMBER INTEGER) + RETURNS VARCHAR(110) + SPECIFIC "GetUniqueName_1" + CONTAINS SQL + BEGIN ATOMIC + DECLARE HINT VARCHAR(10); + DECLARE DOT BIGINT DEFAULT 0; + SET DOT = POSITION('.' IN REVERSE(NAME)); + SET HINT = '%(Prefix)s' || NUMBER || '%(Suffix)s'; + IF DOT != 0 AND DOT < 5 THEN + RETURN INSERT(NAME, CHAR_LENGTH(NAME) - DOT + 1, 0, HINT); + ELSE + RETURN NAME || HINT; + END IF; + END; +GRANT EXECUTE ON SPECIFIC ROUTINE "GetUniqueName_1" TO "%(Role)s";''' % format -# Create Procedure Query +# Create Procedure Query called by dbinit.py elif name == 'createUpdateNewItemId': query = '''\ CREATE PROCEDURE "UpdateNewItemId"(IN USERID VARCHAR(320), @@ -234,26 +252,6 @@ def getSqlQuery(ctx, name, format=None): END; GRANT EXECUTE ON SPECIFIC ROUTINE "GetItem_1" TO "%(Role)s";''' % format - elif name == 'createGetNewTitle': - query = '''\ -CREATE PROCEDURE "GetNewTitle"(IN TITLE VARCHAR(100), - IN PARENTID VARCHAR(256), - OUT NEWTITLE VARCHAR(100)) - SPECIFIC "GetNewTitle_1" - READS SQL DATA - BEGIN ATOMIC - DECLARE NUMBER INTEGER; - DECLARE NEWNAME VARCHAR(100); - SELECT COUNT("Name") INTO NUMBER FROM "Child" WHERE "Name" = TITLE AND "ParentId" = PARENTID; - IF NUMBER > 0 THEN - SET NEWNAME = "GetUniqueName"(TITLE, NUMBER + 1); - ELSE - SET NEWNAME = TITLE; - END IF; - SET NEWTITLE = NEWNAME; - END; -GRANT EXECUTE ON SPECIFIC ROUTINE "GetNewTitle_1" TO "%(Role)s";''' % format - elif name == 'createInsertUser': query = '''\ CREATE PROCEDURE "InsertUser"(IN UserId VARCHAR(100), @@ -268,7 +266,7 @@ def getSqlQuery(ctx, name, format=None): DYNAMIC RESULT SETS 1 BEGIN ATOMIC DECLARE RSLT CURSOR WITH RETURN FOR - SELECT "UserId", "UserName", "RootId", "Token", "SyncMode", "DateCreated", "DateModified", "TimeStamp" + SELECT "UserId", "UserName", "RootId", "Token", "DateCreated", "DateModified", "TimeStamp" FROM "Users" WHERE "UserName" = UserName FOR READ ONLY; INSERT INTO "Users" ("UserId", "UserName", "DisplayName", "RootId", "DateCreated", "DateModified", "TimeStamp") @@ -317,9 +315,11 @@ def getSqlQuery(ctx, name, format=None): MODIFIES SQL DATA BEGIN ATOMIC DECLARE TS TIMESTAMP(6) WITH TIME ZONE; - UPDATE "Items" SET "SyncMode" = 0 WHERE "ItemId" IN (UNNEST(ITEMS)); - SELECT MAX("RowStart") INTO TS FROM "Items" FOR SYSTEM_TIME AS OF CURRENT_TIMESTAMP(6) - WHERE "ItemId"=ITEMS[CARDINALITY(ITEMS)]; + IF CARDINALITY(ITEMS) > 0 THEN + UPDATE "Items" SET "SyncMode" = 0 WHERE "ItemId" IN (UNNEST(ITEMS)); + SELECT MAX("RowStart") INTO TS FROM "Items" FOR SYSTEM_TIME AS OF CURRENT_TIMESTAMP(6) + WHERE "ItemId" IN (UNNEST(ITEMS)); + END IF; IF TS IS NULL THEN SELECT "TimeStamp" INTO TS FROM "Users" WHERE "UserId" = USERID; ELSE @@ -436,29 +436,6 @@ def getSqlQuery(ctx, name, format=None): END; GRANT EXECUTE ON SPECIFIC ROUTINE "GetItemParentIds_1" TO "%(Role)s";''' % format - elif name == 'createPullChanges': - query = '''\ -CREATE PROCEDURE "PullChanges"(IN UserId VARCHAR(100), - IN ItemId VARCHAR(256), - IN Trashed BOOLEAN, - IN Name VARCHAR(100), - IN Modified TIMESTAMP(6), - IN DateTime TIMESTAMP(6) WITH TIME ZONE) - SPECIFIC "PullChanges_1" - MODIFIES SQL DATA - BEGIN ATOMIC - IF Trashed THEN - DELETE FROM "Items" WHERE "UserId"=UserId AND "ItemId"=ItemId; - ELSEIF Name IS NULL THEN - UPDATE "Items" SET "DateModified"=Modified, "TimeStamp"=DateTime - WHERE "UserId"=UserId AND "ItemId"=ItemId; - ELSE - UPDATE "Items" SET "Name"=Name, "DateModified"=Modified, "TimeStamp"=DateTime - WHERE "UserId"=UserId AND "ItemId"=ItemId; - END IF; - END; -GRANT EXECUTE ON SPECIFIC ROUTINE "PullChanges_1" TO "%(Role)s";''' % format - elif name == 'createMergeItem': query = '''\ CREATE PROCEDURE "MergeItem"(IN UserId VARCHAR(100), @@ -574,43 +551,11 @@ def getSqlQuery(ctx, name, format=None): END; GRANT EXECUTE ON SPECIFIC ROUTINE "InsertItem_1" TO "%(Role)s";''' % format -# Get Procedure Query - elif name == 'getItem': - query = 'CALL "GetItem"(?,?,?)' - elif name == 'getNewTitle': - query = 'CALL "GetNewTitle"(?,?,?)' - elif name == 'updatePushItems': - query = 'CALL "UpdatePushItems"(?,?,?)' - elif name == 'getPushItems': - query = 'CALL "GetPushItems"(?,?,?)' - elif name == 'getPushProperties': - query = 'CALL "GetPushProperties"(?,?,?,?)' - elif name == 'getItemParentIds': - query = 'CALL "GetItemParentIds"(?,?,?,?,?)' - elif name == 'insertUser': - query = 'CALL "InsertUser"(?,?,?,?,?,?,?)' - elif name == 'insertSharedFolder': - query = 'CALL "InsertSharedFolder"(?,?,?,?,?,?,?,?,?,?,?,?)' - elif name == 'mergeItem': - query = 'CALL "MergeItem"(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)' - elif name == 'mergeParent': - query = 'CALL "MergeParent"(?,?,?,?,?)' - elif name == 'pullChanges': - query = 'CALL "PullChanges"(?,?,?,?,?,?)' - elif name == 'insertItem': - query = 'CALL "InsertItem"(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)' - elif name == 'updateNewItemId': - query = 'CALL "UpdateNewItemId"(?,?,?,?,?)' - -# ShutDown Queries - elif name == 'shutdown': - query = 'SHUTDOWN;' - elif name == 'shutdownCompact': - query = 'SHUTDOWN COMPACT;' - # Queries don't exist!!! else: logger = getLogger(ctx, g_defaultlog, g_basename) logger.logprb(SEVERE, g_basename, 'getSqlQuery()', 101, name) query = None + return query + diff --git a/uno/lib/uno/ucb/hsqldb/Columns.csv b/uno/lib/uno/ucb/hsqldb/Columns.csv index 260cbafb..0ac838ba 100644 --- a/uno/lib/uno/ucb/hsqldb/Columns.csv +++ b/uno/lib/uno/ucb/hsqldb/Columns.csv @@ -4,21 +4,21 @@ Column|Name 2|DisplayName 3|RootId 4|Token -5|SyncMode -6|DateCreated -7|DateModified -8|ItemId -9|Name -10|MediaType -11|Size -12|Link -13|Trashed -14|ConnectionMode -15|ParentId -16|CanAddChild -17|CanRename -18|IsReadOnly -19|IsVersionable +5|DateCreated +6|DateModified +7|ItemId +8|Name +9|MediaType +10|Size +11|Link +12|Trashed +13|ConnectionMode +14|ParentId +15|CanAddChild +16|CanRename +17|IsReadOnly +18|IsVersionable +19|SyncMode 20|TimeStamp 21|RowStart 22|RowEnd diff --git a/uno/lib/uno/ucb/hsqldb/ForeignKeys.csv b/uno/lib/uno/ucb/hsqldb/ForeignKeys.csv index 51562d83..a2f172a9 100644 --- a/uno/lib/uno/ucb/hsqldb/ForeignKeys.csv +++ b/uno/lib/uno/ucb/hsqldb/ForeignKeys.csv @@ -1,7 +1,7 @@ Table|Column|ReferencedTable|RelatedColumn|UpdateRule|DeleteRule 1|0|0|0|0|0 2|0|0|0|0|0 -2|8|1|8|0|0 +2|7|1|7|0|0 3|0|0|0|0|0 -3|8|1|8|0|0 +3|7|1|7|0|0 4|0|0|0|0|0 diff --git a/uno/lib/uno/ucb/hsqldb/Indexes.csv b/uno/lib/uno/ucb/hsqldb/Indexes.csv index 56f00193..67453514 100644 --- a/uno/lib/uno/ucb/hsqldb/Indexes.csv +++ b/uno/lib/uno/ucb/hsqldb/Indexes.csv @@ -1,3 +1,3 @@ Index|Table|Column|Unique 0|0|1|true -1|1|9|false +1|1|8|false diff --git a/uno/lib/uno/ucb/hsqldb/TableColumn.csv b/uno/lib/uno/ucb/hsqldb/TableColumn.csv index da161574..57f2950a 100644 --- a/uno/lib/uno/ucb/hsqldb/TableColumn.csv +++ b/uno/lib/uno/ucb/hsqldb/TableColumn.csv @@ -4,40 +4,39 @@ Table|Column|TypeName|Type|Scale|IsNullable|DefaultValue|IsRowVersion|IsAutoIncr 0|2|VARCHAR|12|100|1|||| 0|3|VARCHAR|12|256|0|||| 0|4|VARCHAR|12|1000|0|''||| -0|5|SMALLINT|5||0|0|| +0|5|TIMESTAMP|93|6|0|CURRENT_TIMESTAMP(6)||| 0|6|TIMESTAMP|93|6|0|CURRENT_TIMESTAMP(6)||| -0|7|TIMESTAMP|93|6|0|CURRENT_TIMESTAMP(6)||| 0|20|TIMESTAMP WITH TIME ZONE|2014|6|0|CURRENT_TIMESTAMP(6)||| 1|0|VARCHAR|12|320|0|||| -1|8|VARCHAR|12|256|0||||true -1|9|VARCHAR|12|100|0|||| +1|7|VARCHAR|12|256|0||||true +1|8|VARCHAR|12|100|0|||| +1|5|TIMESTAMP|93|6|0|CURRENT_TIMESTAMP(6)||| 1|6|TIMESTAMP|93|6|0|CURRENT_TIMESTAMP(6)||| -1|7|TIMESTAMP|93|6|0|CURRENT_TIMESTAMP(6)||| -1|10|VARCHAR|12|100|0|'application/octet-stream'||| -1|11|BIGINT|-5||0|0||| -1|12|VARCHAR|12|256|0|''||| -1|13|BOOLEAN|16||0|FALSE||| -1|14|SMALLINT|5||0|0||| -1|5|SMALLINT|5||0|0||| +1|9|VARCHAR|12|100|0|'application/octet-stream'||| +1|10|BIGINT|-5||0|0||| +1|11|VARCHAR|12|256|0|''||| +1|12|BOOLEAN|16||0|FALSE||| +1|13|SMALLINT|5||0|0||| +1|19|SMALLINT|5||0|0||| 1|20|TIMESTAMP WITH TIME ZONE|2014|6|0|CURRENT_TIMESTAMP(6)||| 1|21|TIMESTAMP WITH TIME ZONE|2014|6|0||true|| 1|22|TIMESTAMP WITH TIME ZONE|2014|6|0||true|| 2|0|VARCHAR|12|320|0|||| -2|8|VARCHAR|12|256|0|||| -2|15|VARCHAR|12|256|0|||| -2|5|SMALLINT|5||0|0||| +2|7|VARCHAR|12|256|0|||| +2|14|VARCHAR|12|256|0|||| +2|19|SMALLINT|5||0|0||| 2|20|TIMESTAMP WITH TIME ZONE|2014|6|0|CURRENT_TIMESTAMP(6)||| 2|21|TIMESTAMP WITH TIME ZONE|2014|6|0||true|| 2|22|TIMESTAMP WITH TIME ZONE|2014|6|0||true|| 3|0|VARCHAR|12|320|0||||true -3|8|VARCHAR|12|256|0||||true +3|7|VARCHAR|12|256|0||||true +3|15|BOOLEAN|16||0|TRUE||| 3|16|BOOLEAN|16||0|TRUE||| -3|17|BOOLEAN|16||0|TRUE||| +3|17|BOOLEAN|16||0|FALSE||| 3|18|BOOLEAN|16||0|FALSE||| -3|19|BOOLEAN|16||0|FALSE||| 3|20|TIMESTAMP WITH TIME ZONE|2014|6|0|CURRENT_TIMESTAMP(6)||| 3|21|TIMESTAMP WITH TIME ZONE|2014|6|0||true|| 3|22|TIMESTAMP WITH TIME ZONE|2014|6|0||true|| 4|0|VARCHAR|12|320|0|||| -4|8|VARCHAR|12|256|0||||true +4|7|VARCHAR|12|256|0||||true 4|20|TIMESTAMP WITH TIME ZONE|2014|6|0|CURRENT_TIMESTAMP(6)||| diff --git a/uno/lib/uno/ucb/listener.py b/uno/lib/uno/ucb/listener.py new file mode 100644 index 00000000..a315816e --- /dev/null +++ b/uno/lib/uno/ucb/listener.py @@ -0,0 +1,51 @@ +#! +# -*- coding: utf-8 -*- + +""" +╔════════════════════════════════════════════════════════════════════════════════════╗ +β•‘ β•‘ +β•‘ Copyright (c) 2020-24 https://prrvchr.github.io β•‘ +β•‘ β•‘ +β•‘ Permission is hereby granted, free of charge, to any person obtaining β•‘ +β•‘ a copy of this software and associated documentation files (the "Software"), β•‘ +β•‘ to deal in the Software without restriction, including without limitation β•‘ +β•‘ the rights to use, copy, modify, merge, publish, distribute, sublicense, β•‘ +β•‘ and/or sell copies of the Software, and to permit persons to whom the Software β•‘ +β•‘ is furnished to do so, subject to the following conditions: β•‘ +β•‘ β•‘ +β•‘ The above copyright notice and this permission notice shall be included in β•‘ +β•‘ all copies or substantial portions of the Software. β•‘ +β•‘ β•‘ +β•‘ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, β•‘ +β•‘ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES β•‘ +β•‘ OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. β•‘ +β•‘ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY β•‘ +β•‘ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, β•‘ +β•‘ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE β•‘ +β•‘ OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. β•‘ +β•‘ β•‘ +β•šβ•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β• +""" + +import unohelper + +from com.sun.star.util import XCloseListener + +import traceback + + +class CloseListener(unohelper.Base, + XCloseListener): + def __init__(self, datasource): + self._datasource = datasource + + # XCloseListener + def queryClosing(self, source, ownership): + self._datasource.dispose() + + def notifyClosing(self, source): + pass + + def disposing(self, source): + pass + diff --git a/uno/lib/uno/ucb/replicator.py b/uno/lib/uno/ucb/replicator.py index 6c89fca9..d8813c64 100644 --- a/uno/lib/uno/ucb/replicator.py +++ b/uno/lib/uno/ucb/replicator.py @@ -4,7 +4,7 @@ """ ╔════════════════════════════════════════════════════════════════════════════════════╗ β•‘ β•‘ -β•‘ Copyright (c) 2020 https://prrvchr.github.io β•‘ +β•‘ Copyright (c) 2020-24 https://prrvchr.github.io β•‘ β•‘ β•‘ β•‘ Permission is hereby granted, free of charge, to any person obtaining β•‘ β•‘ a copy of this software and associated documentation files (the "Software"), β•‘ @@ -28,7 +28,6 @@ """ import uno -import unohelper from com.sun.star.logging.LogLevel import INFO from com.sun.star.logging.LogLevel import SEVERE @@ -42,13 +41,9 @@ from com.sun.star.ucb.ContentProperties import CONTENT from com.sun.star.ucb.ContentProperties import TRASHED -from com.sun.star.rest import HTTPException -from com.sun.star.rest.HTTPStatusCode import BAD_REQUEST - from .unotool import getConfiguration from .dbtool import currentDateTimeInTZ -from .dbtool import getDateTimeInTZToString from .dbtool import getDateTimeToString from .database import DataBase @@ -60,11 +55,8 @@ g_basename = 'Replicator' -from six import binary_type -from collections import OrderedDict from threading import Thread import traceback -import time class Replicator(Thread): @@ -80,11 +72,11 @@ def __init__(self, ctx, url, provider, users, sync, lock): self._provider = provider self._config = getConfiguration(ctx, g_identifier, False) self._logger = getLogger(ctx, g_synclog, g_basename) - self.DataBase = DataBase(ctx, self._logger, url) + self._database = DataBase(ctx, self._logger, url) sync.clear() self.start() except Exception as e: - self._logger.logprb(INFO, g_basename, '__init__()', 102, e, traceback.format_exc()) + self._logger.logprb(SEVERE, g_basename, '__init__()', 102, e, traceback.format_exc()) else: self._logger.logprb(INFO, g_basename, '__init__()', 101) @@ -95,6 +87,7 @@ def cancel(self): self._canceled = True self._sync.set() self.join() + self._database.dispose() def run(self): try: @@ -116,45 +109,33 @@ def _synchronize(self): elif self._provider.isOffLine(): self._logger.logprb(INFO, g_basename, '_synchronize()', 123) else: - users = self._users.values() - if policy == self._getSynchronizePolicy('SERVER_IS_MASTER'): - users = self._pullUsers(users) - if users: - self._pushUsers(users) - elif policy == self._getSynchronizePolicy('CLIENT_IS_MASTER'): - users = self._pushUsers(users) - if users: - self._pullUsers(users) + reset = self._getResetSetting() + for user in self._users.values(): + self._synchronizeUser(user, policy, reset) self._logger.logprb(INFO, g_basename, '_synchronize()', 124) - def _pullUsers(self, users): - try: - for user in users: - if self._canceled: - break - self._logger.logprb(INFO, g_basename, '_pullUsers()', 201, user.Name) - # In order to make the creation of files or directories possible quickly, - # it is necessary to run the verification of the identifiers first. - self._checkNewIdentifier(user) - if self._isNewUser(user): - self._initUser(user) - else: - self._pullUser(user) - self._logger.logprb(INFO, g_basename, '_pullUsers()', 202, user.Name) - else: - return users - return None - except Exception as e: - self._logger.logprb(SEVERE, g_basename, '_pullUsers()', 203, e, traceback.format_exc()) + def _synchronizeUser(self, user, policy, reset): + self._logger.logprb(INFO, g_basename, '_synchronizeUser()', 131, user.Name) + sync = False + if self._isNewUser(user) or reset: + sync = self._initUser(user) + elif policy == self._getSynchronizePolicy('SERVER_IS_MASTER'): + if self._pullUser(user): + sync = self._pushUser(user) + elif policy == self._getSynchronizePolicy('CLIENT_IS_MASTER'): + if self._pushUser(user): + sync = self._pullUser(user) + if sync: + self._logger.logprb(INFO, g_basename, '_synchronizeUser()', 132, user.Name) def _checkNewIdentifier(self, user): if not self._provider.GenerateIds: user.CanAddChild = True return if self._provider.isOffLine(): - user.CanAddChild = self.DataBase.countIdentifier(user.Id) > 0 + user.CanAddChild = self._database.countIdentifier(user.Id) > 0 return - count = self.DataBase.countIdentifier(user.Id) + count = self._database.countIdentifier(user.Id) if count < min(self._provider.IdentifierRange): total, msg = self._provider.pullNewIdentifiers(user) if total: @@ -165,78 +146,70 @@ def _checkNewIdentifier(self, user): user.CanAddChild = True def _initUser(self, user): - # This procedure is launched only once for each new user - # This procedure corresponds to the initial pull for a new User (ie: without Token) - self._logger.logprb(INFO, g_basename, '_initUser()', 221, user.Name) - pages, count, token = self._provider.firstPull(user) - print("Replicator._initUser() Pages: %s - Count: %s - Token : %s" % (pages, count, token)) - self._provider.initUser(self.DataBase, user, token) - user.releaseLock() - self._fullPull = True - self._logger.logprb(INFO, g_basename, '_initUser()', 222, user.Name) + try: + # This procedure is launched only once for each new user + # This procedure corresponds to the initial pull for a new User (ie: without Token) + self._logger.logprb(INFO, g_basename, '_initUser()', 221, user.Name) + # In order to make the creation of files or directories possible quickly, + # it is necessary to run the verification of the identifiers first. + self._checkNewIdentifier(user) + pages, count, token = self._provider.firstPull(user) + self._logger.logprb(INFO, g_basename, '_initUser()', 222, user.Name, count, pages, token) + self._provider.initUser(user, token) + user.releaseLock() + self._fullPull = True + self._logger.logprb(INFO, g_basename, '_initUser()', 223, user.Name) + return True + except Exception as e: + self._logger.logprb(SEVERE, g_basename, '_initUser()', 224, e, traceback.format_exc()) + return False def _pullUser(self, user): - # This procedure is launched each time the synchronization is started - # This procedure corresponds to the pull for a User (ie: a Token is required) - pages, count, token = self._provider.pullUser(user) - if token: - user.setToken(token) - self._logger.logprb(INFO, g_basename, '_pullUser()', 231, user.Name, count, pages, token) + try: + if self._canceled: + return False + self._logger.logprb(INFO, g_basename, '_pullUser()', 201, user.Name) + self._checkNewIdentifier(user) + pages, count, download, token = self._provider.pullUser(user) + self._logger.logprb(INFO, g_basename, '_pullUser()', 202, user.Name, count, download, pages, token) + if token: + user.Token = token + self._logger.logprb(INFO, g_basename, '_pullUser()', 203, user.Name) + return True + except Exception as e: + self._logger.logprb(SEVERE, g_basename, '_pullUser()', 204, e, traceback.format_exc()) + return False - def _pushUsers(self, users): - # This procedure is launched each time the synchronization is started + def _pushUser(self, user): # This procedure corresponds to the push of changes for the entire database - # for all users, in chronological order, from 'start' to 'end'... + # for a user, in chronological order, from 'start' to 'end'... try: - pusers = [] + if self._canceled: + return False + self._logger.logprb(INFO, g_basename, '_pushUsers()', 301, user.Name) + items = [] + start = user.TimeStamp end = currentDateTimeInTZ() - for user in users: + for item in self._database.getPushItems(user.Id, start, end): if self._canceled: break - self._logger.logprb(INFO, g_basename, '_pushUsers()', 301, user.Name) - if self._isNewUser(user): - self._initUser(user) - item = None - items = [] - start = user.TimeStamp - for item in self.DataBase.getPushItems(user.Id, start, end): - if self._canceled: - break - metadata = self.DataBase.getMetaData(user, item) - newid = self._pushItem(user, item, metadata, start, end) - if newid is None: - modified = getDateTimeToString(metadata.get('DateModified')) - self._logger.logprb(SEVERE, g_basename, '_pushUsers()', 302, metadata.get('Title'), modified, metadata.get('Id')) - break - else: - items.append(newid) - else: - # XXX: User was pushed, we update user timestamp if needed - self.DataBase.updatePushItems(user, items) - self._logger.logprb(INFO, g_basename, '_pushUsers()', 303, user.Name) - continue - break + metadata = self._database.getMetaData(user, item) + pushed = self._pushItem(user, item, metadata, start, end) + if pushed is None: + modified = getDateTimeToString(metadata.get('DateModified')) + self._logger.logprb(SEVERE, g_basename, '_pushUsers()', 302, metadata.get('Title'), modified, metadata.get('Id')) + break + items.append(pushed) else: - return users - return None + self._logger.logprb(INFO, g_basename, '_pushUsers()', 303, user.Name, len(items)) + # XXX: User was pushed, we update user timestamp if needed + self._database.updatePushItems(user, items) + self._logger.logprb(INFO, g_basename, '_pushUsers()', 304, user.Name) + return True + return False except Exception as e: - self._logger.logprb(SEVERE, g_basename, '_pushUsers()', 304, e, traceback.format_exc()) - - - def _filterParents(self, call, provider, items, childs, roots, start): - i = -1 - rows = [] - while len(childs) and len(childs) != i: - i = len(childs) - for item in childs: - itemid, parents = item - if all(parent in roots for parent in parents): - roots.append(itemid) - row = self.DataBase.setDriveCall(call, provider, items[itemid], itemid, parents, start) - rows.append(row) - childs.remove(item) - childs.reverse() - return rows + self._logger.logprb(SEVERE, g_basename, '_pushUsers()', 305, e, traceback.format_exc()) + return False def _pushItem(self, user, item, metadata, start, end): try: @@ -258,7 +231,7 @@ def _pushItem(self, user, item, metadata, start, end): self._logger.logprb(INFO, g_basename, '_pushItem()', *args) # UPDATE procedures, only a few properties are synchronized: Title and content(ie: Size or DateModified) elif action & UPDATE: - for property in self.DataBase.getPushProperties(user.Id, itemid, start, end): + for property in self._database.getPushProperties(user.Id, itemid, start, end): properties = property.get('Properties') timestamp = property.get('TimeStamp') modified = getDateTimeToString(metadata.get('DateModified')) @@ -273,7 +246,7 @@ def _pushItem(self, user, item, metadata, start, end): self._logger.logprb(INFO, g_basename, '_pushItem()', 313, metadata.get('Title'), modified) # MOVE procedures to follow parent changes of a resource elif action & MOVE: - self.DataBase.getItemParentIds(itemid, metadata, start, end) + self._database.getItemParentIds(itemid, metadata, start, end) newid = self._provider.updateParents(user.Request, itemid, metadata) elif action & DELETE: newid = self._provider.updateTrashed(user.Request, itemid, metadata) @@ -283,7 +256,7 @@ def _pushItem(self, user, item, metadata, start, end): self._logger.logprb(SEVERE, g_basename, '_pushItem()', 319, e, traceback.format_exc()) def _isNewUser(self, user): - return user.SyncMode == 0 + return len(user.Token) == 0 def _getReplicateTimeout(self): timeout = self._config.getByName('ReplicateTimeout') @@ -301,6 +274,16 @@ def _getSynchronizePolicy(self, policy): except: return uno.Enum('com.sun.star.ucb.SynchronizePolicy', policy) + def _getResetSetting(self): + reset = self._config.getByName('ResetSync') + if reset: + config = getConfiguration(self._ctx, g_identifier, True) + config.replaceByName('ResetSync', False) + if config.hasPendingChanges(): + config.commitChanges() + return reset + def _getUploadSetting(self): config = self._config.getByHierarchicalName('Settings/Upload') return config.getByName('Chunk'), config.getByName('Retry'), config.getByName('Delay') + diff --git a/uno/lib/uno/ucb/ucp/__init__.py b/uno/lib/uno/ucb/ucp/__init__.py index 58142a4d..520a6d47 100644 --- a/uno/lib/uno/ucb/ucp/__init__.py +++ b/uno/lib/uno/ucb/ucp/__init__.py @@ -4,7 +4,7 @@ """ ╔════════════════════════════════════════════════════════════════════════════════════╗ β•‘ β•‘ -β•‘ Copyright (c) 2020 https://prrvchr.github.io β•‘ +β•‘ Copyright (c) 2020-24 https://prrvchr.github.io β•‘ β•‘ β•‘ β•‘ Permission is hereby granted, free of charge, to any person obtaining β•‘ β•‘ a copy of this software and associated documentation files (the "Software"), β•‘ diff --git a/uno/lib/uno/ucb/ucp/configuration.py b/uno/lib/uno/ucb/ucp/configuration.py index d7840b44..355bd563 100644 --- a/uno/lib/uno/ucb/ucp/configuration.py +++ b/uno/lib/uno/ucb/ucp/configuration.py @@ -4,7 +4,7 @@ """ ╔════════════════════════════════════════════════════════════════════════════════════╗ β•‘ β•‘ -β•‘ Copyright (c) 2020 https://prrvchr.github.io β•‘ +β•‘ Copyright (c) 2020-24 https://prrvchr.github.io β•‘ β•‘ β•‘ β•‘ Permission is hereby granted, free of charge, to any person obtaining β•‘ β•‘ a copy of this software and associated documentation files (the "Software"), β•‘ diff --git a/uno/lib/uno/ucb/ucp/content.py b/uno/lib/uno/ucb/ucp/content.py index 43e3e2b5..7be4fa61 100644 --- a/uno/lib/uno/ucb/ucp/content.py +++ b/uno/lib/uno/ucb/ucp/content.py @@ -4,7 +4,7 @@ """ ╔════════════════════════════════════════════════════════════════════════════════════╗ β•‘ β•‘ -β•‘ Copyright (c) 2020 https://prrvchr.github.io β•‘ +β•‘ Copyright (c) 2020-24 https://prrvchr.github.io β•‘ β•‘ β•‘ β•‘ Permission is hereby granted, free of charge, to any person obtaining β•‘ β•‘ a copy of this software and associated documentation files (the "Software"), β•‘ @@ -106,7 +106,7 @@ def __init__(self, ctx, user, authority, data, new=False): self._ctx = ctx self._user = user self._authority = authority - self.MetaData = data + self._metadata = data self._new = new self._url = None self._listeners = [] @@ -121,51 +121,51 @@ def __init__(self, ctx, user, authority, data, new=False): @property def IsFolder(self): - return self.MetaData.get('IsFolder') + return self._metadata.get('IsFolder') @property def IsDocument(self): - return self.MetaData.get('IsDocument') + return self._metadata.get('IsDocument') @property def IsRoot(self): - return self.MetaData.get('IsRoot') + return self._metadata.get('IsRoot') @property def IsRenamed(self): - return self.MetaData.get('Name') != self.Title + return self._metadata.get('Name') != self.Title @property def CanRename(self): - return self.MetaData.get('CanRename') and not self.IsRenamed + return self._metadata.get('CanRename') and not self.IsRenamed @property def CanAddChild(self): - return self.MetaData.get('CanAddChild') + return self._metadata.get('CanAddChild') @property def Id(self): - return self.MetaData.get('Id') + return self._metadata.get('Id') @property def ParentId(self): - return self.MetaData.get('ParentId') + return self._metadata.get('ParentId') @property def Path(self): - return self.MetaData.get('Path') + return self._metadata.get('Path') @property def Size(self): - return self.MetaData.get('Size') + return self._metadata.get('Size') @property def Link(self): - return self.MetaData.get('Link') + return self._metadata.get('Link') @property def Name(self): - return self.MetaData.get('Name') + return self._metadata.get('Name') @property def Title(self): - return self.MetaData.get('Title') + return self._metadata.get('Title') @property def MediaType(self): - return self.MetaData.get('MediaType', 'application/octet-stream') + return self._metadata.get('MediaType', 'application/octet-stream') @property def ConnectionMode(self): - return self.MetaData.get('ConnectionMode') + return self._metadata.get('ConnectionMode') def setConnectionMode(self, mode): - self.MetaData['ConnectionMode'] = mode + self._metadata['ConnectionMode'] = mode @property def User(self): @@ -234,7 +234,7 @@ def getIdentifier(self): self._logger.logprb(SEVERE, 'Content', 'getIdentifier()', 642, e, traceback.format_exc()) def getContentType(self): - return self.MetaData.get('ContentType') + return self._metadata.get('ContentType') def addContentEventListener(self, listener): self._contentListeners.append(listener) @@ -263,7 +263,7 @@ def execute(self, command, cmdid, environment): return self._setPropertiesValues(environment, command.Argument) elif command.Name == 'delete': - self.MetaData['Trashed'] = True + self._metadata['Trashed'] = True self._user.updateContent(self.Id, 'Trashed', True) elif command.Name == 'open': @@ -278,7 +278,7 @@ def execute(self, command, cmdid, environment): msg = self._logger.resolveString(632, self._identifier) raise CommandAbortedException(msg, self) input = command.Argument.Sink - isreadonly = self.MetaData.get('IsReadOnly') + isreadonly = self._metadata.get('IsReadOnly') sink = hasInterface(input, 'com.sun.star.io.XActiveDataSink') stream = hasInterface(input, 'com.sun.star.io.XActiveDataStreamer') if not isreadonly and stream: @@ -307,9 +307,9 @@ def execute(self, command, cmdid, environment): # For document type resources, the media type is always unknown... mediatype = mimetype if mimetype else getMimeType(self._ctx, stream) stream.closeInput() - self.MetaData['MediaType'] = mediatype + self._metadata['MediaType'] = mediatype - if self._user.insertNewContent(self._authority, self.MetaData): + if self._user.insertNewContent(self._authority, self._metadata): # Need to consum the new Identifier if needed... self._user.deleteNewIdentifier(self.Id) @@ -397,8 +397,8 @@ def _getPropertyValue(self, name): value = '' elif name == 'CreatableContentsInfo': value = self._getCreatableContentsInfo() - elif name in self.MetaData: - value = self.MetaData.get(name) + elif name in self._metadata: + value = self._metadata.get(name) msg = "Name: %s - Value: %s" % (name, value) else: msg = "ERROR: Requested property: %s is not available" % name @@ -444,8 +444,8 @@ def _setProperty(self, environment, name, value): msg = "" level = INFO result = None - elif name in self.MetaData: - self.MetaData[name] = value + elif name in self._metadata: + self._metadata[name] = value msg = "Content: %s set property: %s value: %s" % (self._identifier, name, value) level = INFO result = None @@ -463,7 +463,7 @@ def _setTitle(self, environment, newtitle): level = SEVERE error = IllegalAccessException(msg, self) result = uno.Any('com.sun.star.lang.IllegalAccessException', error) - elif self.Title != self.MetaData.get('Name'): + elif self.Title != self._metadata.get('Name'): msg = "ERROR: Requested property: Title can't be changed" level = SEVERE error = IllegalAccessException(msg, self) @@ -474,8 +474,8 @@ def _setTitle(self, environment, newtitle): error = IllegalAccessException(msg, self) result = uno.Any('com.sun.star.lang.IllegalAccessException', error) else: - self.MetaData['Name'] = newtitle - self.MetaData['Title'] = newtitle + self._metadata['Name'] = newtitle + self._metadata['Title'] = newtitle # If the identifier is new then the content is not yet in the database. # It will be inserted by the insert command of the XCommandProcessor2.execute() # But we must make this content accessible by an appropriate entry in the user paths cache @@ -506,7 +506,7 @@ def _getDocumentContent(self, sf): url = self._user.Provider.getTargetUrl(self.Id) if self.ConnectionMode == OFFLINE and sf.exists(url): return url, sf.getSize(url) - if self._user.Provider.getDocumentContent(self, url): + if self._user.Provider.downloadFile(self._user, self._metadata, url): loaded = self._user.updateConnectionMode(self.Id, OFFLINE) self.setConnectionMode(loaded) else: diff --git a/uno/lib/uno/ucb/ucp/contenthelper.py b/uno/lib/uno/ucb/ucp/contenthelper.py index c6691d4f..707e7c16 100644 --- a/uno/lib/uno/ucb/ucp/contenthelper.py +++ b/uno/lib/uno/ucb/ucp/contenthelper.py @@ -4,7 +4,7 @@ """ ╔════════════════════════════════════════════════════════════════════════════════════╗ β•‘ β•‘ -β•‘ Copyright (c) 2020 https://prrvchr.github.io β•‘ +β•‘ Copyright (c) 2020-24 https://prrvchr.github.io β•‘ β•‘ β•‘ β•‘ Permission is hereby granted, free of charge, to any person obtaining β•‘ β•‘ a copy of this software and associated documentation files (the "Software"), β•‘ diff --git a/uno/lib/uno/ucb/ucp/contentlib.py b/uno/lib/uno/ucb/ucp/contentlib.py index 7cb47cd9..8a1d52b5 100644 --- a/uno/lib/uno/ucb/ucp/contentlib.py +++ b/uno/lib/uno/ucb/ucp/contentlib.py @@ -4,7 +4,7 @@ """ ╔════════════════════════════════════════════════════════════════════════════════════╗ β•‘ β•‘ -β•‘ Copyright (c) 2020 https://prrvchr.github.io β•‘ +β•‘ Copyright (c) 2020-24 https://prrvchr.github.io β•‘ β•‘ β•‘ β•‘ Permission is hereby granted, free of charge, to any person obtaining β•‘ β•‘ a copy of this software and associated documentation files (the "Software"), β•‘ diff --git a/uno/lib/uno/ucb/ucp/contentlistener.py b/uno/lib/uno/ucb/ucp/contentlistener.py index 3445612d..dcd90db2 100644 --- a/uno/lib/uno/ucb/ucp/contentlistener.py +++ b/uno/lib/uno/ucb/ucp/contentlistener.py @@ -4,7 +4,7 @@ """ ╔════════════════════════════════════════════════════════════════════════════════════╗ β•‘ β•‘ -β•‘ Copyright (c) 2020 https://prrvchr.github.io β•‘ +β•‘ Copyright (c) 2020-24 https://prrvchr.github.io β•‘ β•‘ β•‘ β•‘ Permission is hereby granted, free of charge, to any person obtaining β•‘ β•‘ a copy of this software and associated documentation files (the "Software"), β•‘ diff --git a/uno/lib/uno/ucb/ucp/identifier.py b/uno/lib/uno/ucb/ucp/identifier.py index 49d167fc..c72a1489 100644 --- a/uno/lib/uno/ucb/ucp/identifier.py +++ b/uno/lib/uno/ucb/ucp/identifier.py @@ -4,7 +4,7 @@ """ ╔════════════════════════════════════════════════════════════════════════════════════╗ β•‘ β•‘ -β•‘ Copyright (c) 2020 https://prrvchr.github.io β•‘ +β•‘ Copyright (c) 2020-24 https://prrvchr.github.io β•‘ β•‘ β•‘ β•‘ Permission is hereby granted, free of charge, to any person obtaining β•‘ β•‘ a copy of this software and associated documentation files (the "Software"), β•‘ diff --git a/uno/lib/uno/ucb/ucp/provider.py b/uno/lib/uno/ucb/ucp/provider.py index f446236d..ba4bdfbe 100644 --- a/uno/lib/uno/ucb/ucp/provider.py +++ b/uno/lib/uno/ucb/ucp/provider.py @@ -4,7 +4,7 @@ """ ╔════════════════════════════════════════════════════════════════════════════════════╗ β•‘ β•‘ -β•‘ Copyright (c) 2020 https://prrvchr.github.io β•‘ +β•‘ Copyright (c) 2020-24 https://prrvchr.github.io β•‘ β•‘ β•‘ β•‘ Permission is hereby granted, free of charge, to any person obtaining β•‘ β•‘ a copy of this software and associated documentation files (the "Software"), β•‘ @@ -30,6 +30,7 @@ import uno import unohelper +from com.sun.star.logging.LogLevel import INFO from com.sun.star.logging.LogLevel import SEVERE from com.sun.star.ucb.ConnectionMode import OFFLINE @@ -65,34 +66,66 @@ import traceback -class Provider(object): +class Provider(): def __init__(self, ctx, logger): self._ctx = ctx self._logger = logger - self.Scheme = g_scheme self._sf = getSimpleFile(ctx) - self.SourceURL = getResourceLocation(ctx, g_identifier, g_scheme) + self._url = getResourceLocation(ctx, g_identifier, g_scheme) self._folders = [] self._config = getConfiguration(ctx, g_identifier, False) + self._logger.logprb(INFO, 'Provider', '__init__()', 551) + + @property + def Scheme(self): + return g_scheme # Must be implemented properties @property - def Name(self): + def BaseUrl(self): raise NotImplementedError @property def Host(self): raise NotImplementedError @property - def BaseUrl(self): + def Name(self): raise NotImplementedError @property def UploadUrl(self): raise NotImplementedError - @property - def Chunk(self): + + # Must be implemented method + def getDocumentLocation(self, user, item): raise NotImplementedError - @property - def Buffer(self): + + def getFirstPullRoots(self, user): + raise NotImplementedError + + def getRequestParameter(self, request, method, data): + raise NotImplementedError + + def getUser(self, source, request, name): + raise NotImplementedError + + def mergeNewFolder(self, user, itemid, response): + raise NotImplementedError + + def parseFolder(self, parameter, content): + raise NotImplementedError + + def parseItems(self, request, parameter, rootid): + raise NotImplementedError + + def parseNewIdentifiers(self, response): + raise NotImplementedError + + def parseUploadLocation(self, user): + raise NotImplementedError + + def parseUserToken(self, user): + raise NotImplementedError + + def updateItemId(self, user, item, response): raise NotImplementedError # Can be rewrited properties @@ -103,46 +136,49 @@ def IdentifierRange(self): def GenerateIds(self): return all(self.IdentifierRange) @property - def FileSyncModes(self): - return (SYNC_FILE, ) - @property - def FolderSyncModes(self): - return (SYNC_FOLDER, ) - @property def SupportSharedDocuments(self): return self._config.getByName('SupportShare') and self._config.getByName('SharedDocuments') @property def SharedFolderName(self): return self._config.getByName('SharedFolderName') + # Can be rewrited method + def initUser(self, user, token): + user.Token = token + + def initSharedDocuments(self, user, datetime): + # You must implement this method in Provider to be able to handle Shared Documents + pass + + def pullUser(self, user): + count = download = 0 + timestamp = currentDateTimeInTZ() + parameter = self.getRequestParameter(user.Request, 'getPull', user) + for item in self.parseItems(user.Request, parameter, user.RootId): + count += user.DataBase.pullItem(user.Id, item, timestamp) + download += self.pullFileContent(user, item) + return parameter.PageCount, count, download, parameter.SyncToken + # Method called by Content def updateFolderContent(self, content): timestamp = currentDateTimeInTZ() parameter = self.getRequestParameter(content.User.Request, 'getFolderContent', content) - iterator = self.parseRootFolder(parameter, content) + iterator = self.parseFolder(parameter, content) count = content.User.DataBase.pullItems(iterator, content.User.Id, timestamp) return count - def getDocumentContent(self, content, url): - data = self.getDocumentLocation(content) - if data is None: - return False - parameter = self.getRequestParameter(content.User.Request, 'getDocumentContent', data) - chunk, retry, delay = self._getDownloadSetting() - return content.User.Request.download(parameter, url, chunk, retry, delay) + def downloadFile(self, user, item, url): + data = self.getDocumentLocation(user, item) + if data is not None: + parameter = self.getRequestParameter(user.Request, 'downloadFile', data) + return user.Request.download(parameter, url, *self._getDownloadSetting()) + return False # Method called by Replicator - def pullNewIdentifiers(self, user): - count, msg = 0, '' - parameter = self.getRequestParameter(user.Request, 'getNewIdentifier', user) + def createFolder(self, user, itemid, item): + parameter = self.getRequestParameter(user.Request, 'createNewFolder', item) response = user.Request.execute(parameter) - if not response.Ok: - msg = response.Text - else: - iterator = self.parseNewIdentifiers(response) - count = user.DataBase.insertIdentifier(iterator, user.Id) - response.close() - return count, msg + return self.mergeNewFolder(user, itemid, response) def firstPull(self, user): datetime = currentDateTimeInTZ() @@ -156,98 +192,58 @@ def firstPull(self, user): page += parameter.PageCount return page, count, parameter.SyncToken - def initSharedDocuments(self, user, datetime): - # You must implement this method in Provider to be able to handle Shared Documents - pass - - def pullUser(self, user): - timestamp = currentDateTimeInTZ() - parameter = self.getRequestParameter(user.Request, 'getPull', user) - iterator = self.parseItems(user.Request, parameter, user.RootId) - count = user.DataBase.pullItems(iterator, user.Id, timestamp) - return parameter.PageCount, count, parameter.SyncToken - - def getUserToken(self, user): - parameter = self.getRequestParameter(user.Request, 'getToken', user) + def pullNewIdentifiers(self, user): + count, msg = 0, '' + parameter = self.getRequestParameter(user.Request, 'getNewIdentifier', user) response = user.Request.execute(parameter) if not response.Ok: - pass - token = self.parseUserToken(response) + msg = response.Text + else: + iterator = self.parseNewIdentifiers(response) + count = user.DataBase.insertIdentifier(iterator, user.Id) response.close() - return token - - def _getRejectedItems(self, items): - rejected = [] - for item in items: - itemid = self._getItemId(item) - title = self._getItemTitle(item) - parents = self._getItemParents(item) - rejected.append((title, itemid, ','.join(parents))) - return rejected - - def _isValidItem(self, item, roots, orphans): - itemid = self._getItemId(item) - parents = self._getItemParents(item) - if not all(parent in roots for parent in parents): - orphans[itemid] = item - return False - roots.append(itemid) - return True - - def _validItem(self, item, roots, orphans): - return True - - def _getItemId(self, item): - return item[0] - def _getItemTitle(self, item): - return item[1] - def _getItemParents(self, item): - return item[11] - - # Must be implemented method - def getRequestParameter(self, request, method, data): - raise NotImplementedError - - def getUser(self, source, request, name): - raise NotImplementedError - - def parseNewIdentifiers(self, response): - raise NotImplementedError - - def parseItems(self, request, parameter, rootid): - raise NotImplementedError - - def parseChanges(self, user, parameter): - raise NotImplementedError - - def parseUserToken(self, user): - raise NotImplementedError + return count, msg - def getFirstPullRoots(self, user): - raise NotImplementedError + def updateName(self, request, itemid, item): + parameter = self.getRequestParameter(request, 'updateName', item) + response = request.execute(parameter) + response.close() + return itemid - def parseUploadLocation(self, user): - raise NotImplementedError + def updateParents(self, request, itemid, item): + parameter = self.getRequestParameter(request, 'updateParents', item) + response = request.execute(parameter) + response.close() + return itemid - def getDocumentLocation(self, user): - raise NotImplementedError + def updateTrashed(self, request, itemid, item): + parameter = self.getRequestParameter(request, 'updateTrashed', item) + response = request.execute(parameter) + response.close() + return itemid - def mergeNewFolder(self, user, itemid, response): - raise NotImplementedError + # Base method + def getSimpleFile(self): + return self._sf - def createNewFile(self, user, data): - raise NotImplementedError + def getTargetUrl(self, itemid): + return self._url + g_ucbseparator + itemid - def parseRootFolder(self, parameter, content): - raise NotImplementedError + def getUserToken(self, user): + token = '' + parameter = self.getRequestParameter(user.Request, 'getToken', user) + response = user.Request.execute(parameter) + if response.Ok: + token = self.parseUserToken(response) + response.close() + return token - def updateItemId(self, database, item, response): - raise NotImplementedError + def isOffLine(self): + return ONLINE != getConnectionMode(self._ctx, self.Host) - def initUser(self, database, user, token): - pass + def isOnLine(self): + return OFFLINE != getConnectionMode(self._ctx, self.Host) - # Base method def parseDateTime(self, timestamp): datetime = uno.createUnoStruct('com.sun.star.util.DateTime') try: @@ -265,15 +261,11 @@ def parseDateTime(self, timestamp): datetime.IsUTC = dt.tzinfo == tz.tzutc() return datetime - def isOnLine(self): - return OFFLINE != getConnectionMode(self._ctx, self.Host) - - def isOffLine(self): - return ONLINE != getConnectionMode(self._ctx, self.Host) - - def getItem(self, request, identifier): - parameter = self.getRequestParameter(request, 'getItem', identifier) - return request.execute(parameter) + def pullFileContent(self, user, item): + url = self.getTargetUrl(item.get('Id')) + if self.getSimpleFile().exists(url): + return self.downloadFile(user, item, url) + return False def updateNewItemId(self, oldid, newid): source = self.getTargetUrl(oldid) @@ -281,17 +273,6 @@ def updateNewItemId(self, oldid, newid): if self._sf.exists(source) and not self._sf.exists(target): self._sf.move(source, target) - def getSimpleFile(self): - return self._sf - - def getTargetUrl(self, itemid): - return self.SourceURL + g_ucbseparator + itemid - - def createFolder(self, user, itemid, item): - parameter = self.getRequestParameter(user.Request, 'createNewFolder', item) - response = user.Request.execute(parameter) - return self.mergeNewFolder(user, itemid, response) - def uploadFile(self, code, user, item, data, created, chunk, retry, delay, new=False): newid = None method = 'getNewUploadLocation' if new else 'getUploadLocation' @@ -312,7 +293,7 @@ def uploadFile(self, code, user, item, data, created, chunk, retry, delay, new=F args = code + 2, parameter.Name, data.get('Name'), response.Text response.close() elif new: - newid = self.updateItemId(user.DataBase, item, response) + newid = self.updateItemId(user, item, response) args = code + 3, data.get('Name'), created, data.get('Size') else: response.close() @@ -320,24 +301,8 @@ def uploadFile(self, code, user, item, data, created, chunk, retry, delay, new=F args = code + 4, data.get('Name'), created, data.get('Size') return newid, args - def updateName(self, request, itemid, item): - parameter = self.getRequestParameter(request, 'updateName', item) - response = request.execute(parameter) - response.close() - return itemid - - def updateTrashed(self, request, itemid, item): - parameter = self.getRequestParameter(request, 'updateTrashed', item) - response = request.execute(parameter) - response.close() - return itemid - - def updateParents(self, request, itemid, item): - parameter = self.getRequestParameter(request, 'updateParents', item) - response = request.execute(parameter) - response.close() - return itemid - + # Private method def _getDownloadSetting(self): config = self._config.getByHierarchicalName('Settings/Download') return config.getByName('Chunk'), config.getByName('Retry'), config.getByName('Delay') + diff --git a/uno/lib/uno/ucb/ucp/user.py b/uno/lib/uno/ucb/ucp/user.py index 946b9197..5bfb5a2c 100644 --- a/uno/lib/uno/ucb/ucp/user.py +++ b/uno/lib/uno/ucb/ucp/user.py @@ -4,7 +4,7 @@ """ ╔════════════════════════════════════════════════════════════════════════════════════╗ β•‘ β•‘ -β•‘ Copyright (c) 2020 https://prrvchr.github.io β•‘ +β•‘ Copyright (c) 2020-24 https://prrvchr.github.io β•‘ β•‘ β•‘ β•‘ Permission is hereby granted, free of charge, to any person obtaining β•‘ β•‘ a copy of this software and associated documentation files (the "Software"), β•‘ @@ -111,12 +111,12 @@ def __init__(self, ctx, source, logger, database, provider, sync, name, password if not database.createUser(name, password): msg = self._getExceptionMessage(method, 507, name) raise IllegalIdentifierException(msg, source) - self.Request = request - self.MetaData = metadata - self.DataBase = DataBase(ctx, logger, database.Url, name, password) self._paths = {} self._contents = {} self._lock = None + self._metadata = metadata + self.Request = request + self.DataBase = DataBase(ctx, logger, database.Url, name, password) if new: # Start Replicator for pushing changes… self._lock = Event() @@ -125,45 +125,42 @@ def __init__(self, ctx, source, logger, database, provider, sync, name, password @property def Name(self): - return self.MetaData.get('UserName') + return self._metadata.get('UserName') @property def Id(self): - return self.MetaData.get('UserId') + return self._metadata.get('UserId') @property def RootId(self): - return self.MetaData.get('RootId') + return self._metadata.get('RootId') @property def Token(self): - return self.MetaData.get('Token') - @property - def SyncMode(self): - return self.MetaData.get('SyncMode') - @SyncMode.setter - def SyncMode(self, mode): - self.MetaData['SyncMode'] = mode - self.DataBase.updateUserSyncMode(self.Id, mode) + return self._metadata.get('Token') + @Token.setter + def Token(self, token): + self._metadata['Token'] = token + self.DataBase.updateToken(self.Id, token) @property def SessionMode(self): return self.Request.getSessionMode(self.Provider.Host) @property def DateCreated(self): - return self.MetaData.get('DateCreated') + return self._metadata.get('DateCreated') @property def DateModified(self): - return self.MetaData.get('DateModified') + return self._metadata.get('DateModified') @property def TimeStamp(self): - return self.MetaData.get('TimeStamp') + return self._metadata.get('TimeStamp') @TimeStamp.setter def TimeStamp(self, timestamp): - self.MetaData['TimeStamp'] = timestamp + self._metadata['TimeStamp'] = timestamp - def setToken(self, token): - self.MetaData['Token'] = token + # method called from DataSource + def dispose(self): + self.DataBase.dispose() # method called from Replicator def releaseLock(self): - self.SyncMode = 1 if self._lock is not None and not self._lock.is_set(): self._lock.set() @@ -183,7 +180,7 @@ def getContent(self, authority, uri): return self._getContent(authority, uri.getPath(), isroot) def setLock(self): - if self._lock is not None and not self.SyncMode: + if self._lock is not None: self._lock.wait() # method called from Content._identifier diff --git a/uno/resource/ucb/ContentProvider_en_US.properties b/uno/resource/ucb/ContentProvider_en_US.properties index 634551de..bdbfe621 100644 --- a/uno/resource/ucb/ContentProvider_en_US.properties +++ b/uno/resource/ucb/ContentProvider_en_US.properties @@ -1,9 +1,9 @@ 100=ContentProvider.__init__() -101=ContentProvider: {} Loading completed +101=ContentProvider <{}> Loading completed 140=OptionsHandler.callHandlerMethod() -141=An unexpected exception: {} has occurred, producing a series of exceptions: {} +141=An unexpected exception <{}> has occurred, producing a series of exceptions: {} 150=OptionsManager.__init__() 151=OptionManger has been correctly initialized. @@ -12,111 +12,123 @@ 161=OptionManger has been correctly reloaded. 170=OptionsManager.saveSetting() -171=OptionManger saved options {} and logging {}. +171=OptionManger saved options <{}> and logging <{}>. 200=ContentProvider.__init__() 201=ParameterizedProvider{} Loading completed 210=ContentProvider.createContentIdentifier() -211=Identifier: {} -> {} +211=Identifier: <{}> -> <{}> 220=ContentProvider._getDataSource() 221={} - Installation error -222=The extension {} was not found. You must install this extension to use {}. +222=The extension <{}> was not found. You must install this extension to use <{}>. 223={} - Configuration error -224=Version {} of extension {} is obsolete. You need to update this extension to version: {}. +224=Version <{}> of extension <{}> is obsolete. You need to update this extension to version <{}>. 225={} - Connection error -226=Unable to connect to URL: {}. An SQLException was thrown with the message: {} +226=Unable to connect to URL <{}>. An SQLException was thrown with the message: {} 227={} - Configuration error -228=HsqlDB driver version {} is obsolete.You need to update this driver to version {} or higher. +228=HsqlDB driver version <{}> is obsolete. You need to update this driver to version <{}> or higher. 230=ContentProvider.queryContent() -231=Identitifer: {} ... Completed +231=Identitifer <{}> ... Completed 232=IllegalIdentifierException: {} 233=Unexpected python exception with traceback: {} 240=ContentProvider.compareContentIds() -241=Identifiers: {} - {} ... seem to be the same -242=Identifiers: {} - {} ... doesn't seem to be the same +241=Identifiers <{}> - <{}> ... seem to be the same +242=Identifiers <{}> - <{}> ... doesn't seem to be the same 300=DataSource.__init__() 301=DataSource: Loading completed 310=DataSource.queryContent() -311=Error: Unable to load content with an incomplete Identifier: {} +311=Error: Unable to load content with an incomplete Identifier <{}> 320=DataSource._getUser() -321=Error: Unable to load content with an invalid Identifier: {} +321=Error: Unable to load content with an invalid Identifier <{}> 322={} - Identifier error -323=Unable to retrieve User from Identifier: {}!!! +323=Unable to retrieve User from Identifier <{}>!!! 324={} - Authentication error -325=The OAuth2 Wizard was aborted by user: {}. The connection cannot be established!!! +325=The OAuth2 Wizard was aborted by user <{}>. The connection cannot be established!!! 330=DataSource._getUserName() 331={} - Authentication error -332=Unable to load content without User: {}!!! +332=Unable to load content without User <{}>!!! -340=DataSource.queryClosing() -341=The database: {} has been closed... +340=DataSource.dispose() +341=The database <{}> has been closed... +342=Unexpected python exception <{}> with traceback: {} 400=DataBase.__init__() -401=DataBase: Loading completed -402=DataBase._mergeItem() size {} os name {} +401=DataBase: Loading completed. +402=DataBase._mergeItem() size <{}> os name <{}> 410=DataBase.createDataBase() -411=Try to create the database with HsqlDB version: {} -412=Successful database creation -413=Unable to create database: installed HsqlDB version: {} is lower than minimum supported version: {} +411=Try to create the database with HsqlDB version <{}>. +412=Successful database creation. +413=Unable to create database: installed HsqlDB version <{}> is lower than minimum supported version <{}>. 500=User.__init__() 501={} - Authentication error -502=The OAuth2 Wizard was aborted by user: {}. The connection cannot be established!!! +502=The OAuth2 Wizard was aborted by user <{}>. The connection cannot be established!!! 503={} - Network error -504=Can't retrieve User: {} from provider: network is OffLine!!! +504=Can't retrieve User <{}> from provider: network is OffLine!!! 505={} - Authentication error -506=User: {} does not exist at this Provider!!! +506=User <{}> does not exist at this Provider!!! 507={} - Database error -508=Can't insert User: {} in DataBase -509=User loading completed +508=Can't insert User <{}> in DataBase. +509=User loading completed. 510=User.getDocumentContent() -511=Error: {} - {} +511=Error <{}> - {} + + +550=Provider.__init__() +551=Provider loading completed. + +560=Provider._getUser() +561=Error on request <{}> which returned code <{}> and response <{}>. + +570=Provider._getRoot() +571=Error on request <{}> which returned code <{}> and response <{}>. 600=Content.__init__() -601=Content loading completed +601=Content loading completed. 610=Content._getMetaData() -611=Error: Unable to load content with an incomplete Identifier: {} -612=Error: Unable to retrieve metadata for ID: {} and Uri: {} +611=Error: Unable to load content with an incomplete Identifier <{}>. +612=Error: Unable to retrieve metadata for ID <{}> and Uri <{}>. 620=Content._updateFolderContent() -621=The content of the folder: {} is obtained with Request!!! +621=The content of the folder <{}> is obtained with Request!!! 630=Content.execute() -631=The content execute command: {} on identifier: {} -632=Error while downloading file: {} -633=Couldn't handle transfert on identifier: {}, only Folder can handle transfert -634=Couln't handle Url: {} on identifier: {} -635=Save file: {} size: {} +631=The content execute command <{}> on identifier <{}>. +632=Error while downloading file <{}>. +633=Couldn't handle transfert on identifier <{}>, only Folder can handle transfert. +634=Couln't handle Url <{}> on identifier <{}>. +635=Save file <{}> size <{}>. 640=Content.getIdentifier() -641=Content identifier: {} -642=Error: {} - {} +641=Content identifier <{}>. +642=Error <{}> - {} 650=Content.getParent() -651=Error: {} - {} +651=Error <{}> - {} 660=Content.createNewContent() -661=Create new content from folder: {} +661=Create new content from folder <{}>. + 710=PropertySetInfo.getPropertyByName() -711=The content gives the property: {} +711=The content gives the property <{}>. 720=PropertySetInfo.hasPropertyByName() -721=The content has the property: {} - {} +721=The content has the property <{}> - <{}>. diff --git a/uno/resource/ucb/ContentProvider_fr_FR.properties b/uno/resource/ucb/ContentProvider_fr_FR.properties index ce7a598a..c2f31b70 100644 --- a/uno/resource/ucb/ContentProvider_fr_FR.properties +++ b/uno/resource/ucb/ContentProvider_fr_FR.properties @@ -1,9 +1,9 @@ 100=ContentProvider.__init__() -101=ContentProvider: {} Chargement termin\u00e9 +101=ContentProvider <{}> Chargement terminι 140=OptionsHandler.callHandlerMethod() -141=Une exception: {} inattendue s'est produite, produisant une sιrie d'exceptions: {} +141=Une exception <{}> inattendue s'est produite, produisant une sιrie d'exceptions: {} 150=OptionsManager.__init__() 151=OptionManger a ιtι correctement initialisι. @@ -12,111 +12,123 @@ 161=OptionManger a ιtι correctement rechargι. 170=OptionsManager.saveSetting() -171=OptionManger a sauvegardι des options {} et la journalisation {}. +171=OptionManger a sauvegardι des options <{}> et la journalisation <{}>. 200=ContentProvider.__init__() -201=ParameterizedProvider{} Chargement termin\u00e9 +201=ParameterizedProvider{} Chargement terminι 210=ContentProvider.createContentIdentifier() -211=Identifier: {} -> {} +211=Identifier: <{}> -> <{}> 220=ContentProvider._getDataSource() 221={} - Erreur d'installation -222=L'extension {} est introuvable.Vous devez installer cette extension pour utiliser {}. +222=L'extension <{}> est introuvable.Vous devez installer cette extension pour utiliser <{}>. 223={} - Erreur de configuration -224=La version {} de l'extension {} est obsolθte.Vous devez mettre ΰ jour cette extension vers la version: {}. +224=La version <{}> de l'extension <{}> est obsolθte.Vous devez mettre ΰ jour cette extension vers la version: <{}>. 225={} - Erreur de connection -226=Impossible de se connecter sur l'URL: {}. Une SQLException a ιtι levιe avec le message: {} +226=Impossible de se connecter sur l'URL <{}>. Une SQLException a ιtι levιe avec le message: {} 227={} - Erreur de configuration -228=La version {} du pilote HsqlDB est obsolθte. Vous devez mettre ΰ jour ce pilote vers une version {} ou supιrieure. +228=La version <{}> du pilote HsqlDB est obsolθte. Vous devez mettre ΰ jour ce pilote vers une version <{}> ou supιrieure. 230=ContentProvider.queryContent() -231=Identitifer: {} ... Termin\u00e9 +231=Identitifer <{}> ... Terminι 232=IllegalIdentifierException: {} 233=Exception python inattendue avec le traceback: {} 240=ContentProvider.compareContentIds() -241=Identifiers: {} - {} ... semble \u00eatre le m\u00eame -242=Identifiers: {} - {} ... ne semble pas \u00eatre le m\u00eame +241=Identifiers <{}> - <{}> ... semble κtre le mκme +242=Identifiers <{}> - <{}> ... ne semble pas κtre le mκme 300=DataSource.__init__() -301=DataSource: Chargement termin\u00e9 +301=DataSource: Chargement terminι. 310=DataSource.queryContent() -311=Erreur: Impossible de charger le contenu avec un Identifier incomplet: {} +311=Erreur: Impossible de charger le contenu avec un Identifier incomplet <{}>. 320=DataSource._getUser() -321=Erreur: Impossible de charger de contenu avec un Identifier invalide: {} +321=Erreur: Impossible de charger de contenu avec un Identifier invalide <{}>. 322={} - Erreur d'identificateur -323=Impossible de r\u00e9cup\u00e9rer l'utilisateur ΰ partir de l'Identifier: {}!!! +323=Impossible de rιcupιrer l'utilisateur ΰ partir de l'Identifier <{}>!!! 324={} - Erreur d'authentification -325=L'assistant OAuth2 a ιtι abandonnι par l'utilisateur: {}. La connexion ne peut pas κtre ιtablie!!! +325=L'assistant OAuth2 a ιtι abandonnι par l'utilisateur <{}>. La connexion ne peut pas κtre ιtablie!!! 330=DataSource._getUserName() 331={} - Erreur d'authentification -332=Impossible de charger de contenu sans Utilisateur: {}!!! +332=Impossible de charger de contenu sans Utilisateur <{}>!!! -340=DataSource.queryClosing() -341=La base de donn\u00e9e: {} a \u00e9t\u00e9 ferm\u00e9e +340=DataSource.dispose() +341=La base de donnιe <{}> a ιtι fermιe... +342=Exception python inattendue <{}> avec le traceback: {} 400=DataBase.__init__() -401=DataBase: Chargement termin\u00e9 -402=DataBase._mergeItem() size {} os name {} +401=DataBase: Chargement terminι. +402=DataBase._mergeItem() size <{}> os name <{}>. 410=DataBase.createDataBase() -411=Essaie de crιer la base de donnιes avec HsqlDB version: {} -412=Crιation rιussie de la base de donnιes -413=Impossible de crιer la base de donnιes : la version de HsqlDB installιe: {} est infιrieure ΰ la version minimale prise en charge: {} +411=Essaie de crιer la base de donnιes avec HsqlDB version <{}>. +412=Crιation rιussie de la base de donnιes. +413=Impossible de crιer la base de donnιes: la version de HsqlDB installιe <{}> est infιrieure ΰ la version minimale prise en charge <{}>. 500=User.__init__() 501={} - Erreur d'authentification -502=L'assistant OAuth2 a ιtι abandonnι par l'utilisateur: {}. La connexion ne peut pas κtre ιtablie!!! +502=L'assistant OAuth2 a ιtι abandonnι par l'utilisateur <{}>. La connexion ne peut pas κtre ιtablie!!! 503={} - Erreur de rιseau -504=Impossible de retrouver l'utilisateur: {} chez le fournisseur: le r\u00e9seau est hors ligne!!! +504=Impossible de retrouver l'utilisateur <{}> chez le fournisseur: le rιseau est hors ligne!!! 505={} - Erreur d'authentification -506=L'utilisateur: {} est inconnu chez ce fournisseur!!! +506=L'utilisateur <{}> est inconnu chez ce fournisseur!!! 507={} - Erreur de base de donnιes -508=Impossible d'inserer l'utilisateur: {} dans la base de donn\u00e9es -509=User chargement termin\u00e9 +508=Impossible d'inserer l'utilisateur <{}> dans la base de donnιes. +509=User chargement terminι. 510=User.getDocumentContent() -511=Erreur: {} - {} +511=Erreur <{}> - {} + + +550=Provider.__init__() +551=Provider chargement terminι. + +560=Provider._getUser() +561=Erreur sur la requκte <{}> qui a renvoyι le code <{}> et la rιponse <{}>. + +570=Provider._getRoot() +571=Erreur sur la requκte <{}> qui a renvoyι le code <{}> et la rιponse <{}>. 600=Content.__init__() -601=Content chargement termin\u00e9 +601=Content chargement terminι. 610=Content._getMetaData() -611=Erreur: Impossible de charger le contenu avec un Identifier incomplet: {} -612=Erreur: Impossible de r\u00e9cup\u00e9rer les m\u00e9tadonn\u00e9es pour l'ID: {} et l'Uri: {} +611=Erreur: Impossible de charger le contenu avec un Identifier incomplet <{}>. +612=Erreur: Impossible de rιcupιrer les mιtadonnιes pour l'ID <{}> et l'Uri <{}>. 620=Content._updateFolderContent() -621=Le contenu du dossier: {} est obtenu avec Request!!! +621=Le contenu du dossier <{}> est obtenu avec Request!!! 630=Content.execute() -631=Le contenu execute la commande: {} sur l'identifiant: {} -632=Erreur lors du tιlιchargement du fichier : {} -633=Impossible de gιrer le transfert sur l'identifiant: {}, seul le dossier peut gιrer le transfert -634=Impossible de gιrer l'URL: {} sur l'identifiant: {} -635=Sauvegarde du fichier: {} taille: {} +631=Le contenu execute la commande <{}> sur l'identifiant <{}>. +632=Erreur lors du tιlιchargement du fichier <{}>. +633=Impossible de gιrer le transfert sur l'identifiant <{}>, seul le dossier peut gιrer le transfert. +634=Impossible de gιrer l'URL <{}> sur l'identifiant <{}>. +635=Sauvegarde du fichier <{}> taille <{}>. 640=Content.getIdentifier() -641=Identificateur de contenu: {} -642=Erreur: {} - {} +641=Identificateur de contenu <{}>. +642=Erreur <{}> - {} 650=Content.getParent() -651=Erreur: {} - {} +651=Erreur <{}> - {} 660=Content.createNewContent() -661=Creation d'un nouveau contenu dans le dossier: {} +661=Creation d'un nouveau contenu dans le dossier <{}>. + 710=PropertySetInfo.getPropertyByName() -711=Le contenu donne la propriιtι: {} +711=Le contenu donne la propriιtι <{}>. 720=PropertySetInfo.hasPropertyByName() -721=Le contenu a la propriιtι: {} - {} +721=Le contenu a la propriιtι <{}> - <{}>. diff --git a/uno/resource/ucb/Replicator_en_US.properties b/uno/resource/ucb/Replicator_en_US.properties index 5727bc6a..820530a1 100644 --- a/uno/resource/ucb/Replicator_en_US.properties +++ b/uno/resource/ucb/Replicator_en_US.properties @@ -1,11 +1,10 @@ 100=Replicator.__init__() 101=Replicator has been initialized. 102=Replicator thread throw error: {}\n{} -103=Replicator {} 110=Replicator.run() -111=Replicator start thread with id {}. -112=Replicator thread has been paused for {} minutes. +111=Replicator start thread with id <{}>. +112=Replicator thread has been paused for <{}> minutes. 113=Replicator thread throw error: {}\n{} 120=Replicator._synchronize() @@ -14,45 +13,50 @@ 123=Synchronization is not possible network is offline!!! 124=End of data synchronization. -200=Replicator._pullUsers() -201=Starting data download for user: {}. -202=End of download for user: {}. -203=Replicator thread throw error: {}\n{} +130=Replicator._synchronizeUser() +131=Starting data synchronization for user <{}>. +132=End of data synchronization for user <{}>. + +200=Replicator._pullUser() +201=Starting data download for user <{}>. +202=User <{}> pull <{}> changes including <{}> download using <{}> HTTP request with token <{}>. +203=End of download for user <{}>. +204=Replicator thread throw error: {}\n{} 210=Replicator._checkNewIdentifier() -211=User {} only has {} identifiers left, {} identifiers have just been downloaded. -212=Has throw an error when requesting identifiers for user {} with the message: {} +211=User <{}> only has <{}> identifiers left, <{}> identifiers have just been downloaded. +212=Has throw an error when requesting identifiers for user <{}> with the message: {} 220=Replicator._initUser() -221=Initialize user name: {}. -222=First data pull for user name {}, done... - -230=Replicator._pullUser() -231=User {} pull #{} changes on #{} pages with token {} +221=Initialize user <{}>. +222=First data pull for user <{}> make <{}> changes using <{}> HTTP request with token <{}>. +223=First data pull for user <{}>, done... +224=Replicator thread throw error: {}\n{} 300=Replicator._pushUsers() -301=Starting data replication for user: {}. -302=Has encountered an error while replicating the file/folder: {} of {} with Id: {} -303=End of replicating data for user: {}. -304=Replicator thread throw error: {}\n{} +301=Starting data replication for user <{}>. +302=Has encountered an error while replicating the file/folder <{}> of <{}> with Id <{}> +303=Pushing data for user <{}> for <{}> changes. +304=End of replicating data for user <{}>. +305=Replicator thread throw error: {}\n{} 310=Replicator._pushItem -311=Has replicated the creation of a folder: {} of {} -312=Has replicated the file/folder's title: {} update of {} -313=Has replicated the file/folder's trashed: {} of {} -314=Error on request <{}>: Can't retrieve upload location for file: {} HTTP response: {} -315=Error on request <{}>: Can't retrieve upload location for file: {} -316=Error on request <{}>: Can't upload file: {} HTTP response: {} -317=Has replicated the creation of file: {} of {} size: {} -318=Has replicated the update of file: {} of {} size: {} +311=Has replicated the creation of a folder <{}> of <{}>. +312=Has replicated the file/folder's title <{}> update of <{}>. +313=Has replicated the file/folder's trashed <{}> of <{}>. +314=Error on request <{}>: Can't retrieve upload location for file <{}> HTTP response <{}>. +315=Error on request <{}>: Can't retrieve upload location for file <{}>. +316=Error on request <{}>: Can't upload file <{}> HTTP response <{}>. +317=Has replicated the creation of file <{}> of <{}> size <{}>. +318=Has replicated the update of file <{}> of <{}> size <{}>. 319=Replicator thread throw error: {}\n{} 400=DataBase.__init__() -401=DataBase: Loading completed -402=DataBase._mergeItem() size {} os name {} +401=DataBase: Loading completed. +402=DataBase._mergeItem() size <{}> os name <{}>. 410=DataBase.createDataBase() -411=Try to create the database with HsqlDB version: {} -412=Successful database creation -413=Unable to create database: installed HsqlDB version: {} is lower than minimum supported version: {} +411=Try to create the database with HsqlDB version <{}>. +412=Successful database creation. +413=Unable to create database: installed HsqlDB version <{}> is lower than minimum supported version <{}>. diff --git a/uno/resource/ucb/Replicator_fr_FR.properties b/uno/resource/ucb/Replicator_fr_FR.properties index 65a4f518..84f3d984 100644 --- a/uno/resource/ucb/Replicator_fr_FR.properties +++ b/uno/resource/ucb/Replicator_fr_FR.properties @@ -1,58 +1,62 @@ 100=Replicator.__init__() 101=Le rιplicateur a ιtι initialisι. -102=Le fil d'execution du rιplicateur a lancer l'erreur: {}\n{} -103=Le rιplicateur {} +102=Le fil d'execution du rιplicateur a gιnιrι l'erreur: {}\n{} 110=Replicator.run() -111=Le rιplicateur ΰ dιmarrer un fil d'execution avec l'id {}. -112=Le fil d'execution du rιplicateur a ιtι suspendu pour {} minutes. -113=Le fil d'execution du rιplicateur a lancer l'erreur: {}\n{} +111=Le rιplicateur ΰ dιmarrer un fil d'execution avec l'id <{}>. +112=Le fil d'execution du rιplicateur a ιtι suspendu pour <{}> minutes. +113=Le fil d'execution du rιplicateur a gιnιrι l'erreur: {}\n{} 120=Replicator._synchronize() -121=D\u00e9marrage de la synchronisation des donn\u00e9es. +121=Dιmarrage de la synchronisation des donnιes. 122=La synchronisation a ιtι dιsactivιe!!! -123=La synchronisation n'est pas possible le r\u00e9seau est hors ligne!!! -124=Fin de la synchronisation des donn\u00e9es. +123=La synchronisation n'est pas possible le rιseau est hors ligne!!! +124=Fin de la synchronisation des donnιes. -200=Replicator._pullUsers() -201=D\u00e9marrage du t\u00e9l\u00e9chargement des donn\u00e9es pour l'utilisateur: {}. -202=Fin du t\u00e9l\u00e9chargement pour l'utilisateur: {}. -203=Le fil d'execution du rιplicateur a lancer l'erreur: {}\n{} +130=Replicator._synchronizeUser() +131=Dιmarrage de la synchronisation des donnιes pour l'utilisateur <{}>. +132=Fin de la synchronisation des donnιes pour l'utilisateur <{}>. + +200=Replicator._pullUser() +201=Dιmarrage de la rιplication des donnιes distantes pour l'utilisateur <{}>. +202=La rιplication pour l'utilisateur <{}> a produit <{}> modifications dont <{}> tιlιchargements ΰ l'aide de <{}> requκte HTTP avec le jeton <{}>. +203=Fin de la rιplication des donnιes distantes pour l'utilisateur <{}>. +204=Le fil d'execution du rιplicateur a gιnιrι l'erreur: {}\n{} 210=Replicator._checkNewIdentifier() -211=L'utilisateur {} ne dispose plus que de {} identifiants, {} identifiants viennent d'κtre tιlιchargι. -212=A gιnιrι une erreur lors de la demande identifiants pour l'utilisateur {} avec le message: {} +211=L'utilisateur <{}> ne dispose plus que de <{}> identifiants, <{}> identifiants viennent d'κtre tιlιchargι. +212=A gιnιrι une erreur lors de la demande identifiants pour l'utilisateur <{}> avec le message: {} 220=Replicator._initUser() -221=Initialisation de l'utilisateur {}. -222=Premiθre rιplication de donnιes pour l'utilisateur {}, terminιe... - -230=Replicator._pullUser() -231=L'utilisateur {} a #{} changements sur #{} pages avec le jeton {}. +221=Initialisation de l'utilisateur <{}>. +222=La premiθre rιplication pour l'utilisateur <{}> a produit <{}> modifications ΰ l'aide de <{}> requκte HTTP avec le jeton <{}>. +223=La premiθre rιplication pour l'utilisateur <{}>, terminιe... +224=Le fil d'execution du rιplicateur a gιnιrι l'erreur: {}\n{} 300=Replicator._pushUsers() -301=D\u00e9marrage de la r\u00e9plication des donn\u00e9es pour l'utilisateur: {}. -302=A rencontr\u00e9 une erreur lors de la r\u00e9pliquation du fichier/r\u00e9p\u00e9rtoire: {} du {} portant l'Id: {} -303=Fin de la r\u00e9plication des donn\u00e9es pour l'utilisateur: {}. -304=Le fil d'execution du rιplicateur a lancer l'erreur: {}\n{} +301=Dιmarrage de la rιplication des donnιes locales pour l'utilisateur <{}>. +302=A rencontrι une erreur lors de la rιpliquation du fichier/rιpιrtoire <{}> du <{}> portant l'Id <{}> +303=La rιplication pour l'utilisateur <{}> a effectuι <{}> modifications. +304=Fin de la rιplication des donnιes locales pour l'utilisateur <{}>. +305=Le fil d'execution du rιplicateur a gιnιrι l'erreur: {}\n{} 310=Replicator._pushItem -311=A repliqu\u00e9 la cr\u00e9ation du dossier: {} du {} -312=A repliqu\u00e9 la mise \u00e0 jour du titre du fichier/r\u00e9p\u00e9rtoire: {} du {} -313=A repliqu\u00e9 la mise \u00e0 jour de l'effacement du fichier/r\u00e9p\u00e9rtoire: {} du {} -314=Erreur sur la requκte <{}>: Ne peut pas retrouver l'emplacement de la mise \u00e0 jour du fichier: {} la rιponse HTTP est: {} -315=Erreur sur la requκte <{}>: Ne peut pas retrouver l'emplacement de la mise \u00e0 jour du fichier: {} -316=Erreur sur la requκte <{}>: Ne peut pas mettre \u00e0 jour le fichier: {} la rιponse HTTP est: {} -317=A repliquι la crιation du fichier: {} du {} d'une taille de: {} -318=A repliqu\u00e9 la mise \u00e0 jour du fichier: {} du {} d'une taille de: {} -319=Le fil d'execution du rιplicateur a lancer l'erreur: {}\n{} +311=A repliquι la crιation du dossier <{}> du <{}>. +312=A repliquι la mise ΰ jour du titre du fichier/rιpιrtoire <{}> du <{}>. +313=A repliquι la mise ΰ jour de l'effacement du fichier/rιpιrtoire <{}> du <{}>. +314=Erreur sur la requκte <{}>: Ne peut pas retrouver l'emplacement de la mise ΰ jour du fichier <{}> la rιponse HTTP est <{}>. +315=Erreur sur la requκte <{}>: Ne peut pas retrouver l'emplacement de la mise ΰ jour du fichier <{}>. +316=Erreur sur la requκte <{}>: Ne peut pas mettre ΰ jour le fichier <{}> la rιponse HTTP est <{}>. +317=A repliquι la crιation du fichier <{}> du <{}> d'une taille de <{}>. +318=A repliquι la mise ΰ jour du fichier <{}> du <{}> d'une taille de <{}>. +319=Le fil d'execution du rιplicateur a gιnιrι l'erreur: {}\n{} 400=DataBase.__init__() -401=DataBase: Chargement termin\u00e9 -402=DataBase._mergeItem() size {} os name {} +401=DataBase: Chargement terminι +402=DataBase._mergeItem() size <{}> os name <{}> 410=DataBase.createDataBase() -411=Essaie de crιer la base de donnιes avec HsqlDB version: {} -412=Crιation rιussie de la base de donnιes -413=Impossible de crιer la base de donnιes : la version de HsqlDB installιe: {} est infιrieure ΰ la version minimale prise en charge: {} +411=Essaie de crιer la base de donnιes avec HsqlDB version <{}>. +412=Crιation rιussie de la base de donnιes. +413=Impossible de crιer la base de donnιes: la version de HsqlDB installιe <{}> est infιrieure ΰ la version minimale prise en charge <{}>.