Chris Hermansen 发布的文章

通过简单的设置使用你家中的音响收听你最爱的网络电台。

 title=

最近网络媒体对 Google 的 Chromecast 音频设备的下架发出叹息。该设备在音频媒体界备受好评,因此我已经在考虑入手一个。基于 Chromecast 退场的消息,我决定在它们全部被打包扔进垃圾堆之前以一个合理价位买一个。

我在 MobileFun 上找到一个放进我的订单中。这个设备最终到货了。它被包在一个普普通通、简简单单的 Google 包装袋中,外面打印着非常简短的使用指南。

 title=

我通过我的数模转换器的光纤 S/PDIF 连接接入到家庭音响,希望以此能提供最佳的音质。

安装过程并无纰漏,在五分钟后我就可以播放一些音乐了。我知道一些安卓应用支持 Chromecast,因此我决定用 Google Play Music 测试它。意料之中,它工作得不错,音乐效果听上去也相当好。然而作为一个具有开源精神的人,我决定看看我能找到什么开源播放器能兼容 Chromecast。

RadioDroid 的救赎

RadioDroid 安卓应用 满足条件。它是开源的,并且可从 GitHub、Google Play 以及 F-Droid 上获取。根据帮助文档,RadioDroid 从 Community Radio Browser 网页寻找播放流。因此我决定在我的手机上安装尝试一下。

 title=

安装过程快速顺利,RadioDroid 打开展示当地电台十分迅速。你可以在这个屏幕截图的右上方附近看到 Chromecast 按钮(看上去像一个有着波阵面的长方形图标)。

我尝试了几个当地电台。这个应用可靠地在我手机喇叭上播放了音乐。但是我不得不摆弄 Chromecast 按钮来通过 Chromecast 把音乐传到流上。但是它确实可以做到流传输。

我决定找一下我喜爱的网络广播电台:法国马赛的 格雷诺耶广播电台。在 RadioDroid 上有许多找到电台的方法。其中一种是使用标签——“当地”、“最流行”等——就在电台列表上方。其中一个标签是国家,我找到法国,在其 1500 个电台中划来划去寻找格雷诺耶广播电台。另一种办法是使用屏幕上方的查询按钮;查询迅速找到了那家美妙的电台。我尝试了其它几次查询它们都返回了合理的信息。

回到“当地”标签,我在列表中翻来覆去,发现“当地”的定义似乎是“在同一个国家”。因此尽管西雅图、波特兰、旧金山、洛杉矶和朱诺比多伦多更靠近我的家,我并没有在“当地”标签中看到它们。然而通过使用查询功能,我可以发现所有名字中带有西雅图的电台。

“语言”标签使我找到所有用葡语(及葡语方言)播报的电台。我很快发现了另一个最爱的电台 91 Rock Curitiba

接着灵感来了,虽然现在是春天了,但又如何呢?让我们听一些圣诞音乐。意料之中,搜寻圣诞把我引到了 181.FM – Christmas Blender。不错,一两分钟的欣赏对我就够了。

因此总的来说,我推荐把 RadioDroid 和 Chromecast 的组合作为一种用家庭音响以合理价位播放网络电台的良好方式。

对于音乐方面……

最近我从 Blue Coast Music 商店里选了一个 Qua Continuum 创作的叫作 Continuum One 的有趣的氛围(甚至无节拍)音乐专辑。

Blue Coast 有许多可提供给开源音乐爱好者的。音乐可以无需通过那些奇怪的平台专用下载管理器下载(有时以物理形式)。它通常提供几种形式,包括 WAV、FLAC 和 DSD;WAV 和 FLAC 还提供不同的字长和比特率,包括 16/44.1、24/96 和 24/192,针对 DSD 则有 2.8、5.6 和 11.2 MHz。音乐是用优秀的仪器精心录制的。不幸的是,我并没有找到许多符合我口味的音乐,尽管我喜欢 Blue Coast 上能获取的几个艺术家,包括 Qua Continuum,Art Lande 以及 Alex De Grassi

Bandcamp 上,我挑选了 Emancipator’s BaralkuFramework’s Tides,两个都是我喜欢的。两位艺术家创作的音乐符合我的口味——电音但又(总体来说)不是舞蹈,它们的音乐旋律优美,副歌也很好听。有许多可以让开源音乐发烧友爱上 Bandcamp 的东西,比如买前试听整首歌的服务;没有垃圾软件下载器;与大量音乐家的合作;以及对 Creative Commons music 的支持。


via: https://opensource.com/article/19/4/radiodroid-internet-radio-player

作者:Chris Hermansen (Community Moderator) 选题:lujun9972 译者:tomjlw 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出

Linux 桌面环境使你可以根据需要轻松设置键盘。下面来演示如何去做。

对于许多使用计算机很多年的用户来说,自从第一批 PC 键盘从生产线上下线后不久,Ctrl 和大写锁定键就已经在错误的位置上了。对我来说,这张 1995 年 Sun 工作站的老式键盘照片上的两个键的位置才是正确的。(原谅我放了一张模糊的图片,它是在昏暗的光线下使用 Minox 间谍相机拍摄的。)

感兴趣的话,可以读一下维基百科上对于 Ctrl 键位置的历史 的介绍。我不打算讨论将 Ctrl 键放在“a”旁边而不是 Shift 键下方的各种理由,也不评论大写锁定键的无用性,也没有打算与那些主张使用手掌根来触发 Ctrl 键的人争论,即使在一些笔记本电脑键盘上不可能这样做到,因为有的键会位于腕托以下。

相反,我将假设我不是唯一喜欢把 Ctrl 键放在“a”旁边的人,并说明如何使用 Linux 自带的灵活性在各种桌面环境中交换 Ctrl 和大写锁定键的位置。请注意,下面的演示可能只有有限的有效期,因为调整桌面设置的方法经常发生变化,但我希望这为你开了一个好头。

GNOME 3

GNOME 3 桌面环境用户可以使用 Tweaks 工具交换大写锁定和 Ctrl 键,如下所示。

具体步骤如下:

  1. 从你的 Linux 发行版的软件仓库安装 Tweaks 工具。
  2. 启动 Tweaks 程序。
  3. 从左侧菜单中选择 “Keyboard & Mouse”。
  4. 单击 “Additional Layout Options”。
  5. 在打开的窗口中单击 “Ctrl position”,然后选择 “Swap Ctrl and Caps Lock”。

完成!顺便说一句,你可以使用 Tweaks 工具做很多很酷的事情。例如,我将我的右 Ctrl 键设置为 Compose 键,这让我可以使用键盘快捷键打出各种字符,例如通过 Compose+c+,Compose+e+'Compose+o+^ 以及 Compose+n+~ 分别键入 ç、é、ô 和 ñ。(LCTT 译注:可参考 Special characters listed by extended compose sequence

KDE

我不使用 KDE,但我的同事 Seth Kenlon 写的 将改变你的生命的 KDE tweaks 这篇文章的第 5 项演示了如何重新映射按键。

Xfce

据我所知,Xfce 桌面环境没有一个方便的工具来管理这些(指交换按键)设置。 但是,setxkbmap 命令的 ctrl:swapcaps 选项可以帮助你完成交换按键的修改。这个修改包含两部分:

  1. 弄清楚命令的用法;
  2. 找出调用命令的位置,以便在桌面启动时激活它。

第一部分非常简单,命令是:

/usr/bin/setxkbmap -option "ctrl:nocaps"

在终端窗口中执行此命令,以确保结果符合你的预期。

假设上述命令有效,应该在哪里调用此命令呢?这需要一些实验。一种可能是在用户主目录的 .profile 文件中;另一个可能是将命令添加到 Xfce 的自启动配置(在设置管理器中查找 “Session and Startup”)里。

还有一种可能性是在文件 /etc/default/keyboard 中使用相同的选项,最终可能看起来像这样:

# KEYBOARD CONFIGURATION FILE

# Consult the keyboard(5) manual page.

XKBMODEL="pc105"
XKBLAYOUT="us"
XKBVARIANT=""
XKBOPTIONS="ctrl:swapcaps"

BACKSPACE="guess"

注意,这个更改将影响所有用户,因此如果你和其他人共享计算机,请准备好进行一些说明。此外,系统更新可能会覆盖此文件,因此如果你的设置失效了,就需要再次编辑它。将相同的信息放在用户主目录中的 .keyboard 文件内,可以为每个用户进行设置。

最后请注意,这些更改需要重新启动 Xfce(除非在终端窗口中的命令行上运行,但这在会话结束之后便会失效)。

LXQt 和其他桌面环境

我没有用过 LXQt,但根据我使用 LXDE 的经验,我会尝试上面用于 Xfce 的方法。我也希望适用于 Xfce 的方法可以用于其他 Linux 桌面环境。当然了,在其他桌面环境上遇到问题的时候,可以通过你最喜欢的搜索引擎来查找解决办法。

控制台

我没有在控制台上进行过尝试,因为我很少有机会与控制台(你在服务器上看到的或你的窗口系统没有正确显示时出现的界面)进行交互。上面给出的方法以人们希望的方式(即与其他应用程序一致)调整终端窗口。

但是,如果像上面一样已经编辑了 /etc/default/keyboard 文件或 〜/.keyboard,则实用程序 setupcon 可以用于更改控制台的键盘设置,以便实现相同的功能。链接 1链接 2链接 3 给出了一些关于如何从这两个文件实现这些更改的想法。第三个链接还讨论了使用 dumpkeysloadkeys 来实现想要的效果。setupcon 的手册 简短而重要,值得阅读,再结合上面 StackExchange 问题的一些评论,应该足以得到一个解决办法。

其他环境

最后,上面 StackExchange 的链接中提到的这一点值得强调 —— 配置控制台与配置终端窗口不同;如前所述,后者是通过桌面管理器进行配置的。

setxkbmapxkeyboard-configkeyboardconsole-setupsetupcon 命令的手册都是有用的参考资料。或者,如果你不喜欢阅读手册,可以看一下 这篇极好的文章


via: https://opensource.com/article/18/11/how-swap-ctrl-and-caps-lock-your-keyboard

作者:Chris Hermansen 选题:lujun9972 译者:jlztan 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出

本文介绍如何构建一个基于 Grails 的数据浏览器来可视化复杂的表格数据。

我是 Grails 的忠实粉丝。当然,我主要是热衷于利用命令行工具来探索和分析数据的数据从业人员。数据从业人员经常需要查看数据,这也意味着他们通常拥有优秀的数据浏览器。利用 Grails、jQuery,以及 DataTables jQuery 插件,我们可以制作出非常友好的表格数据浏览器。

DataTables 网站提供了许多“食谱式”的教程文档,展示了如何组合一些优秀的示例应用程序,这些程序包含了完成一些非常漂亮的东西所必要的 JavaScript、HTML,以及偶尔出现的 PHP。但对于那些宁愿使用 Grails 作为后端的人来说,有必要进行一些说明示教。此外,样本程序中使用的数据是一个虚构公司的员工的单个平面表格数据,因此处理这些复杂的表关系可以作为读者的一个练习项目。

本文中,我们将创建具有略微复杂的数据结构和 DataTables 浏览器的 Grails 应用程序。我们将介绍 Grails 标准,它是 Groovy 式的 Java Hibernate 标准。我已将代码托管在 GitHub 上方便大家访问,因此本文主要是对代码细节的解读。

首先,你需要配置 Java、Groovy、Grails 的使用环境。对于 Grails,我倾向于使用终端窗口和 Vim,本文也使用它们。为获得现代的 Java 环境,建议下载并安装 Linux 发行版提供的 Open Java Development Kit (OpenJDK)(应该是 Java 8、9、10 或 11 之一,撰写本文时,我正在使用 Java 8)。从我的角度来看,获取最新的 Groovy 和 Grails 的最佳方法是使用 SDKMAN!

从未尝试过 Grails 的读者可能需要做一些背景资料阅读。作为初学者,推荐文章 创建你的第一个 Grails 应用程序

获取员工信息浏览器应用程序

正如上文所提,我将本文中员工信息浏览器的源代码托管在 GitHub上。进一步讲,应用程序 embrow 是在 Linux 终端中用如下命令构建的:

cd Projects
grails create-app com.nuevaconsulting.embrow

域类和单元测试创建如下:

grails create-domain-class com.nuevaconsulting.embrow.Position
grails create-domain-class com.nuevaconsulting.embrow.Office
grails create-domain-class com.nuevaconsulting.embrow.Employeecd embrowgrails createdomaincom.grails createdomaincom.grails createdomaincom.

这种方式构建的域类没有属性,因此必须按如下方式编辑它们:

Position 域类:

package com.nuevaconsulting.embrow
 
class Position {

    String name
    int starting

    static constraints = {
        name nullable: false, blank: false
        starting nullable: false
    }
}com.Stringint startingstatic constraintsnullableblankstarting nullable

Office 域类:

package com.nuevaconsulting.embrow
 
class Office {

    String name
    String address
    String city
    String country

    static constraints = {
        name nullable: false, blank: false
        address nullable: false, blank: false
        city nullable: false, blank: false
        country nullable: false, blank: false
    }
}

Enployee 域类:

package com.nuevaconsulting.embrow
 
class Employee {

    String surname
    String givenNames
    Position position
    Office office
    int extension
    Date hired
    int salary
    static constraints = {
        surname nullable: false, blank: false
        givenNames nullable: false, blank: false
        : false
        office nullable: false
        extension nullable: false
        hired nullable: false
        salary nullable: false
    }
}

请注意,虽然 PositionOffice 域类使用了预定义的 Groovy 类型 String 以及 int,但 Employee 域类定义了 PositionOffice 字段(以及预定义的 Date)。这会导致创建数据库表,其中存储的 Employee 实例中包含了指向存储 PositionOffice 实例表的引用或者外键。

现在你可以生成控制器,视图,以及其他各种测试组件:

-all com.nuevaconsulting.embrow.Position
grails generate-all com.nuevaconsulting.embrow.Office
grails generate-all com.nuevaconsulting.embrow.Employeegrails generateall com.grails generateall com.grails generateall com.

此时,你已经准备好了一个基本的增删改查(CRUD)应用程序。我在 grails-app/init/com/nuevaconsulting/BootStrap.groovy 中包含了一些基础数据来填充表格。

如果你用如下命令来启动应用程序:

grails run-app

在浏览器输入 http://localhost:8080/,你将会看到如下界面:

Embrow home screen

Embrow 应用程序主界面。

单击 “OfficeController” 链接,会跳转到如下界面:

Office list

Office 列表

注意,此表由 OfficeControllerindex 方式生成,并由视图 office/index.gsp 显示。

同样,单击 “EmployeeController” 链接 跳转到如下界面:

Employee controller

employee 控制器

好吧,这很丑陋: Position 和 Office 链接是什么?

上面的命令 generate-all 生成的视图创建了一个叫 index.gsp 的文件,它使用 Grails <f:table/> 标签,该标签默认会显示类名(com.nuevaconsulting.embrow.Position)和持久化示例标识符(30)。这个操作可以自定义用来产生更好看的东西,并且自动生成链接,自动生成分页以及自动生成可排序列的一些非常简洁直观的东西。

但该员工信息浏览器功能也是有限的。例如,如果想查找 “position” 信息中包含 “dev” 的员工该怎么办?如果要组合排序,以姓氏为主排序关键字,“office” 为辅助排序关键字,该怎么办?或者,你需要将已排序的数据导出到电子表格或 PDF 文档以便通过电子邮件发送给无法访问浏览器的人,该怎么办?

jQuery DataTables 插件提供了这些所需的功能。允许你创建一个完成的表格数据浏览器。

创建员工信息浏览器视图和控制器的方法

要基于 jQuery DataTables 创建员工信息浏览器,你必须先完成以下两个任务:

  1. 创建 Grails 视图,其中包含启用 DataTable 所需的 HTML 和 JavaScript
  2. 给 Grails 控制器增加一个方法来控制新视图。

员工信息浏览器视图

在目录 embrow/grails-app/views/employee 中,首先复制 index.gsp 文件,重命名为 browser.gsp

cd Projects
cd embrow/grails-app/views/employee
cp gsp browser.gsp

此刻,你自定义新的 browser.gsp 文件来添加相关的 jQuery DataTables 代码。

通常,在可能的时候,我喜欢从内容提供商处获得 JavaScript 和 CSS;在下面这行后面:

<title><g:message code="default.list.label" args="[entityName]" /></title>

插入如下代码:

<script src="https://code.jquery.com/jquery-2.2.4.min.js" integrity="sha256-BbhdlvQf/xTY9gja0Dq3HiwQF8LaCRTXxZKRutelT44=" crossorigin="anonymous"></script>
<link rel="stylesheet" type="text/css" href="https://cdn.datatables.net/1.10.16/css/jquery.dataTables.css">
<script type="text/javascript" charset="utf8" src="https://cdn.datatables.net/1.10.16/js/jquery.dataTables.js"></script>
<link rel="stylesheet" type="text/css" href="https://cdn.datatables.net/scroller/1.4.4/css/scroller.dataTables.min.css">
<script type="text/javascript" charset="utf8" src="https://cdn.datatables.net/scroller/1.4.4/js/dataTables.scroller.min.js"></script>
<script type="text/javascript" charset="utf8" src="https://cdn.datatables.net/buttons/1.5.1/js/dataTables.buttons.min.js"></script>
<script type="text/javascript" charset="utf8" src="https://cdn.datatables.net/buttons/1.5.1/js/buttons.flash.min.js"></script>
<script type="text/javascript" charset="utf8" src="https://cdnjs.cloudflare.com/ajax/libs/jszip/3.1.3/jszip.min.js"></script>
<script type="text/javascript" charset="utf8" src="https://cdnjs.cloudflare.com/ajax/libs/pdfmake/0.1.32/pdfmake.min.js"></script>
<script type="text/javascript" charset="utf8" src="https://cdnjs.cloudflare.com/ajax/libs/pdfmake/0.1.32/vfs_fonts.js"></script>
<script type="text/javascript" charset="utf8" src="https://cdn.datatables.net/buttons/1.5.1/js/buttons.html5.min.js"></script>
<script type="text/javascript" charset="utf8" src="https://cdn.datatables.net/buttons/1.5.1/js/buttons.print.min.js "></script>

然后删除 index.gsp 中提供数据分页的代码:

<div id="list-employee" class="content scaffold-list" role="main">
<h1><g:message code="default.list.label" args="[entityName]" /></h1>
<g:if test="${flash.message}">
<div class="message" role="status">${flash.message}</div>
</g:if>
<f:table collection="${employeeList}" />

<div class="pagination">
<g:paginate total="${employeeCount ?: 0}" />
</div>
</div>

并插入实现 jQuery DataTables 的代码。

要插入的第一部分是 HTML,它将创建浏览器的基本表格结构。DataTables 与后端通信的应用程序来说,它们只提供表格页眉和页脚;DataTables JavaScript 则负责表中内容。

<div id="employee-browser" class="content" role="main">
<h1>Employee Browser</h1>
<table id="employee_dt" class="display compact" style="width:99%;">
<thead>
<tr>
<th>Surname</th>
<th>Given name(s)</th>
<th>Position</th>
<th>Office</th>
<th>Extension</th>
<th>Hired</th>
<th>Salary</th>
</tr>
</thead>
<tfoot>
<tr>
<th>Surname</th>
<th>Given name(s)</th>
<th>Position</th>
<th>Office</th>
<th>Extension</th>
<th>Hired</th>
<th>Salary</th>
</tr>
</tfoot>
</table>
</div>

接下来,插入一个 JavaScript 块,它主要提供三个功能:它设置页脚中显示的文本框的大小,以进行列过滤,建立 DataTables 表模型,并创建一个处理程序来进行列过滤。

<g:javascript>
$('#employee_dt tfoot th').each( function() {javascript

下面的代码处理表格列底部的过滤器框的大小:

var title = $(this).text();
if (title == 'Extension' || title == 'Hired')
$(this).html('<input type="text" size="5" placeholder="' + title + '?" />');
else
$(this).html('<input type="text" size="15" placeholder="' + title + '?" />');
});titletitletitletitletitle

接下来,定义表模型。这是提供所有表选项的地方,包括界面的滚动,而不是分页,根据 DOM 字符串提供的装饰,将数据导出为 CSV 和其他格式的能力,以及建立与服务器的 AJAX 连接。 请注意,使用 Groovy GString 调用 Grails createLink() 的方法创建 URL,在 EmployeeController 中指向 browserLister 操作。同样有趣的是表格列的定义。此信息将发送到后端,后端查询数据库并返回相应的记录。

var table = $('#employee_dt').DataTable( {
"scrollY": 500,
"deferRender": true,
"scroller": true,
"dom": "Brtip",
"buttons": [ 'copy', 'csv', 'excel', 'pdf', 'print' ],
"processing": true,
"serverSide": true,
"ajax": {
"url": "${createLink(controller: 'employee', action: 'browserLister')}",
"type": "POST",
},
"columns": [
{ "data": "surname" },
{ "data": "givenNames" },
{ "data": "position" },
{ "data": "office" },
{ "data": "extension" },
{ "data": "hired" },
{ "data": "salary" }
]
});

最后,监视过滤器列以进行更改,并使用它们来应用过滤器。

table.columns().every(function() {
var that = this;
$('input', this.footer()).on('keyup change', function(e) {
if (that.search() != this.value && 8 < e.keyCode && e.keyCode < 32)
that.search(this.value).draw();
});

这就是 JavaScript,这样就完成了对视图代码的更改。

});
</g:javascript>

以下是此视图创建的UI的屏幕截图:

这是另一个屏幕截图,显示了过滤和多列排序(寻找 “position” 包括字符 “dev” 的员工,先按 “office” 排序,然后按姓氏排序):

这是另一个屏幕截图,显示单击 CSV 按钮时会发生什么:

最后,这是一个截图,显示在 LibreOffice 中打开的 CSV 数据:

好的,视图部分看起来非常简单;因此,控制器必须做所有繁重的工作,对吧? 让我们来看看……

控制器 browserLister 操作

回想一下,我们看到过这个字符串:

"${createLink(controller: 'employee', action: 'browserLister')}"

对于从 DataTables 模型中调用 AJAX 的 URL,是在 Grails 服务器上动态创建 HTML 链接,其 Grails 标记背后通过调用 createLink() 的方法实现的。这会最终产生一个指向 EmployeeController 的链接,位于:

embrow/grails-app/controllers/com/nuevaconsulting/embrow/EmployeeController.groovy

特别是控制器方法 browserLister()。我在代码中留了一些 print 语句,以便在运行时能够在终端看到中间结果。

    def browserLister() {
        // Applies filters and sorting to return a list of desired employees

首先,打印出传递给 browserLister() 的参数。我通常使用此代码开始构建控制器方法,以便我完全清楚我的控制器正在接收什么。

      println "employee browserLister params $params"
        println()

接下来,处理这些参数以使它们更加有用。首先,jQuery DataTables 参数,一个名为 jqdtParams 的 Groovy 映射:

def jqdtParams = [:]
params.each { key, value ->
    def keyFields = key.replace(']','').split(/\[/)
    def table = jqdtParams
    for (int f = 0; f < keyFields.size() - 1; f++) {
        def keyField = keyFields[f]
        if (!table.containsKey(keyField))
            table[keyField] = [:]
        table = table[keyField]
    }
    table[keyFields[-1]] = value
}
println "employee dataTableParams $jqdtParams"
println()

接下来,列数据,一个名为 columnMap 的 Groovy 映射:

def columnMap = jqdtParams.columns.collectEntries { k, v ->
    def whereTerm = null
    switch (v.data) {
    case 'extension':
    case 'hired':
    case 'salary':
        if (v.search.value ==~ /\d+(,\d+)*/)
            whereTerm = v.search.value.split(',').collect { it as Integer }
        break
    default:
        if (v.search.value ==~ /[A-Za-z0-9 ]+/)
            whereTerm = "%${v.search.value}%" as String
        break
    }
    [(v.data): [where: whereTerm]]
}
println "employee columnMap $columnMap"
println()

接下来,从 columnMap 中检索的所有列表,以及在视图中应如何排序这些列表,Groovy 列表分别称为 allColumnListorderList

def allColumnList = columnMap.keySet() as List
println "employee allColumnList $allColumnList"
def orderList = jqdtParams.order.collect { k, v -> [allColumnList[v.column as Integer], v.dir] }
println "employee orderList $orderList"

我们将使用 Grails 的 Hibernate 标准实现来实际选择要显示的元素以及它们的排序和分页。标准要求过滤器关闭;在大多数示例中,这是作为标准实例本身的创建的一部分给出的,但是在这里我们预先定义过滤器闭包。请注意,在这种情况下,“date hired” 过滤器的相对复杂的解释被视为一年并应用于建立日期范围,并使用 createAlias 以允许我们进入相关类别 PositionOffice

def filterer = {
    createAlias 'position',        'p'
    createAlias 'office',          'o'

    if (columnMap.surname.where)    ilike  'surname',     columnMap.surname.where
    if (columnMap.givenNames.where) ilike  'givenNames',  columnMap.givenNames.where
    if (columnMap.position.where)   ilike  'p.name',      columnMap.position.where
    if (columnMap.office.where)     ilike  'o.name',      columnMap.office.where
    if (columnMap.extension.where)  inList 'extension',   columnMap.extension.where
    if (columnMap.salary.where)     inList 'salary',      columnMap.salary.where
    if (columnMap.hired.where) {
        if (columnMap.hired.where.size() > 1) {
            or {
                columnMap.hired.where.each {
                    between 'hired', Date.parse('yyyy/MM/dd',"${it}/01/01" as String),
                        Date.parse('yyyy/MM/dd',"${it}/12/31" as String)
                }
            }
        } else {
            between 'hired', Date.parse('yyyy/MM/dd',"${columnMap.hired.where[0]}/01/01" as String),
                Date.parse('yyyy/MM/dd',"${columnMap.hired.where[0]}/12/31" as String)
        }
    }
}

是时候应用上述内容了。第一步是获取分页代码所需的所有 Employee 实例的总数:

        def recordsTotal = Employee.count()
        println "employee recordsTotal $recordsTotal"

接下来,将过滤器应用于 Employee 实例以获取过滤结果的计数,该结果将始终小于或等于总数(同样,这是针对分页代码):

        def c = Employee.createCriteria()
        def recordsFiltered = c.count {
            filterer.delegate = delegate
            filterer()
        }
        println "employee recordsFiltered $recordsFiltered"

获得这两个计数后,你还可以使用分页和排序信息获取实际过滤的实例。

      def orderer = Employee.withCriteria {
            filterer.delegate = delegate
            filterer()
            orderList.each { oi ->
                switch (oi[0]) {
                case 'surname':    order 'surname',    oi[1]; break
                case 'givenNames': order 'givenNames', oi[1]; break
                case 'position':   order 'p.name',     oi[1]; break
                case 'office':     order 'o.name',     oi[1]; break
                case 'extension':  order 'extension',  oi[1]; break
                case 'hired':      order 'hired',      oi[1]; break
                case 'salary':     order 'salary',     oi[1]; break
                }
            }
            maxResults (jqdtParams.length as Integer)
            firstResult (jqdtParams.start as Integer)
        }

要完全清楚,JTable 中的分页代码管理三个计数:数据集中的记录总数,应用过滤器后得到的数字,以及要在页面上显示的数字(显示是滚动还是分页)。 排序应用于所有过滤的记录,并且分页应用于那些过滤的记录的块以用于显示目的。

接下来,处理命令返回的结果,在每行中创建指向 EmployeePositionOffice 实例的链接,以便用户可以单击这些链接以获取相关实例的所有详细信息:

        def dollarFormatter = new DecimalFormat('$##,###.##')
        def employees = orderer.collect { employee ->
            ['surname': "<a href='${createLink(controller: 'employee', action: 'show', id: employee.id)}'>${employee.surname}</a>",
                'givenNames': employee.givenNames,
                'position': "<a href='${createLink(controller: 'position', action: 'show', id: employee.position?.id)}'>${employee.position?.name}</a>",
                'office': "<a href='${createLink(controller: 'office', action: 'show', id: employee.office?.id)}'>${employee.office?.name}</a>",
                'extension': employee.extension,
                'hired': employee.hired.format('yyyy/MM/dd'),
                'salary': dollarFormatter.format(employee.salary)]
        }

最后,创建要返回的结果并将其作为 JSON 返回,这是 jQuery DataTables 所需要的。

        def result = [draw: jqdtParams.draw, recordsTotal: recordsTotal, recordsFiltered: recordsFiltered, data: employees]
        render(result as JSON)
    }

大功告成。

如果你熟悉 Grails,这可能看起来比你原先想象的要多,但这里没有火箭式的一步到位方法,只是很多分散的操作步骤。但是,如果你没有太多接触 Grails(或 Groovy),那么需要了解很多新东西 - 闭包,代理和构建器等等。

在那种情况下,从哪里开始? 最好的地方是了解 Groovy 本身,尤其是 Groovy closuresGroovy delegates and builders。然后再去阅读上面关于 Grails 和 Hibernate 条件查询的建议阅读文章。

结语

jQuery DataTables 为 Grails 制作了很棒的表格数据浏览器。对视图进行编码并不是太棘手,但 DataTables 文档中提供的 PHP 示例提供的功能仅到此位置。特别是,它们不是用 Grails 程序员编写的,也不包含探索使用引用其他类(实质上是查找表)的元素的更精细的细节。

我使用这种方法制作了几个数据浏览器,允许用户选择要查看和累积记录计数的列,或者只是浏览数据。即使在相对适度的 VPS 上的百万行表中,性能也很好。

一个警告:我偶然发现了 Grails 中暴露的各种 Hibernate 标准机制的一些问题(请参阅我的其他 GitHub 代码库),因此需要谨慎和实验。如果所有其他方法都失败了,另一种方法是动态构建 SQL 字符串并执行它们。在撰写本文时,我更喜欢使用 Grails 标准,除非我遇到杂乱的子查询,但这可能只反映了我在 Hibernate 中对子查询的相对缺乏经验。

我希望 Grails 程序员发现本文的有趣性。请随时在下面留下评论或建议。


via: https://opensource.com/article/18/9/using-grails-jquery-and-datatables

作者:Chris Hermansen 选题:lujun9972 译者:jrg 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出

容器技术的使用支撑了目前 DevOps 三大主要实践:工作流、及时反馈、持续学习。

有人说容器技术与 DevOps 二者在发展的过程中是互相促进的关系。得益于 DevOps 设计理念的流行,容器生态系统在设计上与组件选择上也有相应发展。同时,由于容器技术在生产环境中的使用,反过来也促进了 DevOps 三大主要实践:支撑 DevOps 的三个实践

工作流

容器中的工作流

每个容器都可以看成一个独立的运行环境,对于容器内部,不需要考虑外部的宿主环境、集群环境,以及其它基础设施。在容器内部,每个功能看起来都是以传统的方式运行。从外部来看,容器内运行的应用一般作为整个应用系统架构的一部分:比如 web API、web app 用户界面、数据库、任务执行、缓存系统、垃圾回收等。运维团队一般会限制容器的资源使用,并在此基础上建立完善的容器性能监控服务,从而降低其对基础设施或者下游其他用户的影响。

现实中的工作流

那些跟“容器”一样业务功能独立的团队,也可以借鉴这种容器思维。因为无论是在现实生活中的工作流(代码发布、构建基础设施,甚至制造 《杰森一家》中的斯贝斯利太空飞轮 等),还是技术中的工作流(开发、测试、运维、发布)都使用了这样的线性工作流,一旦某个独立的环节或者工作团队出现了问题,那么整个下游都会受到影响,虽然使用这种线性的工作流有效降低了工作耦合性。

DevOps 中的工作流

DevOps 中的第一条原则,就是掌控整个执行链路的情况,努力理解系统如何协同工作,并理解其中出现的问题如何对整个过程产生影响。为了提高流程的效率,团队需要持续不断的找到系统中可能存在的性能浪费以及问题,并最终修复它们。

践行这样的工作流后,可以避免将一个已知缺陷带到工作流的下游,避免局部优化导致可能的全局性能下降,要不断探索如何优化工作流,持续加深对于系统的理解。

—— Gene Kim,《支撑 DevOps 的三个实践》,IT 革命,2017.4.25

反馈

容器中的反馈

除了限制容器的资源,很多产品还提供了监控和通知容器性能指标的功能,从而了解当容器工作不正常时,容器内部处于什么样的状态。比如目前流行的 Prometheus,可以用来收集容器和容器集群中相应的性能指标数据。容器本身特别适用于分隔应用系统,以及打包代码和其运行环境,但同时也带来了不透明的特性,这时,从中快速收集信息来解决其内部出现的问题就显得尤为重要了。

现实中的反馈

在现实中,从始至终同样也需要反馈。一个高效的处理流程中,及时的反馈能够快速地定位事情发生的时间。反馈的关键词是“快速”和“相关”。当一个团队被淹没在大量不相关的事件时,那些真正需要快速反馈的重要信息很容易被忽视掉,并向下游传递形成更严重的问题。想象下如果露西和埃塞尔能够很快地意识到:传送带太快了,那么制作出的巧克力可能就没什么问题了(尽管这样就不那么搞笑了)。(LCTT 译注:露西和埃塞尔是上世纪 50 年代的著名黑白情景喜剧《我爱露西》中的主角)

DevOps 中的反馈

DevOps 中的第二条原则,就是快速收集所有相关的有用信息,这样在问题影响到其它开发流程之前就可以被识别出。DevOps 团队应该努力去“优化下游”,以及快速解决那些可能会影响到之后团队的问题。同工作流一样,反馈也是一个持续的过程,目标是快速的获得重要的信息以及当问题出现后能够及时地响应。

快速的反馈对于提高技术的质量、可用性、安全性至关重要。

—— Gene Kim 等人,《DevOps 手册:如何在技术组织中创造世界级的敏捷性,可靠性和安全性》,IT 革命,2016

持续学习

容器中的持续学习

践行第三条原则“持续学习”是一个不小的挑战。在不需要掌握太多边缘的或难以理解的东西的情况下,容器技术让我们的开发工程师和运营团队依然可以安全地进行本地和生产环境的测试,这在之前是难以做到的。即便是一些激进的实验,容器技术仍然让我们轻松地进行版本控制、记录和分享。

现实中的持续学习

举个我自己的例子:多年前,作为一个年轻、初出茅庐的系统管理员(仅仅工作三周),我被安排对一个运行着某个大学核心 IT 部门网站的 Apache 虚拟主机配置进行更改。由于没有方便的测试环境,我直接在生产站点上修改配置,当时觉得配置没问题就发布了,几分钟后,我无意中听到了隔壁同事说:

“等会,网站挂了?”

“没错,怎么回事?”

很多人蒙圈了……

在被嘲讽之后(真实的嘲讽),我一头扎在工作台上,赶紧撤销我之前的更改。当天下午晚些时候,部门主管 —— 我老板的老板的老板 —— 来到我的工位询问发生了什么事。“别担心,”她告诉我。“我们不会责怪你,这是一个错误,现在你已经学会了。”

而在容器中,这种情形在我的笔记本上就很容易测试了,并且也很容易在部署生产环境之前,被那些经验老道的团队成员发现。

DevOps 中的持续学习

持续学习文化的一部分是我们每个人都希望通过一些改变从而能够提高一些东西,并勇敢地通过实验来验证我们的想法。对于 DevOps 团队来说,失败无论对团队还是个人来说都是成长而不是惩罚,所以不要畏惧失败。团队中的每个成员不断学习、共享,也会不断提升其所在团队与组织的水平。

随着系统越来越被细分,我们更需要将注意力集中在具体的点上:上面提到的两条原则主要关注整体流程,而持续学习关注的则是整个项目、人员、团队、组织的未来。它不仅对流程产生了影响,还对流程中的每个人产生影响。

实验和冒险让我们能够不懈地改进我们的工作,但也要求我们尝试之前未用过的工作方式。

—— Gene Kim 等人,《凤凰计划:让你了解 IT、DevOps 以及如何取得商业成功》,IT 革命,2013

容器技术带给 DevOps 的启迪

有效地应用容器技术可以学习 DevOps 的三条原则:工作流,反馈以及持续学习。从整体上看应用程序和基础设施,而不是对容器外的东西置若罔闻,教会我们考虑到系统的所有部分,了解其上游和下游影响,打破隔阂,并作为一个团队工作,以提升整体表现和深度了解整个系统。通过努力提供及时准确的反馈,我们可以在组织内部创建有效的反馈机制,以便在问题发生影响之前发现问题。最后,提供一个安全的环境来尝试新的想法并从中学习,教会我们创造一种文化,在这种文化中,失败一方面促进了我们知识的增长,另一方面通过有根据的猜测,可以为复杂的问题带来新的、优雅的解决方案。


via: https://opensource.com/article/18/9/containers-can-teach-us-devops

作者:Chris Hermansen 选题:lujun9972 译者:littleji 校对:pityonline, wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出

Linux 中高效的备份拷贝命令

在 Linux 上能使用鼠标点来点去的图形化界面是一件很美妙的事……但是如果你喜欢的开发交互环境和编译器是终端窗口、Bash 和 Vim,那你应该像我一样经常和终端打交道。

即使是不经常使用终端的人,如果对终端环境深入了解也能获益良多。举个例子—— cp 命令,据 维基百科) 的解释,cp (意即 copy)命令是第一个版本的 Unix 系统的一部分。连同一组其它的命令 lsmvcdpwdmkdirvishsedawk ,还有提到的 cp 都是我在 1984 年接触 System V Unix 系统时所学习的命令之一。cp 命令最常见的用法是制作文件副本。像这样:

cp sourcefile destfile

在终端中执行此命令,上述命令将名为 sourcefile 的文件复制到名为 destfile 的文件中。如果在执行命令之前 destfile 文件不存在,那将会创建此文件,如果已经存在,那就会覆盖此文件。

这个命令我不知道自己用了多少次了(我也不想知道),但是我知道在我编写测试代码的时候,我经常用,为了保留当前正常的版本,而且又能继续修改,我会输入这个命令:

cp test1.py test1.bak

在过去的30多年里,我使用了无数次这个命令。另外,当我决定编写我的第二个版本的测试程序时,我会输入这个命令:

cp test1.py test2.py

这样就完成了修改程序的第一步。

我通常很少查看 cp 命令的参考文档,但是当我在备份我的图片文件夹的时候(在 GUI 环境下使用 “file” 应用),我开始思考“在 cp 命令中是否有个参数支持只复制新文件或者是修改过的文件。”果然,真的有!

高效用法 1:更新你的文件夹

比如说在我的电脑上有一个存放各种文件的文件夹,另外我要不时的往里面添加一些新文件,而且我会不时地修改一些文件,例如我手机里下载的照片或者是音乐。

假设我收集的这些文件对我而言都很有价值,我有时候会想做个拷贝,就像是“快照”一样将文件保存在其它媒体。当然目前有很多程序都支持备份,但是我想更为精确的将目录结构复制到可移动设备中,方便于我经常使用这些离线设备或者连接到其它电脑上。

cp 命令提供了一个易如反掌的方法。例子如下:

在我的 Pictures 文件夹下,我有这样一个文件夹名字为 Misc。为了方便说明,我把文件拷贝到 USB 存储设备上。让我们开始吧!

me@desktop:~/Pictures$ cp -r Misc /media/clh/4388-D5FE
me@desktop:~/Pictures$

上面的命令是我从按照终端窗口中完整复制下来的。对于有些人来说不是很适应这种环境,在我们输入命令或者执行命令之前,需要注意的是 me@mydesktop:~/Pictures 这个前缀,me 这个是当前用户,mydesktop 这是电脑名称,~/Pictures 这个是当前工作目录,是 /home/me/Pictures 完整路径的缩写。

我输入这个命令 cp -r Misc /media/clh/4388-D5FE 并执行后 ,拷贝 Misc 目录下所有文件(这个 -r 参数,全称 “recursive”,递归处理,意思为本目录下所有文件及子目录一起处理)到我的 USB 设备的挂载目录 /media/clh/4388-D5FE

执行命令后回到之前的提示,大多数命令继承了 Unix 的特性,在命令执行后,如果没有任何异常什么都不显示,在任务结束之前不会显示像 “execution succeeded” 这样的提示消息。如果想获取更多的反馈,就使用 -v 参数让执行结果更详细。

下图中是我的 USB 设备中刚刚拷贝过来的文件夹 Misc ,里面总共有 9 张图片。

 title=

假设我要在原始拷贝路径下 ~/Pictures/Misc 下添加一些新文件,就像这样:

 title=

现在我想只拷贝新的文件到我的存储设备上,我就使用 cp 的“更新”和“详细”选项。

me@desktop:~/Pictures$ cp -r -u -v Misc /media/clh/4388-D5FE
'Misc/asunder.png' -> '/media/clh/4388-D5FE/Misc/asunder.png'
'Misc/editing tags guayadeque.png' -> '/media/clh/4388-D5FE/Misc/editing tags guayadeque.png'
'Misc/misc on usb.png' -> '/media/clh/4388-D5FE/Misc/misc on usb.png'
me@desktop:~/Pictures$

上面的第一行中是 cp 命令和具体的参数(-r 是“递归”, -u 是“更新”,-v 是“详细”)。接下来的三行显示被复制文件的信息,最后一行显示命令行提示符。

通常来说,参数 -r 也可用更详细的风格 --recursive。但是以简短的方式,也可以这么连用 -ruv

高效用法 2:版本备份

回到一开始的例子中,我在开发的时候定期给我的代码版本进行备份。然后我找到了另一种更好用的 cp 参数。

假设我正在编写一个非常有用的 Python 程序,作为一个喜欢不断修改代码的开发者,我会在一开始编写一个程序简单版本,然后不停的往里面添加各种功能直到它能成功的运行起来。比方说我的第一个版本就是用 Python 程序打印出 “hello world”。这只有一行代码的程序就像这样:

print 'hello world'

然后我将这个代码保存成文件命名为 test1.py。我可以这么运行它:

me@desktop:~/Test$ python test1.py
hello world
me@desktop:~/Test$

现在程序可以运行了,我想在添加新的内容之前进行备份。我决定使用带编号的备份选项,如下:

clh@vancouver:~/Test$ cp --force --backup=numbered test1.py test1.py
clh@vancouver:~/Test$ ls
test1.py &nbsp;test1.py.~1~
clh@vancouver:~/Test$ 

所以,上面的做法是什么意思呢?

第一,这个 --backup=numbered 参数意思为“我要做个备份,而且是带编号的连续备份”。所以一个备份就是 1 号,第二个就是 2 号,等等。

第二,如果源文件和目标文件名字是一样的。通常我们使用 cp 命令去拷贝成自己,会得到这样的报错信息:

cp: 'test1.py' and 'test1.py' are the same file

在特殊情况下,如果我们想备份的源文件和目标文件名字相同,我们使用 --force 参数。

第三,我使用 ls (意即 “list”)命令来显示现在目录下的文件,名字为 test1.py 的是原始文件,名字为 test1.py.~1~ 的是备份文件

假如现在我要加上第二个功能,在程序里加上另一行代码,可以打印 “Kilroy was here.”。现在程序文件 test1.py 的内容如下:

print 'hello world'
print 'Kilroy was here'

看到 Python 编程多么简单了吗?不管怎样,如果我再次执行备份的步骤,结果如下:

clh@vancouver:~/Test$ cp --force --backup=numbered test1.py test1.py
clh@vancouver:~/Test$ ls
test1.py test1.py.~1~ test1.py.~2~
clh@vancouver:~/Test$

现在我有有两个备份文件: test1.py.~1~ 包含了一行代码的程序,和 test1.py.~2~ 包含两行代码的程序。

这个很好用的功能,我考虑做个 shell 函数让它变得更简单。

最后总结

第一,Linux 手册页,它在大多数桌面和服务器发行版都默认安装了,它提供了更为详细的使用方法和例子,对于 cp 命令,在终端中输入如下命令:

man cp

对于那些想学习如何使用这些命令,但不清楚如何使用的用户应该首先看一下这些说明,然后我建议创建一个测试目录和文件来尝试使用命令和选项。

第二,兴趣是最好的老师。在你最喜欢的搜索引擎中搜索 “linux shell tutorial”,你会获得很多有趣和有用的资源。

第三,你是不是在想,“为什么我要用这么麻烦的方法,图形化界面中有相同的功能,只用点击几下岂不是更简单?”,关于这个问题我有两个理由。首先,在我们工作中需要中断其他工作流程以及大量使用点击动作时,点击动作可就不简单了。其次,如果我们要完成流水线般的重复性工作,通过使用 shell 脚本和 shell 函数以及 shell 重命名等功能就能很轻松的实现。

你还知道关于 cp 命令其他更棒的使用方式吗?请在留言中积极回复哦~

(题图:stonemaiergames.com)


作者简介:

Chris Hermansen - 1978 年毕业于英国哥伦比亚大学后一直从事计算机相关职业,我从 2005 年开始一直使用 Linux、Solaris、SunOS,在那之前我就是 Unix 系统管理员了,在技术方面,我的大量的职业生涯都是在做数据分析,尤其是空间数据分析,我有大量的编程经验与数据分析经验,熟练使用 awk、Python、PostgreSQL、PostGIS 和 Groovy。


via: https://opensource.com/article/17/7/two-great-uses-cp-command

作者:Chris Hermansen 译者:bigdimple 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出

 title=

音乐是生活的一部分。维基百科关于音乐发展历史的文章有这样一段不错的描述说:“全世界所有的人们,包括哪怕是最孤立、与世隔绝的部落,都会有自己的特色音乐……”好吧,我们开源人就构成了一个部落。我建议我们的“音乐形式”应该包括开源音乐播放器。在过去几年里,我已经使用体验过不少我能接触到的音乐播放器;2016 年 12 月份我根据这六个标准来总结概括了我使用开源音乐播放器的感受:

  1. 必须是能够通过设置让音乐一成不变地转换到 ALSA。(最高分 5分)
  2. 应该有一个不错的“智能播放列表”。(1 分)
  3. 不应该强迫用户只能通过播放列表来进行交互。(1 分)
  4. 应该能够提供一个简单的方法来显示歌曲的封面图片——使用内嵌的封面图或使用在音乐目录里面 cover.jpg(或者 .png)文件替代。
  5. 应该能够在音乐播放的时候显示信号级别和实际比特率。(1 分)
  6. 能够呈现出不错的整体组织,结构布局和执行性能。(1 分)

热心的读者让告诉我有三个播放器是在我的资源仓库里没有的:AqualungLollypopGogglesMM。我并不想在我办公用的电脑里面安装那些来自外面的软件,我承诺过我会配置一个“试验台”来测试这三个音乐播放器,并给出测试的细节。

Aqualung

Aqualung 有一个写的清晰明了的网站来解释它众多的特点。其上提供的说明中我发现其中一点特别有趣:

“你能够(也应该)将你的所有音乐按照艺术家/档案/声轨这样组织成一个树型结构,这样比生成一个一体化的 Winamp/XMMS 播放列表更舒服。”

这点让我有些困惑,因为我总是把我的音乐按照艺术家、专辑和声轨这样组织成树状。但这就可能解释了为什么我有时发现 XMMS 流派的播放器在浏览音乐时有一点古怪。

根据 Aqualung 官网的下载页面说明,官方发布的只有源代码。但是文档上的说明暗示了绝大多数主流的 Linux 发行版本都包括一份 Aqualung 的构建副本,但我当前用的办公电脑所使用的 Linux 发行版 Ubuntu 16.10 并不在此范围内。Launchpad.net 提供有 PPA,但那些软件看起来都有些过时了,所以为什么不试试编译源码安装软件呢?

我根据官网上编译文档的建议和配置脚本的提示安装了 pkgconf 以及 libasoundlibflaclibmp3lamelibvorbislibxml2libglib2.0libgtk+-2.0 的开发库。接下来,我就能够干净利索的进行 configure 然后进行 makemake install。最终我可以执行 /usr/local/bin/aqualung 了。

 title=

Aqualung,不能切换音乐播放的码率。

一旦 Aqualung 启动运行,我就能看到相当简洁直接的两窗口界面:播放器本身和“音乐商店”。我通过右键点击播放器的音乐面板打开参数设置查看这些可设置的参数,看是否能找到 AudioQuest DragonFly 这个数模转换器,但我没有找到任何相关的迹象。然而,站点上的说明指出可以通过命令行指定输出设备。最终我用 plughw 设备才让 Aqualung 启动起来。

在那个时候,真正让我对 Aqualung 感到失望的是 Aqualung 似乎是需要一个固定的输出采样频率。我能够用 Aqualung 播放器的默认设置来正常播放我的 44.1 Khz 文件,但是同样的采样频率播放 96 Khz 的音乐文件时,我不得不关闭软件并重新启动。也正是因为这一点,我不会再继续对 Aqualung 进行使用测评。

无评分。

Lollypop

 title=

优美的 Lollypop 用户界面。

Lollypop 有一个华丽的网站。尽管它不在我办公专用的电脑的软件仓库里面,但是有一个“针对 Ubuntu/Debian 用户的下载”链接带你跳转到 launchpad.net 站点提供的最新的 PPA。这个站点还提供针对 Flatpak、Arch Linux、Fedora 和 OpenSUSE 这些系统的 Lollypop 软件包的下载。我看了下 Fedora COPR 上针对各个 Fedora 版本的 Lollypop 下载链接,看起来 Lollypop 更新的比较及时而且从 Fedora 版本的 23 到 26 都有对应的软件包提供下载安装。

一天内做一次源码编译就足够了,所以我决定试试从 PPA 安装这款软件。我通过命令行来执行 Lollypop 软件。设置菜单能够在 Lollypop 界面的右上方很显眼地看见。更新完我的音乐后,我开始找电脑的输出设备设置,但是在一番查看后,我不知道该怎么选择合适的输出设备。即便我在命令行通过 -help 也找不到有用的帮助信息。

经过一番网上搜索后我找到一个 Lollypop 的开发者的提示才知道我需要 gstreamer libav 来让 Lollypop 工作。通过这个说明我决定停止,因为这可能需要一个 gstreamer 相关配置才有能工作,但是我不太想继续尝试了。

Lollypop 有一个优美的用户交互界面和它的优美的网站相得益彰,但是我现在不会进一步对它进行测评,否则我就又多了一个进一步去学习了解 gstreamer 的理由。

无评分。

GogglesMM

Goggles Music Manager 也有一个在 launchpad.net 及时更新的 PPA;安装流程简单明了,我现在可以在命令行执行 gogglesmm 了。

GogglesMM,非常容易上手使用,看上去和 Rhythmbox 有点像。我在 GogglesMM 的设置里面的参数设置中找到了音频选项设置,能够让我选择 ALSA 和设置音频输出设备。通过查看 /proc/asound/DragonFly/stream0 文件和 DragonFly 自己的 LED 颜色,我确定我能够用 GogglesMM 播放 44.1-KHz/21-bit 和 96-KHz/24-bit 这两种规格的 mp3;因此,就凭 “rate/depth passthrough” 我给 GogglesMM 打 5 分。

 title=

*GogglesMM 在播放 96/24 这种规格的音乐,显示音频输出设备选择。 *

GogglesMM 的说明文档并没有大量的细节介绍,但是我尽可能说明的是,开发者们使用了过滤器来实现类似“智能播放列表”的功能。我在我的测试环境下使用三张专辑来尽我所能检测过滤功能,当我使用“智能播放列表”功能的时候尽管我喜欢我看到的通过过滤筛选出来的歌曲(特别是能够基于广泛的标准来针对歌曲定义筛选条件),但这并不是我认为的“智能播放列表”,对我来说我认为“智能播放列表”应该是这样的,通过借助一些社区数据库来推荐提供和你近期播放的歌曲类似的曲目。或者我该把这个叫作“自动的 DJ”而不是“智能播放列表”,但是通过测试我最终能够确定的是,这个特性并不会在近期版本的 GogglesMM 中出现,所以我给它这个所谓的“智能播放列表”打 0 分。

至于播放列表队列的操作,这款应用能够支持播放你选中的音乐,也能够随机播放音乐或者把一些音乐整合到一个播放列表里面,所以我因为“播放列表的队列选项”给它打 1 分。

同样的,它看起来也能够很好地不需要额外的干预来管理我的音乐艺术封面(每个专辑都包含一张合适的艺术封面, GogglesMM 可以自动识别),所以为“内嵌的艺术封面或者封面图片”打 1 分。

我找不到任何方法来让 GogglesMM 显示信号级别或者实际的比特率。我也不能找到显示比特率和位深度的方法;尽管这款应用能够显示一个“格式”列,但是在我的音乐栏里面除了显示音乐格式不会显示其他的信息了,所以为 GogglesMM 的“信号级别和有效比特率”打 0 分。

至于 GogglesMM 的整体结构,它的所有按钮选项都正好完全符合我的使用习惯。我能够在播放队列里面看到歌曲的时间和歌曲当前已播放的时间所占歌曲总体时间的比例,专辑封面,歌曲名,专辑名和歌唱者。可用的播放栏列表看起来相当大而有用,比如也包括了作曲者。最后,一个真正让我眼前一亮的特点是,音量控制竟然包含了 ALSA 音量。也就是如果我启动 alsamixer 的话,然后不管是在 alsamixer 还是在 GogglesMM 里面调整音量,另一个音量控制也会做相应的音量调整。这个出乎我意外之外的功能相当的酷而且这个功能在其他的音乐播放器上也不常见,因此为它的整体架构给 GogglesMM 加 1 分。

最终 GogglesMM 的这些优点共计得分 8。所表现出来的特点确实很优秀。

评分:8

到目前为止所给出的评分

我之前所提到的这几个开源音乐播放器中,我最喜欢的还是 Guayadeque,根据我制定的标准来进行排名的话,我给 Guayadeque 打满分 10 分。来看下我对这三个开源音乐播放器的评分总结吧(N/R 代表“无评分”,因为我不确定如何配置这些播放器来让它们以完美的码率和贯穿模式工作,以便我的数模信号转换器在相应源的码率和位深度接收 PCM 数据):

 title=

请注意下我用的这个排名方法并不适合每个人。特别是很多人并不清楚高品质音乐的价值,他们更喜欢专有格式的音乐能够给他们带来更好的音乐品质。

与此同时,我会继续评测一些之前向大家承诺的音乐播放器一些和评测评分无关的特性。我特别喜欢 Lollypop 的外观,我也觉得待揭秘的 gstreamer 有一种神秘的魅力,它能让基于 gstreamer 的音乐播放器不用通过转换就能传输它们的数据。

关于音乐的部分……

我还在保持继续购买唱片的习惯,对于唱片的购买我有些不错的推荐。

第一个就是 Nils Frahm 的专辑 Felt,这是我女儿送我的一份非常贴心的礼物。我真的真的很喜欢这张专辑,它的绝大部分歌曲都是在深夜用电麦录制的非常接近钢琴的弦乐,而且也有不少有趣的钢琴演奏的背景音乐,真的是很棒的音乐。至于 Nils Frahm 其他的音乐,这些唱片提供的下载链接允许你下载质量高达 96-KHz,24-bit FLAC 格式的音乐。

第二个就是 Massive Attack 的专辑 Protection 的 Mad Professor 的重混版),专辑名是 No Protection。你可以在这里了解这份专辑,并且如果你想要尝试这份专辑最原始的版本,这里是它的所有汇总信息。该专辑最初发布于 20 世纪 90 年代,这份专辑刻录在唱片上面而且听起来非常奇幻。遗憾的是,不提供下载链接。

第三个就是 Bayonne 的 Primitives这是专辑要表达的想法。Guardian 报社把这份专辑称作是“新式无聊”。那么这种类型的音乐到底怎么样呢?如果这些音乐真的是非常令人乏味的,或许是时候来换份工作了,无论如何你可以试试听这些音乐;或许你会觉得它确实很乏味或者你会像我一样喜欢上这份音乐。

(图片来源:互联网档案馆书中的图片;由 Opensource.com 编辑发布。遵循 CC BY-SA 4.0 协议。)


作者简介:

Chris Hermansen - 自 1978 年毕业于 British Columbia 大学后一直从事计算机相关工作,2005 年之前是 Solaris、SunOS、UNIX System V 的忠实用户,之后是 Linux 的忠实用户。在技术方面,我的职业生涯大部分时间都是在做数据分析;特别是空间数据分析。拥有丰富的和数据分析相关的编程经验,用过的编程语言有 awk,Python、PostgreSQL、 PostGIS 和 最新的 Groovy。


via: https://opensource.com/article/17/1/open-source-music-players

作者:Chris Hermansen 译者:WangYueScream 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出