Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Project history : Add a version viewer windows in the plugin #630

Open
wants to merge 77 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
77 commits
Select commit Hold shift + click to select a range
a4a001f
reimplemenation
ValentinBuira Aug 29, 2024
444ead8
reimplemenation
ValentinBuira Aug 29, 2024
0a89e33
Bring back ui setup
ValentinBuira Sep 4, 2024
17c3131
switch to deque for storing model data
ValentinBuira Sep 5, 2024
cc96ae7
Add clear method
ValentinBuira Sep 6, 2024
1934ba8
Bring back toolbar button
ValentinBuira Sep 6, 2024
3dc9919
Add new icon
ValentinBuira Sep 6, 2024
07e3ae0
Bring back version detail
ValentinBuira Sep 10, 2024
1ab9eaf
brnig back update history on synchronise
ValentinBuira Sep 10, 2024
fc56e80
Fix update on changing project and configuration
ValentinBuira Sep 11, 2024
af6984d
Bring back view version
ValentinBuira Sep 11, 2024
3baee96
Merge remote-tracking branch 'origin/project-history-refactor' into p…
ValentinBuira Sep 11, 2024
9b6afc7
Remove superfluous log
ValentinBuira Sep 11, 2024
8890df5
cleanup useless function
ValentinBuira Sep 17, 2024
e1685ee
Proof of concept version viewer
ValentinBuira Sep 23, 2024
bf49863
Update version viewer
ValentinBuira Sep 30, 2024
0e18db5
bring back missing actions
ValentinBuira Oct 4, 2024
c2bdff3
splitter behaviour
ValentinBuira Oct 4, 2024
7288510
Merge branch 'master' into project-history-refactor
ValentinBuira Oct 4, 2024
7e8f31e
syntax error
ValentinBuira Oct 4, 2024
8457b0a
Apply black formatting
ValentinBuira Oct 4, 2024
5d161cf
FIx view not updated when changed with keyboard and cleanup
ValentinBuira Oct 4, 2024
88652c0
Fix default version openned and reorder things
ValentinBuira Oct 8, 2024
c9a55ce
add control to map
ValentinBuira Oct 8, 2024
e597a20
format
ValentinBuira Oct 8, 2024
2d64dd2
add todo
ValentinBuira Oct 8, 2024
e2adcb6
Hide from the plugin the old history dock
ValentinBuira Oct 8, 2024
7306479
fix version were not cached
ValentinBuira Oct 9, 2024
16e2ee6
Remove previous dock
ValentinBuira Oct 9, 2024
b1410c0
Merge branch 'master' into project-history-refactor
ValentinBuira Oct 9, 2024
625fa81
version parse
ValentinBuira Oct 21, 2024
2ec26ff
Fix thread issue
ValentinBuira Oct 21, 2024
a3c1d23
Fix some version not working
ValentinBuira Oct 22, 2024
111fd92
Add loading
ValentinBuira Oct 22, 2024
363ea0c
Fix fetching last version erroneously
ValentinBuira Oct 22, 2024
1804002
Fix unconnect action to method
ValentinBuira Oct 24, 2024
6ded9f1
Change default info text
ValentinBuira Oct 24, 2024
5fd2796
Remove superflous import
ValentinBuira Oct 24, 2024
6ac5211
Remove not used methods
ValentinBuira Oct 24, 2024
aa09ed3
Remove dependancy to model variable
ValentinBuira Oct 24, 2024
f22177a
Add licence header
ValentinBuira Oct 24, 2024
47cc0b6
Merge branch 'master' into project-history-refactor
ValentinBuira Oct 24, 2024
aa55525
Fix map layers not updated in some conditions
ValentinBuira Oct 29, 2024
a5c39ef
Set changeset details not editable
ValentinBuira Oct 29, 2024
a02c234
Fix elapsed time not working because of difference between local and …
ValentinBuira Oct 30, 2024
af0d091
Display text as well for button
ValentinBuira Oct 30, 2024
c7fb9b7
Remove unused fetching on sync
ValentinBuira Oct 30, 2024
8193a0f
Consider more user agent and simplify code
ValentinBuira Oct 30, 2024
cec1708
Simplify version fetcher
ValentinBuira Nov 4, 2024
e1e02ca
Fix early cancel dialog
ValentinBuira Nov 4, 2024
082f58d
Add tooltip
ValentinBuira Nov 4, 2024
44193ce
Format
ValentinBuira Nov 4, 2024
da63088
change contextual_date for older than one year
ValentinBuira Nov 4, 2024
8f8fdbe
More sales friendly message
ValentinBuira Nov 4, 2024
b766621
Move function to utils
ValentinBuira Nov 4, 2024
96642ce
Fix root_dir parameter not existing in older python version
ValentinBuira Nov 5, 2024
329aa01
Typo
ValentinBuira Nov 5, 2024
363b054
Change window title dynamically
ValentinBuira Nov 6, 2024
fb57936
Remove superflous information in layer name
ValentinBuira Nov 6, 2024
c75e1f4
Add information for local version
ValentinBuira Nov 6, 2024
9447fc3
Revert name
ValentinBuira Nov 6, 2024
69b0b53
* Only display background map
ValentinBuira Nov 6, 2024
ad042bf
Fix extend changing when toggle background layers
ValentinBuira Nov 6, 2024
465e367
Revert "Fix root_dir parameter not existing in older python version"
ValentinBuira Nov 6, 2024
a10653f
cleanup rename variable
ValentinBuira Nov 6, 2024
8f6065c
Rename layers and files tab
ValentinBuira Nov 7, 2024
c9e21a8
Add changeset summary in layer tab as well
ValentinBuira Nov 7, 2024
12d310d
Fix crash because of a edge case with layer named 'gpkh_something'
ValentinBuira Nov 8, 2024
1468cdf
Fix non spatial layer
ValentinBuira Nov 8, 2024
572b91b
Fix last commit + format
ValentinBuira Nov 8, 2024
7e56254
Fix formatting
ValentinBuira Nov 12, 2024
7b3d20d
Fix crash mismatch between changeset and downloaded version
ValentinBuira Nov 12, 2024
3c4a0cc
Fix non spatial layer again
ValentinBuira Nov 12, 2024
ff732f6
Fix projection again
ValentinBuira Nov 13, 2024
cdb2d43
Format
ValentinBuira Nov 13, 2024
53ad133
Fix zoom full
ValentinBuira Nov 19, 2024
fefbdd9
Format
ValentinBuira Nov 19, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
88 changes: 88 additions & 0 deletions Mergin/diff.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,14 @@
import base64
import sqlite3
import tempfile
import glob

from qgis.PyQt.QtCore import QVariant

from qgis.PyQt.QtGui import QColor

from qgis.core import (
QgsApplication,
QgsVectorLayer,
QgsFeature,
QgsGeometry,
Expand Down Expand Up @@ -107,6 +109,18 @@ def parse_db_schema(db_file):
return tables


def db_schema_from_json(schema_json):
"""Create map of tables from the schema JSON file"""
tables = {} # key: name, value: TableSchema
for tbl in schema_json:
columns = []
for col in tbl["columns"]:
columns.append(ColumnSchema(col["name"], col["type"], "primary_key" in col and col["primary_key"]))

tables[tbl["table"]] = TableSchema(tbl["table"], columns)
return tables


def parse_diff(geodiff, diff_file):
"""
Parse binary GeoDiff changeset and return map of changes per table
Expand Down Expand Up @@ -311,6 +325,80 @@ def make_local_changes_layer(mp, layer):
return vl, ""


def make_version_changes_layers(project_path, version):
geodiff = pygeodiff.GeoDiff()

layers = []
version_dir = os.path.join(project_path, ".mergin", ".cache", f"v{version}")
for f in glob.iglob(f"{version_dir}/*.gpkg"):
gpkg_file = os.path.join(version_dir, f)
schema_file = gpkg_file + "-schema.json"
if not os.path.exists(schema_file):
geodiff.schema("sqlite", "", gpkg_file, schema_file)

changeset_file = find_changeset_file(f, version_dir)
if changeset_file is None:
continue

with open(schema_file, encoding="utf-8") as fl:
data = fl.read()
schema_json = json.loads(data.replace("\n", "")).get("geodiff_schema")

db_schema = db_schema_from_json(schema_json)
diff = parse_diff(geodiff, changeset_file)

for table_name in diff.keys():
if table_name.startswith("gpkg_"):
# db schema reported by geodiff exclude layer named "gpkg_*"
# We skip it in the layer displayed to the user
continue
fields, cols_to_fields = create_field_list(db_schema[table_name])
geom_type, geom_crs = get_layer_geometry_info(schema_json, table_name)

db_conn = None # no ref. db
db_conn = sqlite3.connect(gpkg_file)

features = diff_table_to_features(diff[table_name], db_schema[table_name], fields, cols_to_fields, db_conn)

# create diff layer
if geom_type is None:
continue

uri = f"{geom_type}?crs=epsg:{geom_crs}" if geom_crs else geom_type
vl = QgsVectorLayer(uri, table_name, "memory")
if not vl.isValid():
continue

vl.dataProvider().addAttributes(fields)
vl.updateFields()
vl.dataProvider().addFeatures(features)

style_diff_layer(vl, db_schema[table_name])
layers.append(vl)

return layers


def find_changeset_file(file_name, version_dir):
"""Returns path to the diff file for the given version file"""
for f in glob.iglob(f"{version_dir}/*.gpkg-diff*"):
if f.startswith(file_name):
return os.path.join(version_dir, f)
return None


def get_layer_geometry_info(schema_json, table_name):
"""Returns geometry type and CRS for a given table"""
for tbl in schema_json:
if tbl["table"] == table_name:
for col in tbl["columns"]:
if col["type"] == "geometry":
return col["geometry"]["type"], col["geometry"]["srs_id"]
return "NoGeometry", ""

return None, None


def style_diff_layer(layer, schema_table):
"""Apply conditional styling and symbology to diff layer"""
### setup conditional styles!
Expand Down
36 changes: 19 additions & 17 deletions Mergin/diff_dialog.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,14 @@
from qgis.utils import iface, OverrideCursor

from .mergin.merginproject import MerginProject
from .diff import make_local_changes_layer
from .utils import icon_path
from .diff import make_local_changes_layer, make_version_changes_layers
from .utils import icon_path, icon_for_layer

ui_file = os.path.join(os.path.dirname(os.path.realpath(__file__)), "ui", "ui_diff_viewer_dialog.ui")


class DiffViewerDialog(QDialog):
def __init__(self, parent=None):
def __init__(self, version=None, parent=None):
QDialog.__init__(self, parent)
self.ui = uic.loadUi(ui_file, self)

Expand Down Expand Up @@ -91,6 +91,8 @@ def __init__(self, parent=None):
self.diff_layers = []
self.filter_model = None

self.version = version

self.create_tabs()

def reject(self):
Expand All @@ -106,6 +108,12 @@ def save_splitter_state(self):
settings.setValue("Mergin/changesViewerSplitterSize", self.splitter.saveState())

def create_tabs(self):
if self.version is None:
self.show_local_changes()
else:
self.show_version_changes()

def show_local_changes(self):
mp = MerginProject(QgsProject.instance().homePath())
project_layers = QgsProject.instance().mapLayers()
for layer in project_layers.values():
Expand All @@ -122,7 +130,14 @@ def create_tabs(self):
continue

self.diff_layers.append(vl)
self.tab_bar.addTab(self.icon_for_layer(vl), f"{layer.name()} ({vl.featureCount()})")
self.tab_bar.addTab(icon_for_layer(vl), f"{layer.name()} ({vl.featureCount()})")
self.tab_bar.setCurrentIndex(0)

def show_version_changes(self):
layers = make_version_changes_layers(QgsProject.instance().homePath(), self.version)
for vl in layers:
self.diff_layers.append(vl)
self.tab_bar.addTab(icon_for_layer(vl), f"{vl.name()} ({vl.featureCount()})")
self.tab_bar.setCurrentIndex(0)

def toggle_project_layers(self, checked):
Expand Down Expand Up @@ -199,19 +214,6 @@ def zoom_selected(self):
self.map_canvas.zoomToSelected([self.current_diff])
self.map_canvas.refresh()

def icon_for_layer(self, layer):
geom_type = layer.geometryType()
if geom_type == QgsWkbTypes.PointGeometry:
return QgsApplication.getThemeIcon("/mIconPointLayer.svg")
elif geom_type == QgsWkbTypes.LineGeometry:
return QgsApplication.getThemeIcon("/mIconLineLayer.svg")
elif geom_type == QgsWkbTypes.PolygonGeometry:
return QgsApplication.getThemeIcon("/mIconPolygonLayer.svg")
elif geom_type == QgsWkbTypes.UnknownGeometry:
return QgsApplication.getThemeIcon("/mIconGeometryCollectionLayer.svg")
else:
return QgsApplication.getThemeIcon("/mIconTableLayer.svg")

def show_unsaved_changes_warning(self):
self.ui.messageBar.pushMessage(
"Mergin",
Expand Down
9 changes: 9 additions & 0 deletions Mergin/images/default/tabler_icons/file-description.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
7 changes: 7 additions & 0 deletions Mergin/images/default/tabler_icons/history.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
9 changes: 9 additions & 0 deletions Mergin/images/white/tabler_icons/file-description.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
7 changes: 7 additions & 0 deletions Mergin/images/white/tabler_icons/history.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
18 changes: 17 additions & 1 deletion Mergin/plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,10 +42,12 @@
from .sync_dialog import SyncDialog
from .configure_sync_wizard import DbSyncConfigWizard
from .remove_project_dialog import RemoveProjectDialog
from .version_viewer_dialog import VersionViewerDialog
from .utils import (
ServerType,
ClientError,
LoginError,
InvalidProject,
check_mergin_subdirs,
create_mergin_client,
find_qgis_files,
Expand All @@ -62,7 +64,7 @@
unsaved_project_check,
UnsavedChangesStrategy,
)

from .mergin.utils import int_version, is_versioned_file
from .mergin.merginproject import MerginProject
from .processing.provider import MerginProvider
import processing
Expand Down Expand Up @@ -92,6 +94,7 @@ def __init__(self, iface):
self.toolbar.setObjectName("MerginMapsToolbar")

self.iface.projectRead.connect(self.on_qgis_project_changed)
self.on_qgis_project_changed()
self.iface.newProjectCreated.connect(self.on_qgis_project_changed)

settings = QSettings()
Expand Down Expand Up @@ -155,6 +158,15 @@ def initGui(self):
add_to_menu=True,
add_to_toolbar=None,
)
self.history_dock_action = self.add_action(
"history.svg",
text="Project History",
callback=self.open_project_history_window,
add_to_menu=False,
add_to_toolbar=self.toolbar,
enabled=False,
always_on=False,
)

self.enable_toolbar_actions()

Expand Down Expand Up @@ -324,6 +336,10 @@ def configure_db_sync(self):
if not wizard.exec():
return

def open_project_history_window(self):
dlg = VersionViewerDialog(self.mc)
dlg.exec()

def show_no_workspaces_dialog(self):
msg = (
"Workspace is a place to store your projects and share them with your colleagues. "
Expand Down
83 changes: 83 additions & 0 deletions Mergin/ui/ui_project_history_dock.ui
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>ProjectHistoryDockWidget</class>
<widget class="QDockWidget" name="ProjectHistoryDockWidget">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>370</width>
<height>450</height>
</rect>
</property>
<property name="allowedAreas">
<set>Qt::LeftDockWidgetArea|Qt::RightDockWidgetArea</set>
</property>
<property name="windowTitle">
<string>Mergin Maps history</string>
</property>
<widget class="QWidget" name="dockWidgetContents">
<layout class="QVBoxLayout" name="verticalLayout_2">
<property name="spacing">
<number>0</number>
</property>
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QStackedWidget" name="stackedWidget">
<property name="currentIndex">
<number>1</number>
</property>
<widget class="QWidget" name="page">
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QLabel" name="info_label">
<property name="text">
<string>Current project is not a Mergin project. Project history is not available.</string>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</widget>
<widget class="QWidget" name="page_2">
<layout class="QGridLayout" name="gridLayout">
<item row="1" column="0" colspan="2">
<widget class="QTreeView" name="versions_tree">
<property name="contextMenuPolicy">
<enum>Qt::CustomContextMenu</enum>
</property>
<property name="rootIsDecorated">
<bool>false</bool>
</property>
</widget>
</item>
<item row="0" column="0" colspan="2">
<widget class="QPushButton" name="view_changes_btn">
<property name="text">
<string>View changes</string>
</property>
</widget>
</item>
</layout>
</widget>
</widget>
</item>
</layout>
</widget>
</widget>
<resources/>
<connections/>
</ui>
Loading
Loading