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;
239 q = local.findQuark(name);
243 "not found in local quarks. Not yet downloaded from the repository ?"
252 ///// convenience methods for Quarks.global
254 *checkoutAll { this.global.repos.checkoutAll(this.global.local.path) }
255 *checkout { | name, version, sync | this.global.checkout(name,version, sync); }
258 return Quark objects for each in App Support/SuperCollider/Quarks
259 (so actually this would list any Quarks that are in local development
260 and not yet checked in) */
261 *checkedOut { ^this.global.checkedOut }
263 this.checkedOut.do { |q| q.postDesc };
266 this.global.listAvailable
269 download/update only the quark specification files from remote repos
270 and not the quarks themselves */
272 ^this.global.repos.updateDirectory
275 ^this.global.checkoutDirectory
277 *update { |quarkName| this.global.update(quarkName) }
280 this symlinks from {App Support}/SuperCollider/Quarks to
281 {App Support}/SuperCollider/Extensions
282 it is then in the SC compile path */
283 *install { |name, includeDependencies=true, checkoutIfNeeded=true|
284 this.global.install(name, includeDependencies, checkoutIfNeeded)
288 return Quark objects for each installed */
289 *installed { ^this.global.installed }
290 *listInstalled { ^this.global.listInstalled }
291 *isInstalled { |name| ^this.global.isInstalled(name) }
293 removes the symlink */
294 *uninstall { |name| ^this.global.uninstall(name) }
297 add code in {App Support}/SuperCollider/Quarks to the remote repos
298 and also adds the quarks file in DIRECTORY */
299 //*add { |name| this.global.add(name) }
301 you may also use standard svn tools within {App Support}/SuperCollider/Quarks */
302 *commit { |name,message| this.global.commit(name,message) }
303 // post the SVN status
304 *status { |name| this.global.status(name) }
306 *local { ^this.global.local }
307 *repos { ^this.global.repos }
308 *help {|name| ^this.global.help(name) }
309 *gui { ^this.global.gui }
312 if( GUI.id === \qt ) { ^QuarksViewQt(this) } { ^QuarksView(this) }
316 // a gui for Quarks. 2007 by LFSaw.de
318 var quarksCtrl, quarks;
320 var window, caption, explanation, views, resetButton, saveButton, warning,
321 scrollview, scrB, flowLayout, /* quarksflow, */ height, maxPerPage, nextButton, prevButton;
323 *new { |quarksCtrl| ^super.new.init(quarksCtrl) }
326 var pageStart = 0, fillPage;
331 scrollview.visible = false;
333 views.do({ |view| view.remove });
335 scrollview.decorator.reset;
336 views = quarks.collect{|quark|
337 var qView = QuarkView.new(scrollview, 500@20, quark,
338 quarksCtrl.installed.detect{|it| it == quark}.notNil);
339 scrollview.decorator.nextLine;
342 scrollview.visible = true;
346 // note, this doesn't actually contact svn
347 // it only reads the DIRECTORY entries you've already checked out
348 quarks = quarksCtrl.repos.quarks.copy;
350 scrB = GUI.window.screenBounds;
351 height = min(quarks.size * 25 + 120, scrB.height - 60);
353 window = GUI.window.new(quarksCtrl.name, Rect.aboutPoint( scrB.center, 250, height.div( 2 )));
354 flowLayout = FlowLayout( window.view.bounds );
355 window.view.decorator = flowLayout;
357 caption = GUI.staticText.new(window, Rect(20,15,400,30));
358 caption.font_( Font.sansSerif( 24 ));
359 caption.string = quarksCtrl.name;
360 window.view.decorator.nextLine;
362 if ( quarks.size == 0 ){
363 GUI.button.new(window, Rect(0, 0, 229, 20))
364 .states_([["checkout Quarks DIRECTORY", nil, Color.gray(0.5, 0.8)]])
365 .action_({ quarksCtrl.checkoutDirectory; });
367 GUI.button.new(window, Rect(0, 0, 229, 20))
368 .states_([["update Quarks DIRECTORY", nil, Color.gray(0.5, 0.8)]])
369 .action_({ quarksCtrl.updateDirectory;});
372 GUI.button.new(window, Rect(0, 0, 200, 20))
373 .states_([["refresh Quarks listing", nil, Color.gray(0.5, 0.8)]])
379 window.view.decorator.nextLine;
381 GUI.button.new(window, Rect(0, 0, 150, 20))
382 .states_([["browse all help", nil, Color.gray(0.5, 0.8)]])
383 .action_({ HelpBrowser.openBrowsePage("Quarks") });
385 GUI.button.new(window, Rect(15,15,150,20))
386 .states_([["open quark directory", nil, Color.gray(0.5, 0.8)]])
388 openOS(quarksCtrl.local.path.escapeChar($ ))
391 resetButton = GUI.button.new(window, Rect(15,15,75,20));
392 resetButton.states = [
393 ["reset", nil, Color.gray(0.5, 0.8)]
395 resetButton.action = { arg butt;
399 saveButton = GUI.button.new(window, Rect(15,15,75,20));
400 saveButton.states = [
401 ["save", nil, Color.blue(1, 0.5)]
403 saveButton.action = { arg butt;
405 warning.string = "Applying changes, please wait";
406 warning.background_(Color(1.0, 1.0, 0.9));
409 qView.toBeInstalled.if({
410 quarksCtrl.install(qView.quark.name);
413 qView.toBeDeinstalled.if({
414 quarksCtrl.uninstall(qView.quark.name);
418 warning.string = "Done. You should now recompile sclang";
419 warning.background_(Color(0.9, 1.0, 0.9));
423 window.view.decorator.nextLine;
424 explanation = GUI.staticText.new(window, Rect(20,15,500,20));
425 explanation.string = "\"+\" -> installed, \"-\" -> not installed, \"*\" -> marked to install, \"x\" -> marked to uninstall";
426 window.view.decorator.nextLine;
428 warning = GUI.staticText.new(window, Rect(20,15,400,30));
429 warning.font_( Font.sansSerif( 18 ));
431 window.view.decorator.nextLine;
432 GUI.staticText.new( window, 492 @ 1 ).background_( Color.grey );
433 window.view.decorator.nextLine;
435 flowLayout.margin_( 0 @0 ).gap_( 0@0 );
436 scrollview = GUI.scrollView.new(window, 500 @ (height - 165))
438 .autohidesScrollers_(true);
439 scrollview.decorator = FlowLayout( Rect( 0, 0, 500, quarks.size * 25 + 20 ));
442 fillPage.(pageStart);
450 var window, lblCaption,
451 btnUpdate, btnHelp, btnUpdateQuarks, btnOpenDir, btnReset, btnApply,
452 lblStatus, lblExplanation, quarksView,
453 infoView, btnQuarkHelp, btnQuarkOpen, btnQuarkClasses,
454 btnQuarkMethods, txtDescription, btnCloseDetails;
455 var quarks, views, curQuark;
456 var refresh, msgWorking, msgDone;
457 var screen, palette, gizmo;
459 *new { |quarksCtrl| ^super.new.init(quarksCtrl) }
465 quarksView.invokeMethod( \clear );
466 quarks = quarksCtrl.repos.quarks.copy;
467 quarksView.canSort = false;
468 views = quarks.collect{|quark|
469 var qView = QuarkViewQt.new(quarksView, quark,
470 quarksCtrl.installed.detect{|it| it == quark}.notNil);
473 quarksView.canSort = true;
474 quarksView.sort( 1 );
475 quarksView.invokeMethod( \resizeColumnToContents, 0 );
476 quarksView.invokeMethod( \resizeColumnToContents, 1 );
480 msgWorking = { arg msg;
481 lblStatus.background = palette.button.blend(Color.yellow,0.2);
482 lblStatus.string = msg;
486 lblStatus.background = palette.button.blend(Color.green,0.2);
487 lblStatus.string = msg;
490 palette = GUI.current.palette;
491 screen = Window.flipY(Window.availableBounds);
492 window = Window( quarksCtrl.name, Rect( 0, 0, 700, screen.height * 0.9 ).center_(screen.center) );
494 lblCaption = StaticText().font_( GUI.font.new(size:16,usePointSize:true) ).string_(quarksCtrl.name);
497 .states_([["Update Quark Listing"]])
498 .toolTip_("Download the latest information and update the Quarks listing")
500 quarksView.enabled = false;
501 msgWorking.value("Downloading the latest information...");
502 AppClock.sched( 0.2, {
504 quarksCtrl.updateDirectory;
507 quarksView.enabled = true;
508 msgDone.value("Quarks listing has been updated with the latest information.")
514 btnUpdateQuarks = Button()
515 .states_([["Update Quarks"]])
516 .toolTip_("Update installed Quarks")
518 quarksView.enabled = false;
519 msgWorking.value("Updating installed Quarks...");
520 AppClock.sched( 0.2, {
525 quarksView.enabled = true;
526 msgDone.value("Quarks have been updated." +
527 " You should recompile the class library for changes to take effect.")
533 btnHelp = Button().states_([["Help"]])
534 .toolTip_("Browse help for all the Quarks")
535 .action_({ HelpBrowser.openBrowsePage("Quarks") });
537 btnOpenDir = Button().states_([["Directory"]])
538 .toolTip_("Open the local Quarks directory")
539 .action_({ openOS(quarksCtrl.local.path) });
542 .states_([["Reset"]])
543 .toolTip_("Clear the marked changes")
544 .action_({ arg butt; views.do(_.reset) });
546 btnApply = Button().states_([["Apply",nil,Color.blue.blend(palette.button,0.6)]])
547 .toolTip_("Apply the marked changes")
549 quarksView.enabled = false;
550 msgWorking.value("Applying changes, please wait...");
551 AppClock.sched( 0.2, {
554 qView.toBeInstalled.if({
555 quarksCtrl.install(qView.quark.name);
558 qView.toBeDeinstalled.if({
559 quarksCtrl.uninstall(qView.quark.name);
564 msgDone.value("Changes applied." +
565 "You should recompile the class library for changes to take effect."
567 quarksView.enabled = true;
572 lblExplanation = StaticText().string_(
573 "\"+\" installed, \"-\" not installed, \"*\" to install, \"x\" to uninstall"
576 lblStatus = StaticText().font_( GUI.font.new( size:12, usePointSize:true ) );
578 quarksView = TreeView()
579 .setProperty( \rootIsDecorated, false )
580 .columns_([nil,"Name","Summary"])
581 .itemPressedAction_({ |v|
582 infoView.visible = true;
584 .onItemChanged_({ |v|
585 var curItem, curView, inst;
586 curItem = v.currentItem;
588 if( curItem.notNil ) {
589 curView = views.detect { |view| view.treeItem == curItem };
590 if( curView.notNil ) {
591 curQuark = curView.quark;
592 txtDescription.string = curQuark.longDesc;
593 btnQuarkOpen.enabled = curQuark.isLocal;
594 inst = quarksCtrl.installed.detect{|it| it == curQuark}.notNil;
595 btnQuarkClasses.enabled = inst;
596 btnQuarkMethods.enabled = inst;
599 infoView.visible = false
603 txtDescription = TextView(bounds:10@10)
604 .font_( GUI.font.new( size:10, usePointSize:true ) )
606 .autohidesScrollers_( true )
607 .hasVerticalScroller_( true )
609 //.minSize_(Size(0,0));
612 btnQuarkHelp = Button()
614 .toolTip_("Show help for this Quark")
616 curQuark.openHelpFile
619 btnQuarkOpen = Button()
620 .states_([["Source"]])
621 .toolTip_("Open the source directory of this Quark")
623 openOS( "%/%".format(Quarks.local.path, curQuark.path) );
626 btnQuarkClasses = Button()
627 .states_([["Classes"]])
628 .toolTip_("Show list of classes defined by this Quark")
631 var cls = curQuark.definesClasses;
632 var tree, item, buts = [
633 Button().states_([["Browse"]]).action_({
634 cls[item.index].browse;
636 Button().states_([["Help"]]).action_({
637 cls[item.index].openHelpFile;
639 Button().states_([["Source"]]).action_({
640 cls[item.index].openCodeFile;
643 buts.do(_.enabled_(false));
644 Window("% Classes".format(curQuark.name)).layout_(
647 .setProperty( \rootIsDecorated, false )
648 .columns_(["Classes"])
649 .onItemChanged_({|v| item = v.currentItem}),
654 cls.do {|c| tree.addItem([c.name.asString])};
655 tree.itemPressedAction = { buts.do(_.enabled_(true)) }
657 tree.addItem(["No classes"]);
659 tree.invokeMethod( \resizeColumnToContents, 0 );
662 btnQuarkMethods = Button()
663 .states_([["Ext methods"]])
664 .toolTip_("Show list of extension methods added by this Quark")
667 var mets = curQuark.definesExtensionMethods;
668 var tree, item, buts = [
669 Button().states_([["Browse"]]).action_({
670 mets[item.index].ownerClass.browse;
672 Button().states_([["Help"]]).action_({
673 mets[item.index].openHelpFile;
675 Button().states_([["Source"]]).action_({
676 mets[item.index].openCodeFile;
679 buts.do(_.enabled_(false));
680 Window("% Extension Methods".format(curQuark.name)).layout_(
683 .setProperty( \rootIsDecorated, false )
684 .columns_(["Class","Method"])
685 .onItemChanged_({|v| item = v.currentItem}),
691 var x = m.ownerClass.name;
692 tree.addItem(if(x.isMetaClassName) {[x.asString[5..],"*"++m.name]} {[x.asString,"-"++m.name]});
694 tree.itemPressedAction = { buts.do(_.enabled_(true)) }
696 tree.addItem([nil,"No extension methods"]);
698 tree.invokeMethod( \resizeColumnToContents, 0 );
699 tree.invokeMethod( \resizeColumnToContents, 1 );
702 btnCloseDetails = StaticText()
705 .toolTip_("Hide Quark information panel")
707 infoView.visible = false;
709 gizmo = btnCloseDetails.sizeHint;
710 gizmo.width = gizmo.width + 20;
711 btnCloseDetails.fixedSize = gizmo;
714 infoView.layout = VLayout(
715 HLayout( btnCloseDetails, btnQuarkHelp, btnQuarkOpen, btnQuarkClasses, btnQuarkMethods, nil ).margins_(0),
717 ).spacing_(0).margins_(0);
718 infoView.visible = false;
723 HLayout( btnUpdate, btnUpdateQuarks, btnOpenDir, btnHelp, nil ),
725 HLayout( btnReset, btnApply, [lblExplanation, s:1] ).margins_(0),