3 * Subversion based package repository and package manager
4 * sk, cx, danstowell, LFSaw
6 * this is the interface class for
7 * accessing the SVN repository, (eg. the sourceforge quarks project)
8 * downloading those to the local quarks folder (Platform.userAppSupportDir/quarks)
9 * installing individual quarks by symlinking from the local quark folder into the [Platform dependent]/Extensions folder
16 classvar global, <allInstances, <known;
19 *global { ^(global ?? { global = this.new; }) }
21 *new { | reposPath, localPath |
23 if((this.known[reposPath].isNil.not) and: (this.known[reposPath] != localPath), {
24 ("The repository is in the list of known repositories, but with a different local path. You are recommended to use this local path:"
25 + this.known[reposPath]).warn;
27 newQ = super.new.initQuarks(
31 allInstances.add(newQ);
34 initQuarks{|reposPath, localPath|
35 local = LocalQuarks(localPath, this);
36 repos = QuarkSVNRepository(reposPath, local);
40 Class.initClassTree( Platform );
41 allInstances = List(1);
43 "https://quarks.svn.sourceforge.net/svnroot/quarks" -> (Platform.userAppSupportDir +/+ "quarks"),
44 "https://svn.sonenvir.at/svnroot/SonEnvir/trunk/src/quarks-sonenvir" -> (Platform.userAppSupportDir +/+ "quarks-sonenvir"),
45 "https://sc3-plugins.svn.sourceforge.net/svnroot/sc3-plugins/build" -> (Platform.userAppSupportDir +/+ "SC3plugins")
50 this.global; // ensure the global one is constructed
51 q = allInstances.detect({|q| q.repos.url == url});
52 if(q.isNil && this.known[url].isNil.not, {
53 q = Quarks.new(url, this.known[url]);
61 if((q = local.findQuark(name)).isNil,{
62 Error("Local Quark "+name+" not found, cannot update from repository").throw;
64 repos.svn("update", (local.path+/+q.path).escapeChar($ ));
71 // this should create or manage the directory file too if it exists
73 if((q = local.findQuark(name)).isNil,{
74 Error("Local Quark code not found, cannot add to repository").throw;
76 repos.svn("add",local.path++"/"++q.path);
78 commit { |name,message|
80 if((q = local.findQuark(name)).isNil,{
81 Error("Local Quark code not found, cannot commit to repository").throw;
84 Error("svn log message required to commit").throw;
86 repos.svn("commit","-m",message,"-F",local.path +/+ q.path);
88 // TODO: Deprecate "checkoutDirectory" - "updateDirectory" can be used whether or not there's an existing one
90 "Please wait for directory to be checked out.".postln;
98 this.checkedOut.do { |q| q.postDesc };
101 this.repos.quarks.do { |q| q.postDesc };
104 checkoutAll { repos.checkoutAll(local.path) }
105 checkout { |name, version, sync=false|
107 if(local.findQuark(name,version).notNil,{
108 inform("Quark "++name++" already checked out");
112 q = repos.findQuark(name,version);
114 Error("Quark not found in repository.").throw;
116 repos.checkout(q, local.path, sync);
118 // DEPRECATED because it has a different and confusing functionality w.r.t. QuarkSVNRepos.checkDir
121 "Quarks.checkDir is deprecated".warn;
122 d = (Platform.userExtensionDir +/+ local.name).escapeChar($ );
123 if(d.pathMatch.isEmpty,{
124 ("creating: " + d).inform;
134 q = local.findQuark(name);
135 repos.svn("status",(local.path +/+ q.path).escapeChar($ ));
137 repos.svn("status",local.path.escapeChar($ ));
141 // of quarks in local, select those also present in userExtensionDir
142 ^local.quarks.select{|q|
143 (Platform.userExtensionDir.escapeChar($ )
149 install { | name , includeDependencies=true, checkoutIfNeeded=true |
150 var q, deps, installed, dirname, quarksForDep;
152 if(this.isInstalled(name),{
153 (name + "already installed").inform;
157 q = local.findQuark(name);
159 if(checkoutIfNeeded) {
160 (name.asString + " not found in local quarks; checking out from remote ...").postln;
161 this.checkout(name, sync: true);
162 q = local.reread.findQuark(name);
164 Error("Quark" + name + "install: checkout failed.").throw;
168 Error(name.asString + "not found in local quarks. Not yet downloaded from the repository ?").throw;
172 if(q.isCompatible.not,{
173 (q.name + " reports that it is not compatible with your current class library. See the help file for further information.").inform;
177 // create /quarks/ directory if needed
178 if(this.repos.checkDir.not){this.checkoutDirectory};
180 // Now ensure that the dependencies are installed (if available given the current active reposses)
181 if(includeDependencies, {
182 q.dependencies(true).do({ |dep|
183 quarksForDep = if(dep.repos.isNil, {this}, {Quarks.forUrl(dep.repos)});
184 if(quarksForDep.isNil, {
185 ("Quarks:install - unable to find repository for dependency '" ++ dep.name
186 ++ "' - you may need to satisfy this dependency manually. No repository detected locally with URL "++dep.repos).warn;
188 if(quarksForDep.isInstalled(dep.name).not, {
190 quarksForDep.install(dep.name, false, checkoutIfNeeded)
192 ("Unable to satisfy dependency of '"++name++"' on '"++dep.name
193 ++"' - you may need to install '"++dep.name++"' manually.").warn;
200 // Ensure the correct folder-hierarchy exists first
201 dirname = (Platform.userExtensionDir +/+ local.name +/+ q.path).dirname;
202 if(File.exists(dirname).not, {
206 // install via symlink to Extensions/<quarks-dir>
207 ("ln -s " + (local.path +/+ q.path).escapeChar($ ) + (Platform.userExtensionDir +/+ local.name +/+ q.path).escapeChar($ )).systemCmd;
208 (q.name + "installed").inform;
211 this.installed.do { |q| q.postDesc };
213 isInstalled { arg name;
214 ^this.installed.detect{|quark| quark.name == name }.notNil
217 var q, deps, installed;
218 name = name.asString;
219 if(this.isInstalled(name).not,{
223 q = local.findQuark(name);
227 "is not found in Local quarks in order to look up its relative path. You may remove the symlink manually."
231 // install via symlink to Extensions/Quarks
232 ("rm " + (Platform.userExtensionDir +/+ local.name +/+ q.path).escapeChar($ )).systemCmd;
233 (q.name + "uninstalled").inform;
241 q = local.findQuark(name);
245 "not found in local quarks. Not yet downloaded from the repository ?"
254 ///// convenience methods for Quarks.global
256 *checkoutAll { this.global.repos.checkoutAll(this.global.local.path) }
257 *checkout { | name, version, sync | this.global.checkout(name,version, sync); }
260 return Quark objects for each in App Support/SuperCollider/Quarks
261 (so actually this would list any Quarks that are in local development
262 and not yet checked in) */
263 *checkedOut { ^this.global.checkedOut }
265 this.checkedOut.do { |q| q.postDesc };
268 this.global.listAvailable
271 download/update only the quark specification files from remote repos
272 and not the quarks themselves */
274 ^this.global.repos.updateDirectory
277 ^this.global.checkoutDirectory
279 *update { |quarkName| this.global.update(quarkName) }
282 this symlinks from {App Support}/SuperCollider/Quarks to
283 {App Support}/SuperCollider/Extensions
284 it is then in the SC compile path */
285 *install { |name, includeDependencies=true, checkoutIfNeeded=true|
286 this.global.install(name, includeDependencies, checkoutIfNeeded)
290 return Quark objects for each installed */
291 *installed { ^this.global.installed }
292 *listInstalled { ^this.global.listInstalled }
293 *isInstalled { |name| ^this.global.isInstalled(name) }
295 removes the symlink */
296 *uninstall { |name| ^this.global.uninstall(name) }
299 add code in {App Support}/SuperCollider/Quarks to the remote repos
300 and also adds the quarks file in DIRECTORY */
301 //*add { |name| this.global.add(name) }
303 you may also use standard svn tools within {App Support}/SuperCollider/Quarks */
304 *commit { |name,message| this.global.commit(name,message) }
305 // post the SVN status
306 *status { |name| this.global.status(name) }
308 *local { ^this.global.local }
309 *repos { ^this.global.repos }
310 *help {|name| ^this.global.help(name) }
311 *gui { ^this.global.gui }
314 if( GUI.id === \qt ) { ^QuarksViewQt(this) } { ^QuarksView(this) }
318 // a gui for Quarks. 2007 by LFSaw.de
320 var quarksCtrl, quarks;
322 var window, caption, explanation, views, resetButton, saveButton, warning,
323 scrollview, scrB, flowLayout, /* quarksflow, */ height, maxPerPage, nextButton, prevButton;
325 *new { |quarksCtrl| ^super.new.init(quarksCtrl) }
328 var pageStart = 0, fillPage;
333 scrollview.visible = false;
335 views.do({ |view| view.remove });
337 scrollview.decorator.reset;
338 views = quarks.sort { |a, b| a.name < b.name }
340 var qView = QuarkView.new(scrollview, 500@20, quark,
341 quarksCtrl.installed.detect { |it| it == quark }.notNil
343 scrollview.decorator.nextLine;
346 scrollview.visible = true;
350 // note, this doesn't actually contact svn
351 // it only reads the DIRECTORY entries you've already checked out
352 quarks = quarksCtrl.repos.quarks.copy;
354 scrB = GUI.window.screenBounds;
355 height = min(quarks.size * 25 + 120, scrB.height - 60);
357 window = GUI.window.new(quarksCtrl.name, Rect.aboutPoint( scrB.center, 250, height.div( 2 )));
358 flowLayout = FlowLayout( window.view.bounds );
359 window.view.decorator = flowLayout;
361 caption = GUI.staticText.new(window, Rect(20,15,400,30));
362 caption.font_( Font.sansSerif( 24 ));
363 caption.string = quarksCtrl.name;
364 window.view.decorator.nextLine;
366 if ( quarks.size == 0 ){
367 GUI.button.new(window, Rect(0, 0, 229, 20))
368 .states_([["checkout Quarks DIRECTORY"]])
369 .action_({ quarksCtrl.checkoutDirectory; });
371 GUI.button.new(window, Rect(0, 0, 229, 20))
372 .states_([["update Quarks DIRECTORY"]])
373 .action_({ quarksCtrl.updateDirectory;});
376 GUI.button.new(window, Rect(0, 0, 200, 20))
377 .states_([["refresh Quarks listing"]])
383 window.view.decorator.nextLine;
385 GUI.button.new(window, Rect(0, 0, 150, 20))
386 .states_([["browse all help"]])
387 .action_({ HelpBrowser.openBrowsePage("Quarks") });
389 GUI.button.new(window, Rect(15,15,150,20))
390 .states_([["open quark directory"]])
392 openOS(quarksCtrl.local.path.escapeChar($ ))
395 resetButton = GUI.button.new(window, Rect(15,15,75,20));
396 resetButton.states = [
399 resetButton.action = { arg butt;
403 saveButton = GUI.button.new(window, Rect(15,15,75,20));
404 saveButton.states = [
405 ["save", nil, Color.blue(1, 0.2)]
407 saveButton.action = { arg butt;
409 warning.string = "Applying changes, please wait";
410 warning.background_(Color(1.0, 1.0, 0.9));
413 qView.toBeInstalled.if({
414 quarksCtrl.install(qView.quark.name);
417 qView.toBeDeinstalled.if({
418 quarksCtrl.uninstall(qView.quark.name);
422 warning.string = "Done. You should now recompile sclang";
423 warning.background_(Color(0.9, 1.0, 0.9));
427 window.view.decorator.nextLine;
428 explanation = GUI.staticText.new(window, Rect(20,15,500,20));
429 explanation.string = "\"+\" -> installed, \"-\" -> not installed, \"*\" -> marked to install, \"x\" -> marked to uninstall";
430 window.view.decorator.nextLine;
432 warning = GUI.staticText.new(window, Rect(20,15,400,30));
433 warning.font_( Font.sansSerif( 18 ));
435 window.view.decorator.nextLine;
436 GUI.staticText.new( window, 492 @ 1 ).background_( Color.grey );
437 window.view.decorator.nextLine;
439 flowLayout.margin_( 0 @0 ).gap_( 0@0 );
440 scrollview = GUI.scrollView.new(window, 500 @ (height - 165))
442 .autohidesScrollers_(true);
443 scrollview.decorator = FlowLayout( Rect( 0, 0, 500, quarks.size * 25 + 20 ));
446 fillPage.(pageStart);
454 var window, lblCaption,
455 btnUpdate, btnHelp, btnUpdateQuarks, btnOpenDir, btnReset, btnApply,
456 lblStatus, lblExplanation, quarksView,
457 infoView, btnQuarkHelp, btnQuarkOpen, btnQuarkClasses,
458 btnQuarkMethods, txtDescription, btnCloseDetails;
459 var quarks, views, curQuark;
460 var refresh, msgWorking, msgDone;
461 var screen, palette, gizmo;
463 *new { |quarksCtrl| ^super.new.init(quarksCtrl) }
469 quarksView.invokeMethod( \clear );
470 quarks = quarksCtrl.repos.quarks.copy;
471 quarksView.canSort = false;
472 views = quarks.collect{|quark|
473 var qView = QuarkViewQt.new(quarksView, quark,
474 quarksCtrl.installed.detect{|it| it == quark}.notNil);
477 quarksView.canSort = true;
478 quarksView.sort( 1 );
479 quarksView.invokeMethod( \resizeColumnToContents, 0 );
480 quarksView.invokeMethod( \resizeColumnToContents, 1 );
484 msgWorking = { arg msg;
485 lblStatus.background = palette.button.blend(Color.yellow,0.2);
486 lblStatus.string = msg;
490 lblStatus.background = palette.button.blend(Color.green,0.2);
491 lblStatus.string = msg;
494 palette = GUI.current.palette;
495 screen = Window.flipY(Window.availableBounds);
496 window = Window( quarksCtrl.name, Rect( 0, 0, 700, screen.height * 0.9 ).center_(screen.center) );
498 lblCaption = StaticText().font_( GUI.font.new(size:16,usePointSize:true) ).string_(quarksCtrl.name);
501 .states_([["Update Quark Listing"]])
502 .toolTip_("Download the latest information and update the Quarks listing")
504 quarksView.enabled = false;
505 msgWorking.value("Downloading the latest information...");
506 AppClock.sched( 0.2, {
508 quarksCtrl.updateDirectory;
511 quarksView.enabled = true;
512 msgDone.value("Quarks listing has been updated with the latest information.")
518 btnUpdateQuarks = Button()
519 .states_([["Update Quarks"]])
520 .toolTip_("Update installed Quarks")
522 quarksView.enabled = false;
523 msgWorking.value("Updating installed Quarks...");
524 AppClock.sched( 0.2, {
529 quarksView.enabled = true;
530 msgDone.value("Quarks have been updated." +
531 " You should recompile the class library for changes to take effect.")
537 btnHelp = Button().states_([["Help"]])
538 .toolTip_("Browse help for all the Quarks")
539 .action_({ HelpBrowser.openBrowsePage("Quarks") });
541 btnOpenDir = Button().states_([["Directory"]])
542 .toolTip_("Open the local Quarks directory")
543 .action_({ openOS(quarksCtrl.local.path) });
546 .states_([["Reset"]])
547 .toolTip_("Clear the marked changes")
548 .action_({ arg butt; views.do(_.reset) });
550 btnApply = Button().states_([["Apply",nil,Color.blue.blend(palette.button,0.6)]])
551 .toolTip_("Apply the marked changes")
553 quarksView.enabled = false;
554 msgWorking.value("Applying changes, please wait...");
555 AppClock.sched( 0.2, {
558 qView.toBeInstalled.if({
559 quarksCtrl.install(qView.quark.name);
562 qView.toBeDeinstalled.if({
563 quarksCtrl.uninstall(qView.quark.name);
568 msgDone.value("Changes applied." +
569 "You should recompile the class library for changes to take effect."
571 quarksView.enabled = true;
576 lblExplanation = StaticText().string_(
577 "\"+\" installed, \"-\" not installed, \"*\" to install, \"x\" to uninstall"
580 lblStatus = StaticText().font_( GUI.font.new( size:12, usePointSize:true ) );
582 quarksView = TreeView()
583 .setProperty( \rootIsDecorated, false )
584 .columns_([nil,"Name","Summary"])
585 .itemPressedAction_({ |v|
586 infoView.visible = true;
588 .onItemChanged_({ |v|
589 var curItem, curView, inst;
590 curItem = v.currentItem;
592 if( curItem.notNil ) {
593 curView = views.detect { |view| view.treeItem == curItem };
594 if( curView.notNil ) {
595 curQuark = curView.quark;
596 txtDescription.string = curQuark.longDesc;
597 btnQuarkOpen.enabled = curQuark.isLocal;
598 inst = quarksCtrl.installed.detect{|it| it == curQuark}.notNil;
599 btnQuarkClasses.enabled = inst;
600 btnQuarkMethods.enabled = inst;
603 infoView.visible = false
607 txtDescription = TextView(bounds:10@10)
608 .font_( GUI.font.new( size:10, usePointSize:true ) )
610 .autohidesScrollers_( true )
611 .hasVerticalScroller_( true )
613 //.minSize_(Size(0,0));
616 btnQuarkHelp = Button()
618 .toolTip_("Show help for this Quark")
623 btnQuarkOpen = Button()
624 .states_([["Source"]])
625 .toolTip_("Open the source directory of this Quark")
627 openOS( "%/%".format(Quarks.local.path, curQuark.path) );
630 btnQuarkClasses = Button()
631 .states_([["Classes"]])
632 .toolTip_("Show list of classes defined by this Quark")
635 var cls = curQuark.definesClasses;
636 var tree, item, buts = [
637 Button().states_([["Browse"]]).action_({
638 cls[item.index].browse;
640 Button().states_([["Help"]]).action_({
641 cls[item.index].help;
643 Button().states_([["Source"]]).action_({
644 cls[item.index].openCodeFile;
647 buts.do(_.enabled_(false));
648 Window("% Classes".format(curQuark.name)).layout_(
651 .setProperty( \rootIsDecorated, false )
652 .columns_(["Classes"])
653 .onItemChanged_({|v| item = v.currentItem}),
658 cls.do {|c| tree.addItem([c.name.asString])};
659 tree.itemPressedAction = { buts.do(_.enabled_(true)) }
661 tree.addItem(["No classes"]);
663 tree.invokeMethod( \resizeColumnToContents, 0 );
666 btnQuarkMethods = Button()
667 .states_([["Ext methods"]])
668 .toolTip_("Show list of extension methods added by this Quark")
671 var mets = curQuark.definesExtensionMethods;
672 var tree, item, buts = [
673 Button().states_([["Browse"]]).action_({
674 mets[item.index].ownerClass.browse;
676 Button().states_([["Help"]]).action_({
677 mets[item.index].help;
679 Button().states_([["Source"]]).action_({
680 mets[item.index].openCodeFile;
683 buts.do(_.enabled_(false));
684 Window("% Extension Methods".format(curQuark.name)).layout_(
687 .setProperty( \rootIsDecorated, false )
688 .columns_(["Class","Method"])
689 .onItemChanged_({|v| item = v.currentItem}),
695 var x = m.ownerClass.name;
696 tree.addItem(if(x.isMetaClassName) {[x.asString[5..],"*"++m.name]} {[x.asString,"-"++m.name]});
698 tree.itemPressedAction = { buts.do(_.enabled_(true)) }
700 tree.addItem([nil,"No extension methods"]);
702 tree.invokeMethod( \resizeColumnToContents, 0 );
703 tree.invokeMethod( \resizeColumnToContents, 1 );
706 btnCloseDetails = StaticText()
709 .toolTip_("Hide Quark information panel")
711 infoView.visible = false;
713 gizmo = btnCloseDetails.sizeHint;
714 gizmo.width = gizmo.width + 20;
715 btnCloseDetails.fixedSize = gizmo;
718 infoView.layout = VLayout(
719 HLayout( btnCloseDetails, btnQuarkHelp, btnQuarkOpen, btnQuarkClasses, btnQuarkMethods, nil ).margins_(0),
721 ).spacing_(0).margins_(0);
722 infoView.visible = false;
727 HLayout( btnUpdate, btnUpdateQuarks, btnOpenDir, btnHelp, nil ),
729 HLayout( btnReset, btnApply, [lblExplanation, s:1] ).margins_(0),