Skip to content

Commit

Permalink
undo: minimize undo api dependencies between core and ui
Browse files Browse the repository at this point in the history
  • Loading branch information
Oleksiy-Yakovenko committed Feb 2, 2024
1 parent 04885c4 commit b29732a
Show file tree
Hide file tree
Showing 22 changed files with 337 additions and 151 deletions.
17 changes: 13 additions & 4 deletions include/deadbeef/deadbeef.h
Original file line number Diff line number Diff line change
Expand Up @@ -835,9 +835,18 @@ struct ddb_undobuffer_s;

typedef struct {
size_t _size;
void (*initialize)(struct ddb_undomanager_s *undomanager);
int (*process_action)(struct ddb_undobuffer_s *undobuffer, const char *action_name);
void (*group_begin)(void);
void (*group_end)(void);
void (*set_action_name)(const char *action_name);
void (*free_buffer)(struct ddb_undobuffer_s *undobuffer);
void (*execute_buffer)(struct ddb_undobuffer_s *undobuffer);
} ddb_undo_interface_t;

typedef struct {
size_t _size;
void (*initialize)(ddb_undo_interface_t *interface);
int (*process_action)(struct ddb_undobuffer_s *undobuffer, const char *action_name);
} ddb_undo_hooks_t;
#endif

// forward decl for plugin struct
Expand Down Expand Up @@ -1735,10 +1744,10 @@ typedef struct {
void (*undo_process)(void);

/// Allow UI plugin to declare Undo support, and register for receiving undo events.
/// Calling this function will enable Undo system, and will use the interface functions
/// Calling this function will enable Undo system, and will use the hooks functions
/// for communicating with UI plugin.
/// This function can be called only once per session, usually by the UI plugin's start method.
void (*register_for_undo) (ddb_undo_interface_t *interface);
void (*register_for_undo) (ddb_undo_hooks_t *interface);
#endif
} DB_functions_t;

Expand Down
28 changes: 20 additions & 8 deletions osx/deadbeef.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -876,10 +876,6 @@
2D2C515A2B6BE15900EAC44E /* undo.h in Headers */ = {isa = PBXBuildFile; fileRef = 2D2C51572B6BE15800EAC44E /* undo.h */; };
2D2C515B2B6BE15900EAC44E /* undo.c in Sources */ = {isa = PBXBuildFile; fileRef = 2D2C51582B6BE15900EAC44E /* undo.c */; };
2D2C515C2B6BE15900EAC44E /* undo.c in Sources */ = {isa = PBXBuildFile; fileRef = 2D2C51582B6BE15900EAC44E /* undo.c */; };
2D2C515E2B6BE21500EAC44E /* undomanager.c in Sources */ = {isa = PBXBuildFile; fileRef = 2D7387D429BE5DA1003E3126 /* undomanager.c */; };
2D2C515F2B6BE21500EAC44E /* undobuffer.c in Sources */ = {isa = PBXBuildFile; fileRef = 2D7387D529BE5DA1003E3126 /* undobuffer.c */; };
2D2C51612B6BE21500EAC44E /* undomanager.c in Sources */ = {isa = PBXBuildFile; fileRef = 2D7387D429BE5DA1003E3126 /* undomanager.c */; };
2D2C51622B6BE21500EAC44E /* undobuffer.c in Sources */ = {isa = PBXBuildFile; fileRef = 2D7387D529BE5DA1003E3126 /* undobuffer.c */; };
2D30D0B425E2A5DD0023A299 /* DesignModeState.h in Headers */ = {isa = PBXBuildFile; fileRef = 2D30D0B225E2A5DD0023A299 /* DesignModeState.h */; };
2D30D0B525E2A5DD0023A299 /* DesignModeState.m in Sources */ = {isa = PBXBuildFile; fileRef = 2D30D0B325E2A5DD0023A299 /* DesignModeState.m */; };
2D30D20C25E2A8930023A299 /* WidgetTopLevelView.h in Headers */ = {isa = PBXBuildFile; fileRef = 2D30D20A25E2A8930023A299 /* WidgetTopLevelView.h */; };
Expand Down Expand Up @@ -1650,6 +1646,12 @@
2D92D33C29B9323600218F1D /* analyzer.c in Sources */ = {isa = PBXBuildFile; fileRef = 2D92D21C29B92DF900218F1D /* analyzer.c */; };
2D92D33D29B9324A00218F1D /* tftintutil.c in Sources */ = {isa = PBXBuildFile; fileRef = 2D92D1F329B92DF900218F1D /* tftintutil.c */; };
2D92D33E29B9324A00218F1D /* pluginsettings.c in Sources */ = {isa = PBXBuildFile; fileRef = 2D92D21729B92DF900218F1D /* pluginsettings.c */; };
2D92F37C2B6D226F00BD07E8 /* UndoIntegration.h in Headers */ = {isa = PBXBuildFile; fileRef = 2D92F37A2B6D226F00BD07E8 /* UndoIntegration.h */; };
2D92F37D2B6D226F00BD07E8 /* UndoIntegration.m in Sources */ = {isa = PBXBuildFile; fileRef = 2D92F37B2B6D226F00BD07E8 /* UndoIntegration.m */; };
2D92F3822B6D250D00BD07E8 /* undointegration.c in Sources */ = {isa = PBXBuildFile; fileRef = 2D92F37F2B6D23FF00BD07E8 /* undointegration.c */; };
2D92F3832B6D250E00BD07E8 /* undointegration.c in Sources */ = {isa = PBXBuildFile; fileRef = 2D92F37F2B6D23FF00BD07E8 /* undointegration.c */; };
2D92F3842B6D25C400BD07E8 /* undointegration.h in Headers */ = {isa = PBXBuildFile; fileRef = 2D92F37E2B6D23FE00BD07E8 /* undointegration.h */; };
2D92F3852B6D25C400BD07E8 /* undointegration.h in Headers */ = {isa = PBXBuildFile; fileRef = 2D92F37E2B6D23FE00BD07E8 /* undointegration.h */; };
2D95F6B72939283D002D8499 /* libogglib.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 2D7C37EC1B2C40520029DE0A /* libogglib.dylib */; };
2D95F6BA29392884002D8499 /* artwork_ogg.h in Headers */ = {isa = PBXBuildFile; fileRef = 2D95F6B829392884002D8499 /* artwork_ogg.h */; };
2D95F6BB29392884002D8499 /* artwork_ogg.c in Sources */ = {isa = PBXBuildFile; fileRef = 2D95F6B929392884002D8499 /* artwork_ogg.c */; };
Expand Down Expand Up @@ -6235,6 +6237,10 @@
2D92D21C29B92DF900218F1D /* analyzer.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = analyzer.c; sourceTree = "<group>"; };
2D92D21D29B92DF900218F1D /* growableBuffer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = growableBuffer.h; sourceTree = "<group>"; };
2D92D21E29B92DF900218F1D /* ctmap.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = ctmap.c; sourceTree = "<group>"; };
2D92F37A2B6D226F00BD07E8 /* UndoIntegration.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = UndoIntegration.h; sourceTree = "<group>"; };
2D92F37B2B6D226F00BD07E8 /* UndoIntegration.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = UndoIntegration.m; sourceTree = "<group>"; };
2D92F37E2B6D23FE00BD07E8 /* undointegration.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = undointegration.h; sourceTree = "<group>"; };
2D92F37F2B6D23FF00BD07E8 /* undointegration.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = undointegration.c; sourceTree = "<group>"; };
2D95F6B829392884002D8499 /* artwork_ogg.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = artwork_ogg.h; sourceTree = "<group>"; };
2D95F6B929392884002D8499 /* artwork_ogg.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = artwork_ogg.c; sourceTree = "<group>"; };
2D95F6BF29392F17002D8499 /* base64.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = base64.c; sourceTree = "<group>"; };
Expand Down Expand Up @@ -11025,6 +11031,8 @@
2D95F720293BA3B8002D8499 /* NSUndoManager+DdbUndoBuffer.m */,
2D95F727293BB295002D8499 /* DdbUndoBufferRetainer.h */,
2D95F728293BB295002D8499 /* DdbUndoBufferRetainer.m */,
2D92F37A2B6D226F00BD07E8 /* UndoIntegration.h */,
2D92F37B2B6D226F00BD07E8 /* UndoIntegration.m */,
);
path = Undo;
sourceTree = "<group>";
Expand Down Expand Up @@ -12214,6 +12222,8 @@
2D0AC9A92864E12600F6D56A /* selpropertieswidget.h */,
2D2C51572B6BE15800EAC44E /* undo.h */,
2D2C51582B6BE15900EAC44E /* undo.c */,
2D92F37E2B6D23FE00BD07E8 /* undointegration.h */,
2D92F37F2B6D23FF00BD07E8 /* undointegration.c */,
);
name = gtkui;
path = plugins/gtkui;
Expand Down Expand Up @@ -14215,6 +14225,7 @@
2D0AC9AC2864E12700F6D56A /* selpropertieswidget.h in Headers */,
2DC656862744289C00583E14 /* ddb_splitter_size_mode.h in Headers */,
2DC656872744289C00583E14 /* prefwinnetwork.h in Headers */,
2D92F3842B6D25C400BD07E8 /* undointegration.h in Headers */,
2DC656882744289C00583E14 /* support.h in Headers */,
2D60EAFE277235C800C28A44 /* playlistcontroller.h in Headers */,
2DC6568A2744289C00583E14 /* ctmapping.h in Headers */,
Expand Down Expand Up @@ -14300,6 +14311,7 @@
2D0AC9AD2864E12700F6D56A /* selpropertieswidget.h in Headers */,
2DE3B7FB2269BF3100AFF9AE /* ddb_splitter_size_mode.h in Headers */,
2D715C1A26C839020022A8F0 /* prefwinnetwork.h in Headers */,
2D92F3852B6D25C400BD07E8 /* undointegration.h in Headers */,
2DE3B80B2269BF3100AFF9AE /* support.h in Headers */,
2D60EAFF277235C800C28A44 /* playlistcontroller.h in Headers */,
2DE3B7E92269BF3100AFF9AE /* ctmapping.h in Headers */,
Expand Down Expand Up @@ -14687,6 +14699,7 @@
2DF55C292270F415002C44DC /* ScriptableSelectViewController.h in Headers */,
2D6DCB3E291B105D00D6ECE6 /* fastftoi.h in Headers */,
2D71C26A1DC88E5C00247CEF /* ScriptableTableDataSource.h in Headers */,
2D92F37C2B6D226F00BD07E8 /* UndoIntegration.h in Headers */,
2DB951CB26B1E05300602876 /* SpectrumAnalyzerSettings.h in Headers */,
2DAB1A3026A5FB9C00EA8B8F /* PreferencesPluginEntry.h in Headers */,
2D60108D1A9CDF06000136AF /* SearchWindowController.h in Headers */,
Expand Down Expand Up @@ -17983,6 +17996,7 @@
2DC656A12744289C00583E14 /* fileman.c in Sources */,
2DC656A22744289C00583E14 /* prefwinsound.c in Sources */,
2D9793ED276E894E0062585E /* gobjcache.c in Sources */,
2D92F3822B6D250D00BD07E8 /* undointegration.c in Sources */,
2D92D31D29B92F8D00218F1D /* eqpreset.c in Sources */,
2DC656A42744289C00583E14 /* eq.c in Sources */,
2DC656A52744289C00583E14 /* deadbeefapp.c in Sources */,
Expand All @@ -18009,7 +18023,6 @@
2DC656B72744289C00583E14 /* actions.c in Sources */,
2DC656B82744289C00583E14 /* hotkeys.c in Sources */,
2DC656B92744289C00583E14 /* callbacks.c in Sources */,
2D2C515F2B6BE21500EAC44E /* undobuffer.c in Sources */,
2D0AC9AA2864E12700F6D56A /* selpropertieswidget.c in Sources */,
2DC656BA2744289C00583E14 /* pltmenu.c in Sources */,
2DC656BB2744289C00583E14 /* ddbcellrenderertextmultiline.c in Sources */,
Expand Down Expand Up @@ -18038,7 +18051,6 @@
2DC656CE2744289C00583E14 /* dspconfig.c in Sources */,
2DC656CF2744289C00583E14 /* widgets.c in Sources */,
2D56580D2AEE64200014443E /* gtkScriptable.c in Sources */,
2D2C515E2B6BE21500EAC44E /* undomanager.c in Sources */,
2DC656D02744289C00583E14 /* pluginconf.c in Sources */,
2DC656D12744289C00583E14 /* trkproperties.c in Sources */,
2DC656D22744289C00583E14 /* ctmapping.c in Sources */,
Expand Down Expand Up @@ -18092,6 +18104,7 @@
2DE3B7FA2269BF3100AFF9AE /* fileman.c in Sources */,
2D715C0B26C833570022A8F0 /* prefwinsound.c in Sources */,
2D9793EE276E894F0062585E /* gobjcache.c in Sources */,
2D92F3832B6D250E00BD07E8 /* undointegration.c in Sources */,
2D92D31F29B92F8D00218F1D /* eqpreset.c in Sources */,
2DE3B7C02269BF1300AFF9AE /* eq.c in Sources */,
2DE3B8022269BF3100AFF9AE /* deadbeefapp.c in Sources */,
Expand All @@ -18118,7 +18131,6 @@
2DE3B7F62269BF3100AFF9AE /* actions.c in Sources */,
2DE3B8002269BF3100AFF9AE /* hotkeys.c in Sources */,
2DE3B8012269BF3100AFF9AE /* callbacks.c in Sources */,
2D2C51622B6BE21500EAC44E /* undobuffer.c in Sources */,
2D0AC9AB2864E12700F6D56A /* selpropertieswidget.c in Sources */,
2DE3B7C72269BF1300AFF9AE /* pltmenu.c in Sources */,
2DE3B7C32269BF1300AFF9AE /* ddbcellrenderertextmultiline.c in Sources */,
Expand Down Expand Up @@ -18147,7 +18159,6 @@
2DE3B7DD2269BF3100AFF9AE /* dspconfig.c in Sources */,
2DE3B80A2269BF3100AFF9AE /* widgets.c in Sources */,
2D56580E2AEE64210014443E /* gtkScriptable.c in Sources */,
2D2C51612B6BE21500EAC44E /* undomanager.c in Sources */,
2DE3B7D92269BF1700AFF9AE /* pluginconf.c in Sources */,
2DE3B7EA2269BF3100AFF9AE /* trkproperties.c in Sources */,
2DE3B7CB2269BF1300AFF9AE /* ctmapping.c in Sources */,
Expand Down Expand Up @@ -18382,6 +18393,7 @@
2DC657E4274A5F6000583E14 /* PlaylistBrowserWidget.m in Sources */,
2D95F726293BA3DA002D8499 /* DdbUndoBuffer.m in Sources */,
2D9EBAAD25E44A0700255592 /* WidgetSerializer.m in Sources */,
2D92F37D2B6D226F00BD07E8 /* UndoIntegration.m in Sources */,
2D92D32129B92F9100218F1D /* pluginsettings.c in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
Expand Down
8 changes: 4 additions & 4 deletions plugins/cocoaui/AppDelegate.m
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@
#import "ReplayGainScannerController.h"
#import "TrackPropertiesManager.h"
#import "streamer.h"
#import "undomanager.h"
#import "UndoIntegration.h"

extern DB_functions_t *deadbeef;

Expand Down Expand Up @@ -457,7 +457,7 @@ - (void)openFiles:(BOOL)clear play:(BOOL)play {
if (!fileadd_cancelled) {
dispatch_async(dispatch_get_main_queue(), ^{
ddb_playItem_t *tail = deadbeef->plt_get_tail_item(plt_curr, PL_MAIN);
ddb_undomanager_set_action_name (ddb_undomanager_shared(), "Add Files");
ddb_undo->set_action_name ("Add Files");
deadbeef->plt_move_all_items(plt_curr, plt, tail);
if (tail != NULL) {
deadbeef->pl_item_unref (tail);
Expand Down Expand Up @@ -520,7 +520,7 @@ - (IBAction)addFoldersAction:(id)sender {
deadbeef->pl_item_unref (tail);
}
deadbeef->pl_save_current();
ddb_undomanager_set_action_name (ddb_undomanager_shared(), "Add Folders");
ddb_undo->set_action_name ("Add Folders");
deadbeef->plt_add_files_end (plt, 0);
deadbeef->plt_unref (plt);
deadbeef->plt_unref (plt_curr);
Expand Down Expand Up @@ -560,7 +560,7 @@ - (IBAction)addLocationAction:(id)sender {
deadbeef->pl_save_current ();

dispatch_async(dispatch_get_main_queue(), ^{
ddb_undomanager_set_action_name (ddb_undomanager_shared(), "Add Location");
ddb_undo->set_action_name ("Add Location");
deadbeef->plt_add_files_end (plt, 0);
deadbeef->plt_unref (plt);
deadbeef->plt_unref (plt_curr);
Expand Down
10 changes: 4 additions & 6 deletions plugins/cocoaui/MediaLibrary/MediaLibraryOutlineViewController.m
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,7 @@
#import "MedialibItemDragDropHolder.h"
#import "TrackContextMenu.h"
#import "TrackPropertiesWindowController.h"
#import "undo/undobuffer.h"
#import "undo/undomanager.h"
#import "UndoIntegration.h"

extern DB_functions_t *deadbeef;

Expand Down Expand Up @@ -374,8 +373,7 @@ - (int)addSelectionToPlaylist:(ddb_playlist_t *)plt {

int count = 0;

ddb_undobuffer_t *undobuffer = ddb_undomanager_get_buffer (ddb_undomanager_shared ());
ddb_undobuffer_group_begin (undobuffer);
ddb_undo->group_begin();

ddb_playItem_t *prev = deadbeef->plt_get_last(plt, PL_MAIN);
for (item in items) {
Expand All @@ -393,7 +391,7 @@ - (int)addSelectionToPlaylist:(ddb_playlist_t *)plt {
count += 1;
}

ddb_undobuffer_group_end (undobuffer);
ddb_undo->group_end ();

if (prev != NULL) {
deadbeef->pl_item_unref (prev);
Expand Down Expand Up @@ -423,7 +421,7 @@ - (void)outlineViewDoubleAction:(NSOutlineView *)sender {

deadbeef->plt_unref (curr_plt);

ddb_undomanager_set_action_name (ddb_undomanager_shared(), "Add Files");
ddb_undo->set_action_name ("Add Files");

deadbeef->sendmessage (DB_EV_PLAYLISTCHANGED, DDB_PLAYLIST_CHANGE_CONTENT, 0, 0);
if (count > 0) {
Expand Down
4 changes: 2 additions & 2 deletions plugins/cocoaui/Playlist/PlaylistContentView.m
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
#import "MedialibItemDragDropHolder.h"
#import "PlaylistLocalDragDropHolder.h"
#include <deadbeef/deadbeef.h>
#import "undomanager.h"
#import "UndoIntegration.h"

extern DB_functions_t *deadbeef;

Expand Down Expand Up @@ -164,7 +164,7 @@ - (BOOL)performDragOperation:(id<NSDraggingInfo>)sender {
}


ddb_undomanager_set_action_name (ddb_undomanager_shared(), "Drag & drop");
ddb_undo->set_action_name ("Drag & drop");

if ([pboard.types containsObject:ddbPlaylistItemsUTIType]) {
NSArray *classes = @[[PlaylistLocalDragDropHolder class]];
Expand Down
4 changes: 2 additions & 2 deletions plugins/cocoaui/Undo/DdbUndoBuffer.h
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,13 @@
*/

#import <Foundation/Foundation.h>
#include "undobuffer.h"
#include <deadbeef/deadbeef.h>

NS_ASSUME_NONNULL_BEGIN

@interface DdbUndoBuffer : NSObject

- (instancetype)initWithUndoBuffer:(ddb_undobuffer_t *)buffer;
- (instancetype)initWithUndoBuffer:(struct ddb_undobuffer_s *)buffer;
- (void)apply;

@end
Expand Down
12 changes: 6 additions & 6 deletions plugins/cocoaui/Undo/DdbUndoBuffer.m
Original file line number Diff line number Diff line change
Expand Up @@ -22,31 +22,31 @@
*/

#import "DdbUndoBuffer.h"
#include "undomanager.h"
#include <deadbeef/deadbeef.h>
#import <deadbeef/deadbeef.h>
#import "UndoIntegration.h"

extern DB_functions_t *deadbeef;

@interface DdbUndoBuffer()

@property (nonatomic) ddb_undobuffer_t *buffer;
@property (nonatomic) struct ddb_undobuffer_s *buffer;

@end

@implementation DdbUndoBuffer

- (void)dealloc {
ddb_undobuffer_free (_buffer);
ddb_undo->free_buffer (_buffer);
}

- (instancetype)initWithUndoBuffer:(ddb_undobuffer_t *)buffer {
- (instancetype)initWithUndoBuffer:(struct ddb_undobuffer_s *)buffer {
self = [super init];
_buffer = buffer;
return self;
}

- (void)apply {
ddb_undobuffer_execute(self.buffer, ddb_undomanager_get_buffer(ddb_undomanager_shared()));
ddb_undo->execute_buffer (self.buffer);

deadbeef->sendmessage(DB_EV_PLAYLISTCHANGED, 0, 0, 0);
}
Expand Down
8 changes: 4 additions & 4 deletions plugins/cocoaui/Undo/NSUndoManager+DdbUndoBuffer.m
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,9 @@
#import "DdbUndoBuffer.h"
#import "DdbUndoBufferRetainer.h"
#import "NSUndoManager+DdbUndoBuffer.h"
#include "undomanager.h"
#import "UndoIntegration.h"

extern DB_functions_t *deadbeef;

@implementation NSUndoManager (DdbUndoBuffer)

Expand All @@ -41,9 +43,7 @@ - (void)registerUndoBuffer:(DdbUndoBuffer *)undoBuffer {
[target apply];
[DdbUndoBufferRetainer.shared releaseBuffer:target];

ddb_undobuffer_t *undobuffer = ddb_undomanager_consume_buffer(ddb_undomanager_shared());
DdbUndoBuffer *wrappedBuffer = [[DdbUndoBuffer alloc] initWithUndoBuffer:undobuffer];
[self registerUndoBuffer:wrappedBuffer];
deadbeef->undo_process();
}];
}

Expand Down
32 changes: 32 additions & 0 deletions plugins/cocoaui/Undo/UndoIntegration.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/*
DeaDBeeF -- the music player
Copyright (C) 2009-2024 Oleksiy Yakovenko and other contributors
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/

#ifndef UndoIntegration_h
#define UndoIntegration_h

#include <deadbeef/deadbeef.h>

FOUNDATION_EXTERN ddb_undo_interface_t *ddb_undo;
void UndoIntegrationInit(void);

#endif /* UndoIntegration_h */
Loading

0 comments on commit b29732a

Please sign in to comment.